Skip to content

Merchant Shell Write-Path Audit (Phase G) โ€‹

For each write action in the merchant shell, the source endpoint and disposition.

Audit Table โ€‹

TabActionEndpointAuth model todayDisposition
OverviewSave businessNamePATCH /auth/admin/users/merchant/:userId (via updateMerchantUser)admin-onlyNew handler under /me/profile โ€” merchants should update their own business name
OverviewSave contactName, phonePATCH /auth/admin/users/merchant/:userId (via updateMerchantUser)admin-onlySame new /me/profile handler โ€” covers all three editable fields atomically
NotesSave internal notesPATCH /auth/admin/users/merchant/:userId (via updateMerchantUser)admin-onlyRead-only in v1 โ€” notes are explicitly admin-only ("internal notes โ€” only visible to admins"); hide Edit button when role !== 'admin'
VenuesAssociate venuePOST /auth/admin/merchants/:merchantId/venues (via associateVenueWithMerchant)admin-onlyMount existing associateVenueWithMerchant handler under /me/venues; make frontend role-aware
VenuesRemove (disassociate) venueDELETE /auth/admin/merchants/:merchantId/venues/:venueId (via disassociateVenueFromMerchant)admin-onlyMount existing disassociateVenueFromMerchant handler under /me/venues/:venueId; make frontend role-aware
Photos(none)โ€”โ€”No change โ€” Photos tab is a placeholder with no writes
Address(none)โ€”โ€”No change โ€” Address tab is a placeholder with no writes
OffersCreate offerPOST /merchants/:merchantId/offers (services/api/merchants port 8085)requireMerchantAccess (admin OR own merchant)No change โ€” already merchant-accessible
OffersUpdate offerPUT /merchants/:merchantId/offers/:offerIdsameNo change
OffersDelete/archive offerDELETE /merchants/:merchantId/offers/:offerIdsameNo change
OffersPublish draft offerPUT /merchants/:merchantId/offers/:offerId (status: active)sameNo change

Gap Analysis โ€‹

Gap 1: Overview save โ€” PATCH /auth/admin/users/merchant/:userId โ€‹

The existing admin endpoint is user-UID-keyed (/users/merchant/:userId). The merchant-self path resolves by merchantId from the token claim, not userId directly โ€” the server can look up the primary owner uid from users.doc(uid) since the authenticated user IS the merchant.

Decision: Add a new updateMerchantSelf handler in merchantHandlers.js that uses req.user.uid (the authenticated merchant's uid) instead of req.params.userId. Mount it at PATCH /auth/merchant/me/profile. Update merchantApi.js to be role-aware. Update firebase.js to delegate updateMerchantUser calls for the self-update case.

Fields covered: businessName, contactName, phone. Notes is excluded (admin-only, see below).

Gap 2: Notes tab โ€” admin-only in v1 โ€‹

The Notes tab JSDoc says "internal notes about this merchant (only visible to admins)". Editable notes about a business written by admin staff should not be writable by the merchant themselves โ€” this is an admin moderation/onboarding tool.

Decision: Gate the Edit button in Notes.jsx behind isAdmin. The MerchantShell already receives isAdmin as a prop and passes it to <Routes>. Thread isAdmin through to the Notes tab via React Router's outlet context or direct prop.

Gap 3: Venues associate/disassociate โ€” mount on /me โ€‹

Both handlers in merchantHandlers.js already work by req.params.merchantId, which the /me middleware injects from the token claim. No handler changes needed โ€” just two new route mounts.

Decision: Mount associateVenueWithMerchant at POST /auth/merchant/me/venues and disassociateVenueFromMerchant at DELETE /auth/merchant/me/venues/:venueId in merchantSelf.js. Make frontend (merchantApi.js) role-aware for both. Update firebase.js wrappers and AdminVenuePicker to use the role-aware client.

Decisions Summary โ€‹

#GapAction in Task 32
1Overview save (businessName/contactName/phone)New updateMerchantSelf handler + /me/profile route + role-aware merchantApi.js
2Notes tab Edit buttonRead-only for merchants in v1 โ€” gate Edit UI behind isAdmin prop
3Venues associateMount existing handler at POST /auth/merchant/me/venues; role-aware frontend
4Venues disassociateMount existing handler at DELETE /auth/merchant/me/venues/:venueId; role-aware frontend

Gaps 3 and 4 share a single commit (same route file, same frontend change). Gap 2 (Notes read-only) is a single commit gating the Edit UI. Gap 1 (Overview save) is one commit: handler + route mount + frontend.

Confirmed No-Change โ€‹

  • Offers CRUD โ€” services/api/merchants/ uses requireMerchantAccess which explicitly allows merchants accessing their own merchantId. Already merchant-accessible.
  • Photos โ€” Placeholder, no writes.
  • Address โ€” Placeholder, no writes.

Built with VitePress