From ca63dddbdd221198bcb563dcfe826e9bcfb94b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenneth=20Wu=C3=9Fmann?= Date: Wed, 28 Feb 2024 00:35:48 +0100 Subject: [PATCH] Streamline prisma initialization (#107) --- src/server/api/routers/server.ts | 4 +- src/server/api/trpc.ts | 41 ++------------------ src/server/auth/auth.ts | 4 +- src/server/auth/customCredentialsProvider.ts | 14 +++---- src/server/db.ts | 16 -------- src/server/lib/applicationContext.ts | 7 +++- src/server/lib/user/userService.ts | 7 ++++ 7 files changed, 28 insertions(+), 65 deletions(-) delete mode 100644 src/server/db.ts diff --git a/src/server/api/routers/server.ts b/src/server/api/routers/server.ts index e8fc988..f1cb1ce 100644 --- a/src/server/api/routers/server.ts +++ b/src/server/api/routers/server.ts @@ -5,11 +5,11 @@ import { protectedProcedure, publicProcedure, } from "~/server/api/trpc"; -import { db } from "~/server/db"; +import { defaultApplicationContext } from "~/server/lib/applicationContext"; const isDatabaseHealthy = async () => { try { - await db.$queryRaw`SELECT 1`; + await defaultApplicationContext.prismaClient.$queryRaw`SELECT 1`; return true; } catch (e) { return false; diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 1f5e53d..9ed9dd2 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -9,47 +9,14 @@ import { initTRPC, TRPCError } from "@trpc/server"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; -import { type Session } from "next-auth"; import superjson from "superjson"; import { ZodError } from "zod"; -import { ApplicationContext } from "~/server/lib/applicationContext"; +import { defaultApplicationContext } from "~/server/lib/applicationContext"; import { getServerAuthSession } from "~/server/auth/auth"; -import { db } from "~/server/db"; import { type RateLimitType } from "../lib/rate-limit/rateLimitService"; import { UserRole } from "@prisma/client"; -/** - * 1. CONTEXT - * - * This section defines the "contexts" that are available in the backend API. - * - * These allow you to access things when processing a request, like the database, the session, etc. - */ - -interface CreateContextOptions { - session: Session | null; - remoteAddress: string; - applicationContext: ApplicationContext; -} - -/** - * This helper generates the "internals" for a tRPC context. If you need to use it, you can export - * it from here. - * - * Examples of things you may need it for: - * - testing, so we don't have to mock Next.js' req/res - * - tRPC's `createSSGHelpers`, where we don't have req/res - * - * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts - */ -const createInnerTRPCContext = (opts: CreateContextOptions) => { - return { - ...opts, - db, - }; -}; - /** * This is the actual context you will use in your router. It will be used to process every request * that goes through your tRPC endpoint. @@ -77,11 +44,11 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { }); } - return createInnerTRPCContext({ + return { session, remoteAddress, - applicationContext: new ApplicationContext(), - }); + applicationContext: defaultApplicationContext, + }; }; /** diff --git a/src/server/auth/auth.ts b/src/server/auth/auth.ts index 3845d07..72a8932 100644 --- a/src/server/auth/auth.ts +++ b/src/server/auth/auth.ts @@ -5,8 +5,8 @@ import { type DefaultSession, type NextAuthOptions, } from "next-auth"; -import { db } from "~/server/db"; import { providers } from "./providers"; +import { defaultApplicationContext } from "../lib/applicationContext"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -46,7 +46,7 @@ export const authOptions: NextAuthOptions = { pages: { signIn: "/auth/signin", }, - adapter: PrismaAdapter(db), + adapter: PrismaAdapter(defaultApplicationContext.prismaClient), providers, }; diff --git a/src/server/auth/customCredentialsProvider.ts b/src/server/auth/customCredentialsProvider.ts index c2eb8d3..725a7cb 100644 --- a/src/server/auth/customCredentialsProvider.ts +++ b/src/server/auth/customCredentialsProvider.ts @@ -1,5 +1,4 @@ import CredentialsProvider from "next-auth/providers/credentials"; -import { db } from "../db"; import { verifyPassword } from "../lib/utils/passwordUtils"; import { defaultApplicationContext } from "../lib/applicationContext"; import { sanitizeEmail } from "../lib/utils/emailUtils"; @@ -13,7 +12,7 @@ export const CustomCredentialsProvider = () => password: { label: "Password", type: "password" }, }, async authorize(credentials, req): Promise { - const { rateLimitService } = defaultApplicationContext; + const { rateLimitService, userService } = defaultApplicationContext; if (!credentials) { return null; @@ -39,11 +38,12 @@ export const CustomCredentialsProvider = () => } try { - const user = await db.user.findFirst({ - where: { - email, - }, - }); + const user = await userService.findUserByEmail(email); + + if (!user) { + await rateLimitService.consume("login_failed_by_ip", ip); + return null; + } const isLoggedIn = user?.password && diff --git a/src/server/db.ts b/src/server/db.ts deleted file mode 100644 index a4753ca..0000000 --- a/src/server/db.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PrismaClient } from "@prisma/client"; - -import { env } from "~/env.mjs"; - -const globalForPrisma = globalThis as unknown as { - prisma: PrismaClient | undefined; -}; - -export const db = - globalForPrisma.prisma ?? - new PrismaClient({ - log: - env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], - }); - -if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; diff --git a/src/server/lib/applicationContext.ts b/src/server/lib/applicationContext.ts index edfa549..2559ebd 100644 --- a/src/server/lib/applicationContext.ts +++ b/src/server/lib/applicationContext.ts @@ -21,7 +21,12 @@ import { TeamCreationService } from "./team/TeamCreationService"; import { ImportService } from "./import/importService"; export class ApplicationContext { - public readonly prismaClient = new PrismaClient(); + public readonly prismaClient = new PrismaClient({ + log: + process.env.LOG_LEVEL === "debug" + ? ["query", "error", "warn"] + : ["error"], + }); public readonly logger = createLogger({}); private readonly meiliSearch = new MeiliSearch({ host: process.env.MEILI_URL ?? "http://127.0.0.1:7700", diff --git a/src/server/lib/user/userService.ts b/src/server/lib/user/userService.ts index d14d43e..8f3e7a1 100644 --- a/src/server/lib/user/userService.ts +++ b/src/server/lib/user/userService.ts @@ -40,6 +40,13 @@ export class UserService { }); }; + public findUserByEmail = async (email: string) => { + const user = await this.prisma.user.findUnique({ + where: { email: sanitizeEmail(email) }, + }); + return user; + }; + public changePassword = async ( userId: string, input: UserChangePasswordRequest