Chat mode imported from briosoemilio/ambagan-be (
.github/chatmodes/user.chatmode.md). Copyright stays with the author.
Master agent for the users Firestore collection in this Express/Firebase
Functions backend. A user document is the app-side profile that mirrors a
Firebase Auth user.
Scope
Owns the users collection: its CRUD routes and the Auth trigger that
creates the user document. Aware of but does not own: every other collection
denormalizes a slice of the user doc (displayName/photoURL → name/
photoUrl) into embedded objects — project owner/members, ambag
contributor, invitation sentBy, expense payer/creator fields. Changing
the shape of the user document does not retroactively update those
denormalized copies.
Key files
functions/src/routes/users.ts— CRUD routes:GET /,GET /:id,POST /,PATCH /:id,DELETE /:id.functions/src/index.ts(bottom of file) —exports.onUserCreate, afunctions.region("asia-southeast1").auth.user().onCreate(...)v1 trigger that fires on Firebase Auth account creation and writes the initial user document (email,displayName,photoURL,createdAt) tousers/{uid}. This is the real provisioning logic — notfunctions/src/services/onUserCreate.ts, which currently exists but is an empty file.functions/src/constants/Collection.ts—Collection.USERS.functions/src/utils/validateDocument.ts— used byGET /:id,PATCH /:id,DELETE /:idto 404 on a missing document.
Data shape (as used in code, not formally schematized)
A user document (keyed by Firebase Auth uid) has: email, displayName,
photoURL, createdAt. There is no UserSchema.ts under
functions/src/schemas/ — POST/PATCH accept the raw request body with
no validation. Flag this if asked to harden the routes.
Conventions to follow
usersRouter.use(authenticated)gates every route (matchesambagsRouter's pattern).GET /andGET /:idare open to any authenticated user (mirrors how member info is visible elsewhere, e.g. project members).PATCH /:idandDELETE /:idadditionally requirereq.user?.uid === req.params.id— a user may only modify or delete their own document; otherwise a plain 403 is returned. There is no admin/elevated role in this codebase, so this self-only check is the full authorization model — don't add a broader "admin can edit anyone" path without an explicit role system to back it.- All five routes now consistently use
Collection.USERS(previouslyGET /andPOST /used the raw string"users"— fixed). - Errors:
logger.errorthenres.status(...).send(...), matching the rest of the codebase. - User doc creation should normally happen via the
onUserCreateAuth trigger, keyed byuid.POST /usersinstead creates a document with an auto-generated id via.add(...), which does not match theuid-keyed documents the trigger creates — a client using this endpoint to self-register would produce a user doc whose id doesn't correspond to any Firebase Auth uid. This divergence is still unresolved — flag it before extendingPOST /, since fixing it (e.g. switching to.doc(uid).set(...)) changes the endpoint's contract and needs explicit sign-off.
When making changes
- If a change affects
displayName/photoURL/emailfield names, check every other route file for denormalized copies (owner,members,contributor,sentBy) — they're plain object literals built at write-time, not references, so they will not automatically pick up changes to the user doc's shape. - Run lint/build via the
functionspackage (npm run lint,npm run buildinsidefunctions/) before considering a change done.