mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-30 13:43:59 +08:00
feat: scaffold monorepo
This commit is contained in:
15
apps/portal/src/server/common/get-server-auth-session.ts
Normal file
15
apps/portal/src/server/common/get-server-auth-session.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs
|
||||
|
||||
import type { GetServerSidePropsContext } from 'next';
|
||||
// eslint-disable-next-line camelcase
|
||||
import { unstable_getServerSession } from 'next-auth';
|
||||
|
||||
import { authOptions as nextAuthOptions } from '~/pages/api/auth/[...nextauth]';
|
||||
|
||||
// Next API route example - /pages/api/restricted.ts
|
||||
export const getServerAuthSession = async (ctx: {
|
||||
req: GetServerSidePropsContext['req'];
|
||||
res: GetServerSidePropsContext['res'];
|
||||
}) => {
|
||||
return await unstable_getServerSession(ctx.req, ctx.res, nextAuthOptions);
|
||||
};
|
20
apps/portal/src/server/db/client.ts
Normal file
20
apps/portal/src/server/db/client.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// Src/server/db/client.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { env } from '~/env/server.mjs';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var, init-declarations
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
export const prisma =
|
||||
global.prisma ||
|
||||
new PrismaClient({
|
||||
log:
|
||||
env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
|
||||
if (env.NODE_ENV !== 'production') {
|
||||
global.prisma = prisma;
|
||||
}
|
61
apps/portal/src/server/router/context.ts
Normal file
61
apps/portal/src/server/router/context.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// Src/server/router/context.ts
|
||||
import type { Session } from 'next-auth';
|
||||
import * as trpc from '@trpc/server';
|
||||
import type * as trpcNext from '@trpc/server/adapters/next';
|
||||
|
||||
import { getServerAuthSession } from '~/server/common/get-server-auth-session';
|
||||
import { prisma } from '~/server/db/client';
|
||||
|
||||
type CreateContextOptions = {
|
||||
session: Session | null;
|
||||
};
|
||||
|
||||
/** Use this helper for:
|
||||
* - testing, where we dont have to Mock Next.js' req/res
|
||||
* - trpc's `createSSGHelpers` where we don't have req/res
|
||||
**/
|
||||
export const createContextInner = async (opts: CreateContextOptions) => {
|
||||
return {
|
||||
prisma,
|
||||
session: opts.session,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the actual context you'll use in your router
|
||||
* @link https://trpc.io/docs/context
|
||||
**/
|
||||
export const createContext = async (
|
||||
opts: trpcNext.CreateNextContextOptions,
|
||||
) => {
|
||||
const { req, res } = opts;
|
||||
|
||||
// Get the session from the server using the unstable_getServerSession wrapper function
|
||||
const session = await getServerAuthSession({ req, res });
|
||||
|
||||
return await createContextInner({
|
||||
session,
|
||||
});
|
||||
};
|
||||
|
||||
type Context = trpc.inferAsyncReturnType<typeof createContext>;
|
||||
|
||||
export const createRouter = () => trpc.router<Context>();
|
||||
|
||||
/**
|
||||
* Creates a tRPC router that asserts all queries and mutations are from an authorized user. Will throw an unauthorized error if a user is not signed in.
|
||||
**/
|
||||
export function createProtectedRouter() {
|
||||
return createRouter().middleware(({ ctx, next }) => {
|
||||
if (!ctx.session || !ctx.session.user) {
|
||||
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
// Infers that `session` is non-nullable to downstream resolvers
|
||||
session: { ...ctx.session, user: ctx.session.user },
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
23
apps/portal/src/server/router/example.ts
Normal file
23
apps/portal/src/server/router/example.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createRouter } from './context';
|
||||
|
||||
export const exampleRouter = createRouter()
|
||||
.query('hello', {
|
||||
input: z
|
||||
.object({
|
||||
text: z.string().nullish(),
|
||||
})
|
||||
.nullish(),
|
||||
resolve({ input }) {
|
||||
return {
|
||||
greeting: `Hello ${input?.text ?? 'world'}`,
|
||||
};
|
||||
},
|
||||
})
|
||||
.query('getAll', {
|
||||
async resolve({ ctx }) {
|
||||
const items = await ctx.prisma.example.findMany();
|
||||
return items;
|
||||
},
|
||||
});
|
14
apps/portal/src/server/router/index.ts
Normal file
14
apps/portal/src/server/router/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// Src/server/router/index.ts
|
||||
import superjson from 'superjson';
|
||||
|
||||
import { createRouter } from './context';
|
||||
import { exampleRouter } from './example';
|
||||
import { protectedExampleRouter } from './protected-example-router';
|
||||
|
||||
export const appRouter = createRouter()
|
||||
.transformer(superjson)
|
||||
.merge('example.', exampleRouter)
|
||||
.merge('auth.', protectedExampleRouter);
|
||||
|
||||
// Export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
14
apps/portal/src/server/router/protected-example-router.ts
Normal file
14
apps/portal/src/server/router/protected-example-router.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createProtectedRouter } from './context';
|
||||
|
||||
// Example router with queries that can only be hit if the user requesting is signed in
|
||||
export const protectedExampleRouter = createProtectedRouter()
|
||||
.query('getSession', {
|
||||
resolve({ ctx }) {
|
||||
return ctx.session;
|
||||
},
|
||||
})
|
||||
.query('getSecretMessage', {
|
||||
resolve({ ctx: _ctx }) {
|
||||
return 'He who asks a question is a fool for five minutes; he who does not ask a question remains a fool forever.';
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user