{"record":{"id":"e22d86a53f9fcb6b","repo":"toeverything/AFFiNE","slug":"email-already-used-e22d86","errorCode":"email_already_used","errorMessage":"This email has already been registered.","messagePattern":"This email has already been registered\\.","errorType":"error_code","errorClass":"EmailAlreadyUsed","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/user.ts","lineNumber":180,"sourceCode":"    return user;\n  }\n\n  async getPublicUserByEmail(email: string): Promise<PublicUser | null> {\n    const rows = await this.db.$queryRaw<PublicUser[]>`\n      SELECT id, name, avatar_url as \"avatarUrl\"\n      FROM \"users\"\n      WHERE lower(\"email\") = lower(${email})\n      AND disabled = false\n    `;\n\n    return rows[0] ?? null;\n  }\n\n  async create(data: CreateUserInput) {\n    let user = await this.getUserByEmail(data.email, { withDisabled: true });\n\n    if (user) {\n      throw new EmailAlreadyUsed();\n    }\n\n    if (data.password) {\n      data.password = await this.crypto.encryptPassword(data.password);\n    }\n\n    user = await this.db.user.create({\n      data: {\n        ...data,\n        name: data.name ?? data.email.split('@')[0],\n      },\n    });\n\n    // delegate the responsibility of finish user creating setup to the corresponding models\n    await this.event.emitAsync('user.postCreated', user);\n\n    this.logger.debug(`User [${user.id}] created with email [${user.email}]`);\n    this.event.emit('user.created', user);","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/models/user.ts#L162-L198","documentation":"Thrown by UserModel.create (user.ts:180) when getUserByEmail(withDisabled:true) finds any user at that email — disabled accounts included — to prevent duplicate identities and shadow accounts. The lookup is case-insensitive on lower(email). UserFriendlyError, code email_already_used, type resource_already_exists, HTTP 400.","triggerScenarios":"POST /sign-up or UserModel.create with an email that already exists (active or disabled); re-importing a user CSV that contains already-existing emails.","commonSituations":"User forgot they already registered; a disabled account still occupies the email; test fixtures collide with real users; case variants (User@x vs user@x) collide because the lookup is case-insensitive.","solutions":["Sign in instead of signing up.","If the existing account is disabled, have an admin re-enable it rather than recreating it.","Normalize (trim + lowercase) emails before lookup to avoid case/whitespace collisions.","For imports, skip or update existing rows instead of calling create."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function createIfFree(models, input: CreateUserInput) {\n  const existing = await models.user.getUserByEmail(input.email, { withDisabled: true });\n  if (existing) {\n    throw new Error('Email already registered; sign in or re-enable the account.');\n  }\n  return models.user.create(input);\n}","typeGuard":null,"tryCatchPattern":"import { EmailAlreadyUsed } from '../base/error/errors.gen';\n\ntry {\n  await models.user.create(input);\n} catch (e) {\n  if (e instanceof EmailAlreadyUsed) {\n    // redirect to sign-in / password reset\n  } else throw e;\n}","preventionTips":["Normalize email (trim + lowercase) before lookup to avoid case/whitespace duplicates.","Treat disabled accounts as occupied when checking availability.","For imports, skip-or-update existing rows rather than calling create blindly."],"tags":["auth","signup","uniqueness","email"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}