Merchant Shell Write-Path Audit (Phase G) โ
For each write action in the merchant shell, the source endpoint and disposition.
Audit Table โ
| Tab | Action | Endpoint | Auth model today | Disposition |
|---|---|---|---|---|
| Overview | Save businessName | PATCH /auth/admin/users/merchant/:userId (via updateMerchantUser) | admin-only | New handler under /me/profile โ merchants should update their own business name |
| Overview | Save contactName, phone | PATCH /auth/admin/users/merchant/:userId (via updateMerchantUser) | admin-only | Same new /me/profile handler โ covers all three editable fields atomically |
| Notes | Save internal notes | PATCH /auth/admin/users/merchant/:userId (via updateMerchantUser) | admin-only | Read-only in v1 โ notes are explicitly admin-only ("internal notes โ only visible to admins"); hide Edit button when role !== 'admin' |
| Venues | Associate venue | POST /auth/admin/merchants/:merchantId/venues (via associateVenueWithMerchant) | admin-only | Mount existing associateVenueWithMerchant handler under /me/venues; make frontend role-aware |
| Venues | Remove (disassociate) venue | DELETE /auth/admin/merchants/:merchantId/venues/:venueId (via disassociateVenueFromMerchant) | admin-only | Mount 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 |
| Offers | Create offer | POST /merchants/:merchantId/offers (services/api/merchants port 8085) | requireMerchantAccess (admin OR own merchant) | No change โ already merchant-accessible |
| Offers | Update offer | PUT /merchants/:merchantId/offers/:offerId | same | No change |
| Offers | Delete/archive offer | DELETE /merchants/:merchantId/offers/:offerId | same | No change |
| Offers | Publish draft offer | PUT /merchants/:merchantId/offers/:offerId (status: active) | same | No 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 โ
| # | Gap | Action in Task 32 |
|---|---|---|
| 1 | Overview save (businessName/contactName/phone) | New updateMerchantSelf handler + /me/profile route + role-aware merchantApi.js |
| 2 | Notes tab Edit button | Read-only for merchants in v1 โ gate Edit UI behind isAdmin prop |
| 3 | Venues associate | Mount existing handler at POST /auth/merchant/me/venues; role-aware frontend |
| 4 | Venues disassociate | Mount 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/usesrequireMerchantAccesswhich explicitly allows merchants accessing their own merchantId. Already merchant-accessible. - Photos โ Placeholder, no writes.
- Address โ Placeholder, no writes.