From 32f673fa3d125297ccbce4c71c38270118159f56 Mon Sep 17 00:00:00 2001 From: forehalo Date: Mon, 9 Sep 2024 04:43:59 +0000 Subject: [PATCH] perf(server): index lower user email (#8167) --- .../migration.sql | 2 + .../backend/server/src/core/user/service.ts | 84 ++++++++++++++----- 2 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 packages/backend/server/migrations/20240909100000_lower_index_user_email/migration.sql diff --git a/packages/backend/server/migrations/20240909100000_lower_index_user_email/migration.sql b/packages/backend/server/migrations/20240909100000_lower_index_user_email/migration.sql new file mode 100644 index 0000000000..d99eb562de --- /dev/null +++ b/packages/backend/server/migrations/20240909100000_lower_index_user_email/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "users_email_lowercase_idx" ON "users"(lower("email")) diff --git a/packages/backend/server/src/core/user/service.ts b/packages/backend/server/src/core/user/service.ts index d5a2d7d317..f2b10f79e7 100644 --- a/packages/backend/server/src/core/user/service.ts +++ b/packages/backend/server/src/core/user/service.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -import { Prisma, PrismaClient } from '@prisma/client'; +import { Prisma, PrismaClient, User } from '@prisma/client'; import { Config, @@ -106,32 +106,76 @@ export class UserService { }); } - async findUserByEmail(email: string) { + async findUserByEmail( + email: string + ): Promise | null> { validators.assertValidEmail(email); - return this.prisma.user.findFirst({ - where: { - email: { - equals: email, - mode: 'insensitive', - }, - }, - select: this.defaultUserSelect, - }); + const rows = await this.prisma.$queryRaw< + // see [this.defaultUserSelect] + { + id: string; + name: string; + email: string; + email_verified: Date | null; + avatar_url: string | null; + registered: boolean; + created_at: Date; + }[] + >` + SELECT "id", "name", "email", "email_verified", "avatar_url", "registered", "created_at" + FROM "users" + WHERE lower("email") = lower(${email}) + `; + + const user = rows[0]; + + if (!user) { + return null; + } + + return { + ...user, + emailVerifiedAt: user.email_verified, + avatarUrl: user.avatar_url, + createdAt: user.created_at, + }; } /** * supposed to be used only for `Credential SignIn` */ - async findUserWithHashedPasswordByEmail(email: string) { + async findUserWithHashedPasswordByEmail(email: string): Promise { validators.assertValidEmail(email); - return this.prisma.user.findFirst({ - where: { - email: { - equals: email, - mode: 'insensitive', - }, - }, - }); + + // see https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#typing-queryraw-results + const rows = await this.prisma.$queryRaw< + { + id: string; + name: string; + email: string; + password: string | null; + email_verified: Date | null; + avatar_url: string | null; + registered: boolean; + created_at: Date; + }[] + >` + SELECT * + FROM "users" + WHERE lower("email") = lower(${email}) + `; + + const user = rows[0]; + if (!user) { + return null; + } + + return { + ...user, + emailVerifiedAt: user.email_verified, + avatarUrl: user.avatar_url, + createdAt: user.created_at, + }; } async signIn(email: string, password: string) {