Skip to content

[ARCHIVE] feat: introduce BetterGov.ph Project Registry with schema validation, GET API, and test suite#637

Closed
zelkim wants to merge 7 commits into
bettergovph:mainfrom
zelkim:archive/projects-api
Closed

[ARCHIVE] feat: introduce BetterGov.ph Project Registry with schema validation, GET API, and test suite#637
zelkim wants to merge 7 commits into
bettergovph:mainfrom
zelkim:archive/projects-api

Conversation

@zelkim
Copy link
Copy Markdown
Contributor

@zelkim zelkim commented May 9, 2026

Important

This is an archive of a more extensive projects API, that has, for now, been simplified to a static projects.json. This will be revisited in the future, see referenced discussion below:

We'll come back to the proper /api routes/workers as there's some big changes I'm planning for BetterGov. I hope for your participation when that time comes!

Originally posted by @jasontorres in #635 (comment)

Summary

This PR introduces a centralized, JSON-based Project Registry for all officially recognized BetterGov.ph projects, along with a public GET API to serve the data, pre-commit schema validation to guard data integrity, and a full test suite covering unit, integration, and live scenarios.


Motivation

As BetterGov.ph grows into an ecosystem of projects — not all of which live within the bettergovph GitHub organization — there was no single source of truth for which projects are officially recognized. This registry solves that by:

  • Giving maintainers a PR-based recognition workflow (submit a PR → get reviewed → get listed)
  • Providing a stable data source for contributor tracking and Member Card tier progression in the volunteer portal
  • Enabling all BetterGov.ph platforms to consume the same project list via a lightweight API (e.g. "Our Projects" dropdowns, ecosystem dashboards)

Changes

1. Project Registry — src/data/projects.json

A new JSON file seeded with all 16 currently recognized BetterGov.ph projects (sourced from the existing ourProjects list in navigation.ts, enriched with descriptions and repository URLs fetched from the bettergovph GitHub org).

Each entry contains:

Field Description
slug Unique kebab-case identifier
title Display name
description Short description of the project
repositoryUrls Array of GitHub URLs (supports multi-repo projects; empty if private)
projectUrl Live URL of the project
status active | development | archived

2. JSON Schema — src/data/schema/projects.schema.json

A JSON Schema Draft-07 file that enforces the registry structure:

  • All six fields are required with additionalProperties: false
  • slug validated as kebab-case via regex (^[a-z0-9]+(?:-[a-z0-9]+)*$)
  • projectUrl and each repositoryUrl validated as HTTPS URLs
  • status validated as an enum

3. Pre-commit Validation — .lintstagedrc.js

Schema validation is wired into the existing husky → lint-staged pre-commit pipeline. Whenever src/data/projects.json is staged, the existing validate-json-schema.js script runs automatically and blocks the commit if any entry violates the schema.

git commit  →  husky pre-commit  →  lint-staged  →  validate-json-schema.js

This also fixes a pre-existing bug in validate-json-schema.js where Ajv v8's strict mode caused schemas using format keywords (including the existing services.schema.json and population-2020.schema.json) to fail to compile. Fixed by passing strict: false to the Ajv instance.

4. GET API — functions/api/projects.ts + functions/index.ts

A new Cloudflare Worker endpoint: GET /api/projects

The data is bundled statically at build time (JSON import) — no KV or D1 bindings required. Filtering and pagination are done in-memory, which is appropriate for a registry of this scale.

Query parameters:

Param Type Default Description
search string Case-insensitive substring match on title and description
status string Filter by active, development, or archived
page number 1 1-based page number
limit number 20 Items per page (max 100)

Response shape:

{
  "data": [ ...projects ],
  "meta": {
    "total": 16,
    "page": 1,
    "limit": 20,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}

CORS headers (Access-Control-Allow-Origin: *) and OPTIONS preflight are handled by index.ts consistently with all other endpoints. The new route is also listed in /api/status.

5. TypeScript — functions/types.ts

Added a Project interface mirroring the schema shape, used to type the bundled import and the API response.

6. Test Suite

Three layers of tests, all runnable without external services:

Unit tests — functions/api/projects.test.ts (23 tests)

Call onRequest directly with constructed Request objects. Cover:

  • Response shape (status code, content-type, data fields, meta fields)
  • Status filter (all three valid values + invalid → 400)
  • Search (title match, description match, no results, combined with status)
  • Pagination (defaults, custom page/limit, limit cap at 100, hasNextPage/hasPrevPage, out-of-range clamping, NaN inputs)

Run: npm run test:functions

In-process integration tests — functions/integration/projects.integration.test.ts (9 tests)

Call the full index.ts fetch handler directly (no server). Cover routing, CORS header injection, OPTIONS preflight, and end-to-end query param handling through the complete worker pipeline.

Run: npm run test:functions

Live integration tests — functions/integration/projects.live.test.ts (21 tests)

Start a real Wrangler dev server via unstable_dev and make actual HTTP fetch() calls against it. Cover every scenario above plus wire-level CORS header verification and the full network stack.

Run: npm run test:functions:live

7. Dev Server Fix — wrangler.test.jsonc + functions:dev script

npm run functions:dev previously failed if ./dist did not exist (the production wrangler config requires a built assets directory). A new wrangler.test.jsonc provides a functions-only config (with KV bindings preserved for weather/forex) so functions can be developed and tested without a prior frontend build.

# Before: required npm run build first
npm run functions:dev  ✗

# After: works immediately
npm run functions:dev  ✓

New npm scripts

Script Description
npm run test:functions Run unit + in-process integration tests (fast, no server)
npm run test:functions:watch Watch mode for the above
npm run test:functions:live Run live tests against a real Wrangler dev server

Files changed

File Change
src/data/projects.json New — project registry (16 entries)
src/data/schema/projects.schema.json New — JSON Schema Draft-07
functions/api/projects.ts New — GET /api/projects endpoint
functions/api/projects.test.ts New — 23 unit tests
functions/integration/projects.integration.test.ts New — 9 in-process integration tests
functions/integration/projects.live.test.ts New — 21 live integration tests
functions/vitest.config.ts New — vitest config for unit/integration tests
functions/vitest.live.config.ts New — vitest config for live tests (extended timeouts)
wrangler.test.jsonc New — functions-only wrangler config
functions/types.ts Modified — added Project interface
functions/index.ts Modified — registered /api/projects route and updated status/404 docs
.lintstagedrc.js Modified — added schema validation on projects.json commit
scripts/validate-json-schema.js Bug fixstrict: false on Ajv to support format keywords in existing schemas
package.json Modified — added vitest dev dependency and new test scripts

@DaijobuDes DaijobuDes added enhancement New feature or request low priority This project can take it easy, no to long deadline. pending This is currently being worked on (on-going). labels May 10, 2026
@KishonShrill
Copy link
Copy Markdown
Member

Feel free to Reopen this PR when this topic comes back in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request low priority This project can take it easy, no to long deadline. pending This is currently being worked on (on-going).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants