Problem
AuthService.register() (src/modules/auth/auth.service.ts, lines 46–77) enforces uniqueness application-side: findByWallet() then checkUsernameExists() then createProfile() — three separate round trips with no transaction and no reliance on a database-level unique constraint. Two concurrent registrations with the same wallet (double-click, retry storm, deliberate race) both observe "no existing wallet" and both insert. Same for usernames.
Downstream blast radius: identity lookups key on wallet address throughout the platform (loans, reputation reads, liquidity summaries). Duplicate profiles mean some requests resolve to identity A while others hit identity B — split credit history, split balances display, and an authentication ambiguity where findOrCreateUser()'s upsert (line 148) silently adopts whichever row the onConflict target selects. On-chain, one wallet maps to one borrower; off-chain now mapping to two profiles corrupts every reconciliation job.
If the users.wallet_address unique constraint exists in the schema, the second insert errors — but the current code surfaces it as a generic 500 rather than AUTH_WALLET_EXISTS, and username uniqueness handling is equally accidental.
Ground Rules
- Read context/architecture-context.md, context/code-standards.md, context/progress-tracker.md in full
- Read
src/database/repositories/users.repository.ts in full
- Inspect the actual table schema/constraints before coding; do not assume them
What To Build
- Ensure DB-level UNIQUE constraints exist on
users.wallet_address and users.username; add a migration if missing.
- Rewrite
register() to attempt the insert directly and map unique-violation errors to the existing structured 409 codes (AUTH_WALLET_EXISTS, AUTH_USERNAME_TAKEN) — eliminating the pre-check race entirely.
- Wrap profile creation + avatar upload + token issuance failure handling so a failed registration does not leave orphaned avatar objects.
- Tests: parallel duplicate-wallet registrations yield exactly one profile and one 409; duplicate username likewise; sequential re-register returns the same structured 409 as today (compatibility).
Files To Touch
src/modules/auth/auth.service.ts
src/database/repositories/users.repository.ts
- migration file
- tests
- relevant docs/progress tracker
Acceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.
Problem
AuthService.register()(src/modules/auth/auth.service.ts, lines 46–77) enforces uniqueness application-side:findByWallet()thencheckUsernameExists()thencreateProfile()— three separate round trips with no transaction and no reliance on a database-level unique constraint. Two concurrent registrations with the same wallet (double-click, retry storm, deliberate race) both observe "no existing wallet" and both insert. Same for usernames.Downstream blast radius: identity lookups key on wallet address throughout the platform (loans, reputation reads, liquidity summaries). Duplicate profiles mean some requests resolve to identity A while others hit identity B — split credit history, split balances display, and an authentication ambiguity where
findOrCreateUser()'s upsert (line 148) silently adopts whichever row theonConflicttarget selects. On-chain, one wallet maps to one borrower; off-chain now mapping to two profiles corrupts every reconciliation job.If the
users.wallet_addressunique constraint exists in the schema, the second insert errors — but the current code surfaces it as a generic 500 rather thanAUTH_WALLET_EXISTS, and username uniqueness handling is equally accidental.Ground Rules
src/database/repositories/users.repository.tsin fullWhat To Build
users.wallet_addressandusers.username; add a migration if missing.register()to attempt the insert directly and map unique-violation errors to the existing structured 409 codes (AUTH_WALLET_EXISTS,AUTH_USERNAME_TAKEN) — eliminating the pre-check race entirely.Files To Touch
src/modules/auth/auth.service.tssrc/database/repositories/users.repository.tsAcceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.