Skip to content

Commit 32f673f

Browse files
committed
perf(server): index lower user email (#8167)
1 parent a7ecfea commit 32f673f

File tree

2 files changed

+66
-20
lines changed

2 files changed

+66
-20
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- CreateIndex
2+
CREATE INDEX "users_email_lowercase_idx" ON "users"(lower("email"))

packages/backend/server/src/core/user/service.ts

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable, Logger } from '@nestjs/common';
2-
import { Prisma, PrismaClient } from '@prisma/client';
2+
import { Prisma, PrismaClient, User } from '@prisma/client';
33

44
import {
55
Config,
@@ -106,32 +106,76 @@ export class UserService {
106106
});
107107
}
108108

109-
async findUserByEmail(email: string) {
109+
async findUserByEmail(
110+
email: string
111+
): Promise<Pick<User, keyof typeof this.defaultUserSelect> | null> {
110112
validators.assertValidEmail(email);
111-
return this.prisma.user.findFirst({
112-
where: {
113-
email: {
114-
equals: email,
115-
mode: 'insensitive',
116-
},
117-
},
118-
select: this.defaultUserSelect,
119-
});
113+
const rows = await this.prisma.$queryRaw<
114+
// see [this.defaultUserSelect]
115+
{
116+
id: string;
117+
name: string;
118+
email: string;
119+
email_verified: Date | null;
120+
avatar_url: string | null;
121+
registered: boolean;
122+
created_at: Date;
123+
}[]
124+
>`
125+
SELECT "id", "name", "email", "email_verified", "avatar_url", "registered", "created_at"
126+
FROM "users"
127+
WHERE lower("email") = lower(${email})
128+
`;
129+
130+
const user = rows[0];
131+
132+
if (!user) {
133+
return null;
134+
}
135+
136+
return {
137+
...user,
138+
emailVerifiedAt: user.email_verified,
139+
avatarUrl: user.avatar_url,
140+
createdAt: user.created_at,
141+
};
120142
}
121143

122144
/**
123145
* supposed to be used only for `Credential SignIn`
124146
*/
125-
async findUserWithHashedPasswordByEmail(email: string) {
147+
async findUserWithHashedPasswordByEmail(email: string): Promise<User | null> {
126148
validators.assertValidEmail(email);
127-
return this.prisma.user.findFirst({
128-
where: {
129-
email: {
130-
equals: email,
131-
mode: 'insensitive',
132-
},
133-
},
134-
});
149+
150+
// see https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#typing-queryraw-results
151+
const rows = await this.prisma.$queryRaw<
152+
{
153+
id: string;
154+
name: string;
155+
email: string;
156+
password: string | null;
157+
email_verified: Date | null;
158+
avatar_url: string | null;
159+
registered: boolean;
160+
created_at: Date;
161+
}[]
162+
>`
163+
SELECT *
164+
FROM "users"
165+
WHERE lower("email") = lower(${email})
166+
`;
167+
168+
const user = rows[0];
169+
if (!user) {
170+
return null;
171+
}
172+
173+
return {
174+
...user,
175+
emailVerifiedAt: user.email_verified,
176+
avatarUrl: user.avatar_url,
177+
createdAt: user.created_at,
178+
};
135179
}
136180

137181
async signIn(email: string, password: string) {

0 commit comments

Comments
 (0)