Skip to content

Commit

Permalink
feat: employ tRPC (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
exKAZUu authored Sep 11, 2024
1 parent 51ba9ae commit 328ab70
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 14 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"@emotion/styled": "11.13.0",
"@prisma/client": "5.19.1",
"@tanstack/react-query": "5.55.0",
"@trpc/client": "11.0.0-rc.502",
"@trpc/next": "11.0.0-rc.502",
"@trpc/react-query": "11.0.0-rc.502",
"@trpc/server": "11.0.0-rc.502",
"@willbooster/shared-lib-react": "3.2.9",
"@willbooster/wb": "8.0.2",
"build-ts": "13.1.8",
Expand Down
5 changes: 5 additions & 0 deletions src/app/(withAuth)/courses/[courseId]/Course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import NextLink from 'next/link';
import React, { useEffect } from 'react';
import { useLocalStorage } from 'usehooks-ts';

import { backendTrpcReact } from '../../../../infrastructures/trpcBackend/client';
import {
Accordion,
AccordionButton,
Expand Down Expand Up @@ -60,6 +61,10 @@ export const Course: React.FC<{
userCompletedProblems: { programId: string; languageId: VisibleLanguageId }[];
userProblemSessions: UserProblemSessionWithUserAnswers[];
}> = ({ courseId, userCompletedProblems, userProblemSessions }) => {
// TODO: remove the following example code
const getSessionQuery = backendTrpcReact.getSession.useQuery();
console.log('getSessionQuery:', getSessionQuery.isLoading, getSessionQuery.data, getSessionQuery.error);

const [selectedLanguageId, setSelectedLanguageId] = useLocalStorage<VisibleLanguageId>(
selectedLanguageIdKey,
defaultLanguageId
Expand Down
19 changes: 19 additions & 0 deletions src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';

import { logger } from '../../../../infrastructures/pino';
import { createContext } from '../../../../infrastructures/trpcBackend/context';
import { backendRouter } from '../../../../infrastructures/trpcBackend/routers';

// cf.https://trpc.io/docs/server/adapters/nextjs#route-handlers
function handler(req: Request): Promise<Response> {
return fetchRequestHandler({
createContext,
endpoint: '/api/trpc',
onError({ error }) {
logger.error(error);
},
req,
router: backendRouter,
});
}
export { handler as GET, handler as POST };
15 changes: 9 additions & 6 deletions src/components/organisms/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React from 'react';
import { SuperTokensWrapper } from 'supertokens-auth-react';

import { ensureSuperTokensReactInit, setRouter } from '../../infrastructures/supertokens/frontendConfig';
import { backendTrpcReact, backendTrpcReactClient } from '../../infrastructures/trpcBackend/client';
import { ChakraProvider } from '../../infrastructures/useClient/chakra';
import { theme } from '../../theme';

Expand All @@ -19,12 +20,14 @@ export const Providers: React.FC<{ children: React.ReactNode }> = ({ children })

return (
<SuperTokensWrapper>
<QueryClientProvider client={queryClient}>
{/* Chakra UI */}
<CacheProvider>
<ChakraProvider theme={theme}>{children}</ChakraProvider>
</CacheProvider>
</QueryClientProvider>
<backendTrpcReact.Provider client={backendTrpcReactClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{/* Chakra UI */}
<CacheProvider>
<ChakraProvider theme={theme}>{children}</ChakraProvider>
</CacheProvider>
</QueryClientProvider>
</backendTrpcReact.Provider>
</SuperTokensWrapper>
);
};
4 changes: 2 additions & 2 deletions src/components/organisms/TurtleGraphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react';

import {
TURTLE_GRAPHICS_DEFAULT_COLOR as DEFAULT_COLOR,
TURTLE_GRAPHICS_GRID_COLUMNS as GRID_COLUMNS,
TURTLE_GRAPHICS_GRID_ROWS as GRID_ROWS,
TURTLE_GRAPHICS_GRID_SIZE as GRID_SIZE,
TURTLE_GRAPHICS_DEFAULT_COLOR as DEFAULT_COLOR,
} from '../../constants';
import { Box, Grid, GridItem, Image } from '../../infrastructures/useClient/chakra';
import type { Problem } from '../../problems/generateProblem';
import { charToColor, type CharacterTrace, type TraceItem } from '../../problems/traceProgram';
import { type CharacterTrace, charToColor, type TraceItem } from '../../problems/traceProgram';
import type { ColorChar, SelectedCell } from '../../types';
import { TurtleGraphicsController } from '../molecules/TurtleGraphicsController';

Expand Down
12 changes: 12 additions & 0 deletions src/infrastructures/trpcBackend/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client';

import { createTRPCReact, httpBatchLink } from '@trpc/react-query';

import type { BackendRouter } from './routers';

// https://trpc.io/docs/client/react/setup
export const backendTrpcReact = createTRPCReact<BackendRouter>();

export const backendTrpcReactClient = backendTrpcReact.createClient({
links: [httpBatchLink({ url: '/api/trpc' })],
});
15 changes: 15 additions & 0 deletions src/infrastructures/trpcBackend/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { inferRouterContext } from '@trpc/server';
import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';

import { logger } from '../pino';

import type { BackendRouter } from './routers';

export type Context = FetchCreateContextFnOptions;

// cf. https://trpc.io/docs/server/adapters/fastify#create-the-context
export function createContext(options: FetchCreateContextFnOptions): inferRouterContext<BackendRouter> {
logger.debug(`%s %s` + (options.req.body ? `\n%o` : ''), options.req.method, options.req.url, options.req.body);

return options;
}
27 changes: 27 additions & 0 deletions src/infrastructures/trpcBackend/middlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TRPCError } from '@trpc/server';

import { getSessionOnNode } from '../../utils/sessionOnNode';
import { logger } from '../pino';

import { middleware } from './trpc';

const accessTokenRegex = /(^|;)\s*sAccessToken=([^;]*)/;

// https://dev.to/franciscomendes10866/authentication-and-authorization-in-a-node-api-using-fastify-trpc-and-supertokens-3cgn
export const authorize = middleware(async ({ ctx, next }) => {
logger.trace('Headers: %o', ctx.req.headers);

let session;
try {
const match = ctx.req.headers.get('Cookie')?.match(accessTokenRegex);
const token = match && decodeURIComponent(match[2]);
if (token) session = await getSessionOnNode(token);
} catch (error) {
logger.warn(error);
}
if (!session) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({ ctx: { session } });
});
14 changes: 14 additions & 0 deletions src/infrastructures/trpcBackend/routers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from 'zod';

import { authorize } from '../middlewares';
import { procedure, router } from '../trpc';

export const backendRouter = router({
getSession: procedure
.use(authorize)
.output(z.object({ userId: z.string().uuid() }))
.query(({ ctx }) => ({ userId: ctx.session.superTokensUserId })),
});

// export type definition of API
export type BackendRouter = typeof backendRouter;
10 changes: 10 additions & 0 deletions src/infrastructures/trpcBackend/trpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// cf. https://trpc.io/docs/client/nextjs/setup#3-create-a-trpc-router
import { initTRPC } from '@trpc/server';

import type { Context } from './context';

const t = initTRPC.context<Context>().create();

export const middleware = t.middleware;
export const router = t.router;
export const procedure = t.procedure;
6 changes: 6 additions & 0 deletions src/infrastructures/trpcBackend/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';

import type { BackendRouter } from './routers';

export type BackendRouterInputs = inferRouterInputs<BackendRouter>;
export type BackendRouterOutputs = inferRouterOutputs<BackendRouter>;
2 changes: 1 addition & 1 deletion src/problems/generateProblem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Random } from '../app/lib/random';

import type { LanguageId, ProgramId } from './problemData';
import { programIdToLanguageIdToProgram } from './problemData';
import { traceProgram, type TraceItem } from './traceProgram';
import { type TraceItem, traceProgram } from './traceProgram';

export type Problem = {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/problems/traceProgram.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
TURTLE_GRAPHICS_DEFAULT_COLOR as DEFAULT_COLOR,
TURTLE_GRAPHICS_EMPTY_COLOR as EMPTY_COLOR,
TURTLE_GRAPHICS_GRID_COLUMNS as GRID_COLUMNS,
TURTLE_GRAPHICS_GRID_ROWS as GRID_ROWS,
TURTLE_GRAPHICS_EMPTY_COLOR as EMPTY_COLOR,
TURTLE_GRAPHICS_DEFAULT_COLOR as DEFAULT_COLOR,
} from '../constants';
import type { CellColor, ColorChar } from '../types';

Expand Down
3 changes: 0 additions & 3 deletions src/utils/session.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { logger } from '../infrastructures/pino';
import { prisma } from '../infrastructures/prisma';
import { ensureSuperTokensInit } from '../infrastructures/supertokens/backendConfig';

import type { SessionOnNode } from './sessionOnNode';
import { getSessionOnServer } from './sessionOnServer';

ensureSuperTokensInit();

export interface SessionOnServer {
session: SessionOnNode | undefined;
hasToken: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/utils/sessionOnNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { verify } from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import SuperTokensNode from 'supertokens-node';

import { ensureSuperTokensInit } from '../infrastructures/supertokens/backendConfig';

ensureSuperTokensInit();
const client = jwksClient({
jwksUri: `${process.env.SUPERTOKENS_URI}/.well-known/jwks.json`,
});
Expand Down
53 changes: 53 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4188,6 +4188,55 @@ __metadata:
languageName: node
linkType: hard

"@trpc/client@npm:11.0.0-rc.502":
version: 11.0.0-rc.502
resolution: "@trpc/client@npm:11.0.0-rc.502"
peerDependencies:
"@trpc/server": 11.0.0-rc.502+2a8c56027
checksum: 10c0/1785a6fe90d70037629e52fe8dc6d9caae373c8a8306ce2a6f9976a853648ecedabf8d5ebe4f6d6ddfa6f32ad42b3f69d5e99e0b958b3dcc5505d79b91d93b67
languageName: node
linkType: hard

"@trpc/next@npm:11.0.0-rc.502":
version: 11.0.0-rc.502
resolution: "@trpc/next@npm:11.0.0-rc.502"
peerDependencies:
"@tanstack/react-query": ^5.49.2
"@trpc/client": 11.0.0-rc.502+2a8c56027
"@trpc/react-query": 11.0.0-rc.502+2a8c56027
"@trpc/server": 11.0.0-rc.502+2a8c56027
next: "*"
react: ">=16.8.0"
react-dom: ">=16.8.0"
peerDependenciesMeta:
"@tanstack/react-query":
optional: true
"@trpc/react-query":
optional: true
checksum: 10c0/2ebb92ffdd0cd83e469e9be8fb15d6d648f29f6cb6ce4e56f08369a5ddc011e57a6910fc0b7f3e6c1b68da369ae0712b07ba81d8b33f062c6b2c422343ba6648
languageName: node
linkType: hard

"@trpc/react-query@npm:11.0.0-rc.502":
version: 11.0.0-rc.502
resolution: "@trpc/react-query@npm:11.0.0-rc.502"
peerDependencies:
"@tanstack/react-query": ^5.49.2
"@trpc/client": 11.0.0-rc.502+2a8c56027
"@trpc/server": 11.0.0-rc.502+2a8c56027
react: ">=18.2.0"
react-dom: ">=18.2.0"
checksum: 10c0/fbdb2c93ea457b1b2bafd203b3c0f681b1b2a2fa91fbefe0c7a7d1e31393b8f088aa8c503683273f625f4b790f9651651a67c485d577929605786c5b6f71ca93
languageName: node
linkType: hard

"@trpc/server@npm:11.0.0-rc.502":
version: 11.0.0-rc.502
resolution: "@trpc/server@npm:11.0.0-rc.502"
checksum: 10c0/f700d03549a8978138da56ff263f032de20a4b9d5c9131415a4c5e9dfdc3701b2cfc2fc7a88a563262fb2c2e525fc311450a84bb266f7b90589ff672d6817361
languageName: node
linkType: hard

"@types/body-parser@npm:*":
version: 1.19.5
resolution: "@types/body-parser@npm:1.19.5"
Expand Down Expand Up @@ -12346,6 +12395,10 @@ __metadata:
"@emotion/styled": "npm:11.13.0"
"@prisma/client": "npm:5.19.1"
"@tanstack/react-query": "npm:5.55.0"
"@trpc/client": "npm:11.0.0-rc.502"
"@trpc/next": "npm:11.0.0-rc.502"
"@trpc/react-query": "npm:11.0.0-rc.502"
"@trpc/server": "npm:11.0.0-rc.502"
"@types/cookie": "npm:0.6.0"
"@types/eslint": "npm:9.6.1"
"@types/jsonwebtoken": "npm:9.0.6"
Expand Down

0 comments on commit 328ab70

Please sign in to comment.