diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml new file mode 100644 index 0000000..782af35 --- /dev/null +++ b/.github/workflows/deno.yml @@ -0,0 +1,42 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow will install Deno then run `deno lint` and `deno test`. +# For more information see: https://github.com/denoland/setup-deno + +name: Deno + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Setup repo + uses: actions/checkout@v4 + + - name: Setup Deno + # uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2 + with: + deno-version: v1.x + + # Uncomment this step to verify the use of 'deno fmt' on each commit. + # - name: Verify formatting + # run: deno fmt --check + + - name: Run linter + run: deno lint + + - name: Run tests + run: deno test -A diff --git a/services/votes-api/api/index.ts b/services/votes-api/api/index.ts new file mode 100644 index 0000000..be33eee --- /dev/null +++ b/services/votes-api/api/index.ts @@ -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); +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); diff --git a/services/votes-api/public/.gitkeep b/services/votes-api/public/.gitkeep new file mode 100644 index 0000000..2ed9523 --- /dev/null +++ b/services/votes-api/public/.gitkeep @@ -0,0 +1 @@ +votes-api diff --git a/services/votes-api/vercel.json b/services/votes-api/vercel.json new file mode 100644 index 0000000..a56a70b --- /dev/null +++ b/services/votes-api/vercel.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": null, + "installCommand": "npm install", + "buildCommand": "", + "outputDirectory": "public", + "rewrites": [{ "source": "/(.*)", "destination": "/api" }] +}