refactor: use userRepository class methods for signup handlers (#29561)

* refactor: use userRepository class methods for signup handlers

* feat: add user repo modules for DI

* feat: build user repo container

* test: integration test for user repository

* fix: use DI container in selfhosted

* refactor: tests, module, container and handlers

---------

Co-authored-by: Bandhan Majumder <bandhanmajumder16@gmail.com>
This commit is contained in:
Anirban SinghaandBandhan Majumder authored and GitHub committed 2026-06-14 16:11:21 +00:00
1 parent 642c38582e
commit dcd0da831f
5 files changed
+399 -106

No files matched your search

@@ -13,7 +13,7 @@ import {
} from "@calcom/features/auth/signup/utils/token";
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/features/auth/signup/utils/validateUsername";
import { getFeatureRepository } from "@calcom/features/di/containers/FeatureRepository";
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { getUserRepository } from "@calcom/features/di/containers/UserRepository";
import { GlobalWatchlistRepository } from "@calcom/features/watchlist/lib/repository/GlobalWatchlistRepository";
import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry";
import { normalizeEmail } from "@calcom/features/watchlist/lib/utils/normalization";
@@ -26,7 +26,7 @@ import { isPrismaError } from "@calcom/lib/server/getServerErrorFromUnknown";
import type { CustomNextApiHandler } from "@calcom/lib/server/username";
import { usernameHandler } from "@calcom/lib/server/username";
import { getTrackingFromCookies } from "@calcom/lib/tracking";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import {
CreationSource,
IdentityProvider,
@@ -65,6 +65,8 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
})
.parse(body);
const userRepository = getUserRepository();
const shouldLockByDefault = await checkIfEmailIsBlockedInWatchlistController({
email: _email,
organizationId: null,
@@ -103,10 +105,8 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
});
if (foundToken?.teamId) {
const existingUser = await prisma.user.findUnique({
where: { email },
select: { invitedTo: true },
});
const existingUser = await userRepository.findByEmailWithInvitedTo({email})
if (existingUser && existingUser.invitedTo !== foundToken.teamId) {
return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
}
@@ -197,14 +197,11 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
const organizationId = team.isOrganization ? team.id : (team.parent?.id ?? null);
if (username) {
const existingUserByUsername = await prisma.user.findFirst({
where: {
username,
organizationId,
NOT: { email },
},
select: { id: true },
});
const existingUserByUsername = await userRepository.findByUsernameAndOrganizationId({
username,
organizationId,
excludeEmail: email
})
if (existingUserByUsername) {
return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
}
@@ -212,30 +209,14 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
let user: { id: number };
try {
user = await prisma.user.upsert({
where: { email },
update: {
username,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
password: {
upsert: {
create: { hash: hashedPassword },
update: { hash: hashedPassword },
},
},
organizationId,
},
create: {
username,
email,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
password: { create: { hash: hashedPassword } },
organizationId,
},
select: { id: true },
});
user = await userRepository.upsertForSignup({
email,
username,
hashedPassword,
organizationId,
emailVerified: new Date(),
identityProvider: IdentityProvider.CAL
})
} catch (error) {
if (isPrismaError(error) && error.code === "P2002") {
const target = String(error.meta?.target ?? "");
@@ -269,19 +250,19 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
} else {
// Create the user
try {
await prisma.user.create({
data: {
username,
email,
locked: shouldLockByDefault,
password: { create: { hash: hashedPassword } },
metadata: {
stripeCustomerId: customer.stripeCustomerId,
checkoutSessionId,
},
creationSource: CreationSource.WEBAPP,
},
});
await userRepository.create({
username,
email,
hashedPassword,
organizationId: null,
creationSource: CreationSource.WEBAPP,
locked: shouldLockByDefault,
identityProvider: IdentityProvider.CAL,
metadata: {
stripeCustomerId: customer.stripeCustomerId,
checkoutSessionId,
}
})
} catch (error) {
// Fallback for race conditions where user was created between our check and create
if (isPrismaError(error) && error.code === "P2002") {
@@ -316,7 +297,6 @@ const handler: CustomNextApiHandler = async (body, usernameStatus, query) => {
});
}
const userRepository = new UserRepository(prisma);
await userRepository.lockByEmail({ email });
return NextResponse.json(
@@ -16,14 +16,18 @@ import logger from "@calcom/lib/logger";
import { isPrismaError } from "@calcom/lib/server/getServerErrorFromUnknown";
import { isUsernameReservedDueToMigration } from "@calcom/lib/server/username";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
import { signupSchema } from "@calcom/prisma/zod-utils";
import { NextResponse } from "next/server";
import { getUserRepository } from "@calcom/features/di/containers/UserRepository";
import { CreationSource } from "@calcom/prisma/enums";
export default async function handler(body: Record<string, string>) {
const { email, password, language, token } = signupSchema.parse(body);
const userRepository = getUserRepository()
const username = slugify(body.username);
const userEmail = email.toLowerCase();
@@ -44,10 +48,10 @@ export default async function handler(body: Record<string, string>) {
});
if (foundToken?.teamId) {
const existingUser = await prisma.user.findUnique({
where: { email: userEmail },
select: { invitedTo: true },
});
const existingUser = await userRepository.findByEmailWithInvitedTo({
email: userEmail
})
if (existingUser && existingUser.invitedTo !== foundToken.teamId) {
return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
}
@@ -100,44 +104,26 @@ export default async function handler(body: Record<string, string>) {
const organizationId = team.isOrganization ? team.id : (team.parent?.id ?? null);
const existingUserByUsername = await prisma.user.findFirst({
where: {
username: correctedUsername,
organizationId,
NOT: { email: userEmail },
},
select: { id: true },
});
const existingUserByUsername = await userRepository.findByUsernameAndOrganizationId({
username: correctedUsername,
organizationId,
excludeEmail: userEmail
})
if (existingUserByUsername) {
return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
}
let user: { id: number };
try {
user = await prisma.user.upsert({
where: { email: userEmail },
update: {
username: correctedUsername,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
password: {
upsert: {
create: { hash: hashedPassword },
update: { hash: hashedPassword },
},
},
organizationId,
},
create: {
username: correctedUsername,
email: userEmail,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
password: { create: { hash: hashedPassword } },
organizationId,
},
select: { id: true },
});
user = await userRepository.upsertForSignup({
email: userEmail,
username: correctedUsername,
hashedPassword,
organizationId,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL
})
} catch (error) {
if (isPrismaError(error) && error.code === "P2002") {
const target = String(error.meta?.target ?? "");
@@ -174,15 +160,15 @@ export default async function handler(body: Record<string, string>) {
return NextResponse.json({ message: "A user exists with that username" }, { status: 409 });
}
try {
await prisma.user.create({
data: {
username: correctedUsername,
email: userEmail,
password: { create: { hash: hashedPassword } },
identityProvider: IdentityProvider.CAL,
},
select: { id: true },
});
await userRepository.create({
username: correctedUsername,
email: userEmail,
hashedPassword,
organizationId: null,
creationSource: CreationSource.WEBAPP,
identityProvider: IdentityProvider.CAL,
locked: false
})
} catch (error) {
// Fallback for race conditions where user was created between our check and create
if (isPrismaError(error) && error.code === "P2002") {