Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions services/votes-api/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Vercel entrypoint for the votes API (Hono). The long-running Node server lives in
// src/index.ts (PM2/nginx path) and is untouched; this file only adapts the same app
// factory to Vercel's fetch handler. Config comes from env vars (DATABASE_URL,
// VOTES_HMAC_PEPPER, TURNSTILE_SECRET_KEY, ...) exactly as in src/index.ts. If they are not
// set, config.ts falls back to local-dev defaults and DB-backed routes return errors
// (/api/v1/health reports db: down) instead of crashing the function at import time.
import { handle } from 'hono/vercel';
import { createApp } from '../src/app.js';
import { loadConfig } from '../src/config.js';
import { connect } from '../src/db/client.js';
import { createLogger } from '../src/logger.js';
import { SlugRegistry } from '../src/slug/registry.js';
import { createTurnstileVerifier } from '../src/turnstile.js';
import { LiveTemplateManifest } from '../src/submissions/live-manifest.js';
import { createShareLinkVerifier } from '../src/submissions/share-link.js';

const cfg = loadConfig();
const logger = createLogger(cfg.logLevel);
// Serverless: keep the pool tiny; each instance handles few concurrent requests.
const db = connect(cfg.databaseUrl, 1);
Comment on lines +17 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right factory, and GET /api/v1/health does return 503 db: down without crashing import. Two holes vs src/index.ts + vs a public Vercel host:

  1. loadConfig() still fail-opens to VOTES_HMAC_PEPPER=local-dev-only-replace-me and TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA. I posted {"turnstileToken":"x"} to this handler; Turnstile did not 403 (test secret + Cloudflare always-pass) and the handler proceeded to insert into identities (then 500 on ECONNREFUSED). If Vercel Auth is off and those env vars are missing, this is a public signer.
  2. No export const config = { runtime: 'nodejs' }. This graph uses node:fs, postgres TCP, and node:crypto. Hono's Vercel guide sets Node explicitly so the function cannot land on Edge.

src/index.ts also await slugRegistry.load(true) before serve. Lazy has() is fine on serverless, but the first vote/counts call will throw no use-case slugs loaded unless content/dist or SLUGS_URL is actually in the bundle (see vercel.json).

const slugRegistry = new SlugRegistry({
contentDir: cfg.useCaseContentDir,
slugsFile: cfg.slugsFile,
slugsUrl: cfg.slugsUrl,
refreshMs: cfg.slugRefreshMs,
});

const app = createApp({
db,
slugRegistry,
turnstileVerifier: createTurnstileVerifier(cfg.turnstileSecret),
shareLinkVerifier: createShareLinkVerifier(cfg.shareLinkTimeoutMs),
liveManifest: new LiveTemplateManifest({
file: cfg.submissionsManifestFile,
url: cfg.submissionsManifestUrl,
ttlMs: cfg.submissionsManifestTtlMs,
}),
pepper: cfg.pepper,
logger,
});

export const GET = handle(app);
export const POST = handle(app);
export const PUT = handle(app);
export const PATCH = handle(app);
export const DELETE = handle(app);
export const OPTIONS = handle(app);
export const HEAD = handle(app);
1 change: 1 addition & 0 deletions services/votes-api/public/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
votes-api
8 changes: 8 additions & 0 deletions services/votes-api/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"framework": null,
"installCommand": "npm install",
"buildCommand": "",
"outputDirectory": "public",
"rewrites": [{ "source": "/(.*)", "destination": "/api" }]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locally, handle(app) 404s when the request URL is /api or /v1/health and only matches the real routes when the URL is still /api/v1/…. This rewrite sends every path to /api with no $1 capture. If Vercel gives the function the destination URL (not the original path), the Ready deploy is a 404 farm.

This file also never packs the slug sources the registry actually reads. Defaults are ../../content/use-cases and ../../dist/api-meta/use-case-slugs.json; slugsUrl is unset unless SLUGS_URL is in the Vercel project. NFT will not include those trees (they are readdir'd, not imported). The dashboard “include source files outside root” note is not reproducible from git. Add functions["api/index.ts"].includeFiles (or set SLUGS_URL / SLUGS_FILE in committed docs + project env) and use a rewrite that preserves the path, e.g. destination: "/api/$1" or keep this rewrite and prove with an unauthenticated GET /api/v1/health that the function still sees /api/v1/health.

}
Loading