Repository navigation
fix(api): consolidate admin endpoints below the Vercel Hobby function cap (Task 8.6) - #12
Merged
Merged
Conversation
… cap (Task 8.6) Restores deployability and makes the serverless layer actually run. Layout (the cap): - api/lib/ -> api/_lib/ and api/admin/*.ts -> api/_lib/admin/*.ts (paths containing a `_`-prefixed segment are excluded from Vercel's function count); the 11 handlers moved verbatim. - New api/admin/[action].ts dispatches /api/admin/<action> through a plain Record<string, handler> table. Public URLs and src/admin/services/adminApi.ts are unchanged. - api/ now counts 6 functions against the Hobby ceiling of 12 (was 22). Runtime blocker A - extensionless ESM imports: - Vercel transpiles api/ in place rather than bundling, so Node's ESM resolver runs at request time. With `"type": "module"` in package.json, every extensionless relative import threw ERR_MODULE_NOT_FOUND and every endpoint returned FUNCTION_INVOCATION_FAILED despite a green build. - Appended .js to relative value imports across api/ plus src/utils/schemaValidation.ts's ./rut. Type-only imports targeting a directory stay extensionless (they are erased at transpile). - Proven pre-existing by building main (e4206bc) in a throwaway worktree: its emitted create-preference.js carried identical extensionless specifiers. Runtime blocker B - jwks-rsa CJS requiring ESM-only jose: - firebase-admin -> jwks-rsa@4 is CommonJS and calls require('jose') at module load, but jose@6 is ESM-only, so merely importing firebase-admin/auth crashed with ERR_REQUIRE_ESM and took down every admin route. Upstream: auth0/node-jwks-rsa#507 and firebase/firebase-admin-node#3181; the fix (PR #508) is merged but unreleased (jwks-rsa latest is still 4.1.0). - Worked around with pnpm.overrides pinning jose to ^5.10.0 - the last dual CJS/ESM major; jwks-rsa only uses importJWK/exportSPKI, which are API-identical across jose 4/5/6. Remove once jwks-rsa > 4.1.0 ships. Verification: - New src/tests/api/admin/admin-router.test.ts (11 tests): happy path, unknown/missing/empty/non-string action -> 404, array normalization, whitespace trim, OPTIONS passthrough, all 11 actions mapped, and rejection of inherited prototype keys. - 429 tests across 59 suites pass; pnpm build, pnpm lint and pnpm format:check are clean. - Live preview verified: 6 functions, all 11 admin actions route (GET -> 403 auth, POST-only -> 405, unknown -> 404, OPTIONS -> 200), zero runtime errors in the function logs. Docs: - api/AGENTS.md new 1.2 (function layout + router) and 1.3 (ESM resolution invariants, jose override removal condition). - root AGENTS.md 2.2 (routed-entry-point exception) and 7 ("a green build does not mean the functions run"). - .vercel/** added to eslint ignores - a local `vercel build` otherwise surfaced 2800 artifact errors; test counts refreshed to 429/59. - PRODUCTION_READINESS_TODO 8.6 resolved, recording both runtime blockers. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…uilt state
7.3 asked for `favicon.ico` / `apple-touch-icon.png` and an OG image
"featuring company logo and Melipilla delivery badge". All three are
superseded by the as-built repo:
- `public/` already exists (it holds `favicon.svg` and `assets/`).
- `favicon.svg` is Appendix B.2's turnkey SVG, already committed and wired
via `<link rel="icon" type="image/svg+xml" href="/favicon.svg" />`; no
.ico and no apple-touch-icon are used by the markup.
- There is no delivery badge: Appendix B.1's approved composition carries a
"Deposito dental - Melipilla y San Antonio" text line instead.
The entry now points at UI_UX_EVALUATION_AND_REDESIGN_PROPOSAL.md
Appendix B as the asset spec of record (as root AGENTS.md 7 and
src/components/AGENTS.md 2.2 already do), records the favicon as delivered,
and lists the three assets still owed - with `public/og-preview.png`
flagged as the hard `vercel --prod` gate.
The audit-table row at line 38 is deliberately left untouched: it is a
historical snapshot and remains accurate ("meta tags implemented; missing
actual image file").
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 Context & Motivation
Reference:
PRODUCTION_READINESS_TODO.md→ 8.6. Consolidateapi/Endpoints Below the Vercel Hobby Function Cap 🔴 blocks every deployment.Every deployment — preview and production — was refused at the output stage after a successful build:
The TODO counted 15 endpoints; the real count was 22, because Vercel counts every file under
api/unless its path contains a_-prefixed segment. Collapsing the admin endpoints and relocating shared code behindapi/_lib/takesapi/to 6 functions against the ceiling of 12.Lifting the cap then exposed two further pre-existing runtime blockers that had never been reachable (no deploy had succeeded since 2026-09-17, and production still serves a much older build). Both are fixed here, because a deployment whose functions all return HTTP 500 does not count as "unblocked".
🛠️ Summary of Changes
API / Services — the cap (8.6 core)
api/lib/→api/_lib/(6 shared modules) andapi/admin/*.ts→api/_lib/admin/*.ts(11 handlers, moved verbatim —git diff -Mconfirmed import-lines-only, 54 insertions / 54 deletions).api/admin/[action].ts— a single routed entry point. A plainRecord<string, handler>table maps the 11 action names; unknown/missing/empty/non-string actions return404 { success: false, error: 'Endpoint de administración no encontrado' }and log an[Admin Router]warning. Inherited prototype keys (constructor,__proto__, …) are rejected by an own-property check.^/api/admin/([^/]+)$ → /api/admin/[action]?action=$1, sosrc/admin/services/adminApi.tsandvercel.jsonneeded zero changes.OPTIONSpreflight, method gate,verifyAdminTokencall, and error handling.Runtime blocker A — extensionless ESM imports (pre-existing)
Vercel does not bundle
api/; it transpiles each file in place and ships the tree, so Node's ESM resolver runs at request time. With"type": "module"inpackage.json, every extensionless relative import ('./_lib/firebaseAdmin') threwERR_MODULE_NOT_FOUND— everyapi/function returnedFUNCTION_INVOCATION_FAILED, including the untouched public endpoints..jsto relative value imports acrossapi/plussrc/utils/schemaValidation.ts's./rut.'../../../src/types') were deliberately left extensionless: they are erased at transpile time, and appending.jswould break TypeScript's directory resolution.main(e4206bc) in a throwaway worktree produced acreate-preference.jswith identical extensionless specifiers.Runtime blocker B —
jwks-rsaCJS requiring ESM-onlyjose(pre-existing)firebase-admin→jwks-rsa@4is CommonJS and callsrequire('jose')at module load, whilejose@6is ESM-only — so merely importingfirebase-admin/authcrashed withERR_REQUIRE_ESM, taking down every admin route.jwks-rsalatest is still4.1.0).pnpm.overridespinningjoseto^5.10.0— the last dual CJS/ESM major.jwks-rsaonly usesjose.importJWK/exportSPKI, which the upstream issue confirms are API-identical across jose 4/5/6.SECURITY.md. The CVE that motivated the v6 bump (CVE-2025-45767) is disputed by the maintainer and specific to v6.0.10.api/AGENTS.md§1.3): drop the override oncejwks-rsa>4.1.0ships, then re-verifyfirebase-admin/authon a preview deploy.Tests
src/tests/api/admin/admin-router.test.ts(11 tests): happy path, unknown/missing/empty/non-string action → 404, array normalization, whitespace trim,OPTIONSpassthrough, all 11 actions mapped, and rejection of inherited prototype keys.Docs / tooling
api/AGENTS.md— new §1.2 (function layout, router,_libexclusion) and §1.3 (ESM resolution invariants + thejoseoverride removal condition).AGENTS.md— §2.2 records the routed-entry-point exception; §7 gains "a green build does NOT mean the functions run"..vercel/**added toeslint.config.jsignores — a localvercel buildotherwise surfaced 2800 artifact errors (my code contributed zero; verified by parsing the JSON report). Consistent with the existingdist/**/coverage/**/public/**entries.PRODUCTION_READINESS_TODO.md— 8.6 marked resolved, both runtime blockers recorded, acceptance criterion 1 checked.🇨🇱 Chilean Localization Compliance
Math.round()19% IVA math, and Modulo 11 RUT validation are untouched.dev_*/production stock inside a Firestore transaction,America/Santiagotimezone bucketing indashboard-stats, and Boleta/Factura field handling in the admin inspector.'Endpoint de administración no encontrado','Método no permitido').🧪 Verification & Quality Assurance
Automated:
pnpm testpnpm buildpnpm lintpnpm format:checkvercel buildBuild Completed— 6 functions, no new TS diagnostics (same 8 pre-existingTS7006/TS2503)Live preview deploy (end-to-end):
✓ Ready in 23s— verified withvercel curl(which bypasses preview SSO):GET /api/admin/{dashboard-stats,orders,order-history,products}verifyAdminTokenrejects absent BearerGET /api/admin/{approve-transfer,dispatch-order,mark-delivered,update-stock,update-product,create-product,toggle-visibility}GET /api/admin/nonexistentOPTIONS /api/admin/ordersaccess-control-allow-methods: GET, OPTIONS)GET /api/{create-preference,order-confirmation,track-order,upload-voucher}GET /api/webhooks/mercadopago{status:"ok"})Function logs recorded zero errors across all requests. The 403/OPTIONS responses carry the delegated handlers' CORS headers, proving the handler modules executed rather than the dispatcher short-circuiting.
Not proven (stated honestly): an authenticated end-to-end operation (e.g. approving a transfer and observing stock decrement). That requires a real Firebase admin ID token, which was not handled. Note that preview deployments resolve to
dev_*collections viagetCollectionName(), so that manual step is safe to perform without touching production.🛡️ Strict Guardrails Verification
AGENTS.md§2.2 andapi/AGENTS.md§1.2, forced by the Hobby cap.firestore.rulesuntouched,verifyAdminToken+admincustom-claim gate unchanged.pnpm.overrideswas added only after explicit human sign-off (it is a dependency pin, not a security-policy relaxation — no.npmrcsettings were modified).vercel --prodremains gated by the human-producedpublic/og-preview.pngasset (rootAGENTS.md§7). Production has not been touched.