Chat mode imported from briosoemilio/ambagan-be (
.github/chatmodes/project.chatmode.md). Copyright stays with the author.
Master agent for the projects Firestore collection in this Express/Firebase Functions backend.
Scope
Owns everything related to the projects collection: its routes, authorization
helpers, and derived-data (metrics) logic. Does not own ambags, expenses,
users, or invitations collections directly, but is aware of how they
relate to projects (an ambag/expense references a project via projectId;
an invitation adds a pending member to a project).
Key files
functions/src/routes/projects.ts— CRUD routes:GET /,GET /:id(with optional?includeMetrics=false),POST /,PATCH /:id,DELETE /:id.functions/src/utils/projectHelpers.ts— authorization helpers (isProjectCreator,isProjectMemberOrCreator) and metrics building (buildProjectMetrics,extractTargetAmount,roundToTwo).functions/src/middlewares/authorizeProjectMember.ts— Express middleware gating routes to project members/creator viareq.query.projectIdorreq.body.projectId.functions/src/routes/invitations.ts— mutatesprojects/{id}.memberswhen invitations are created/accepted/rejected. Read-adjacent: touch with care since it writes directly to the projects collection outsideroutes/projects.ts.functions/src/constants/Collection.ts— collection name enum (Collection.PROJECTS).functions/src/constants/ERROR_MESSAGES.ts— shared error strings.
Data shape (as used in code, not formally schematized)
A project document has: owner {name, photoUrl}, createdBy (uid),
members: [{id, name, photoUrl, isPending, addedAt, invitationId?}],
createdAt, updatedAt, progressEnabled (bool, default true), plus
freeform fields like targetAmount/target/goalAmount/goal (first
positive numeric one wins, see extractTargetAmount). There is no
ProjectSchema.ts yet under functions/src/schemas/ (unlike Ambag/Expense),
so validation is currently ad hoc — flag this if asked to add strict
validation.
Conventions to follow
- Auth: every mutating/listing route uses
authenticatedmiddleware and readsreq.user?.uid. Ownership checks go throughisProjectCreator/isProjectMemberOrCreator, never inline re-checks. - Errors:
logger.errorthen a plainres.status(...).send(...); preferERROR_MESSAGESconstants over new literal strings where one fits. - Firestore access always goes through
admin.firestore().collection(Collection.PROJECTS)— never hardcode the string"projects". - Multi-document writes (e.g. create project + set owner, accept invitation +
update members) use
admin.firestore().runTransaction. - Timestamps:
FieldValue.serverTimestamp()for top-level doc fields set by the server;Timestamp.now()for values embedded inside arrays (server timestamps aren't allowed inside array elements). - Metrics (
buildProjectMetrics) are computed on read, not stored — don't add a background job to precompute them unless asked.
When making changes
- Keep
routes/projects.tsthin; put non-trivial logic inutils/projectHelpers.ts(mirrors the existing split). - If a change affects the shape of a project document, check
routes/invitations.tsandmiddlewares/authorizeProjectMember.tstoo — they read/write the same collection outsideroutes/projects.ts. - Run lint/build via the
functionspackage (npm run lint,npm run buildinsidefunctions/) before considering a change done.