{"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":"exception","errorClass":"EmailAlreadyUsed","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/user.ts","lineNumber":132,"sourceCode":"    return rows[0] ?? null;\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":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/models/user.ts#L114-L150","documentation":"EmailAlreadyUsed, thrown by UserModel.create (user.ts:174-182): getUserByEmail(data.email, { withDisabled: true }) found an existing row. The lookup is case-insensitive (lower(email)) and includes disabled accounts, so 'A@x.com' collides with 'a@x.com' even when the existing account is disabled - duplicates are refused before the user row is inserted.","triggerScenarios":"create({ email, ... }) where any users row (enabled or disabled) already owns the same lowercased email.","commonSituations":"Re-registering after an account was disabled; OAuth signup colliding with a pre-existing local account; case-variant emails; retried signup requests.","solutions":["Sign in with (or reset the password of) the existing account instead of creating a new one","If the existing account is disabled, re-enable it administratively rather than duplicating the email","Normalize the email (trim + lowercase) before create, and map EmailAlreadyUsed to an 'already registered - sign in' UX"],"exampleFix":"// before\nawait user.create({ email, password }); // throws email_already_used\n\n// after\nconst normalized = email.trim().toLowerCase();\ntry {\n  await user.create({ email: normalized, password });\n} catch (e) {\n  if (e instanceof EmailAlreadyUsed) {\n    return res.status(409).json({ error: 'This email has already been registered.' });\n  }\n  throw e;\n}","handlingStrategy":"validation","validationCode":"const normalized = data.email.trim().toLowerCase();\nconst existing = await user.getUserByEmail(normalized, { withDisabled: true });\nif (existing) {\n  // prompt 'already registered - sign in or reset password' instead of create\n} else {\n  await user.create({ ...data, email: normalized });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await user.create(data);\n} catch (e) {\n  if (e instanceof EmailAlreadyUsed) {\n    return res.status(409).json({ error: 'This email has already been registered.' });\n  }\n  throw e;\n}","preventionTips":["Normalize email casing (trim + lowercase) before create","Remember the duplicate check includes disabled accounts and is case-insensitive","Map the error to a sign-in/reset prompt, not a generic 500"],"tags":["auth","signup","email","duplicate","user"],"backgroundTag":"email-already-registered","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}