Volunteer & Project Matching Platform for PauseAI
Catalyse connects volunteers with projects, matching skills to needs and enabling effective coordination across PauseAI initiatives.
- Browse Projects - Filter by skills, status, urgency
- Skill Matching - See how well your skills match each project
- Express Interest - Apply to contribute or lead projects
- Personal Dashboard - Track your projects and interests
- Privacy Controls - Choose what info to share and how
- Post Projects - Describe needs, required skills, time commitment
- Find Volunteers - See interested volunteers and their skills
- Team Communication - Contact volunteers through the platform
- Project Triage - Review and approve volunteer proposals
- Create Org Projects - Post official PauseAI initiatives
- Platform Stats - Monitor volunteer and project activity
- Frontend: Next.js (App Router), React, TypeScript, Tailwind CSS
- Backend: Next.js API routes
- Database: PostgreSQL via Prisma ORM
- Email: Resend SDK
- Auth: Custom token-based + Google OAuth
- Hosting: Railway
- Node.js 22+
- npm
- A PostgreSQL 18 server.
docker compose up -dstarts one matching CI and production; a native install (Homebrew, apt, Postgres.app) works too — setDATABASE_URLaccordingly.pg_dump/pg_restoreare needed forfetch-prod-dband the backup job;local-setupinstalls them if missing (npm run install:pg-tools: Homebrew on macOS, apt/dnf/pacman/apk on Linux).
npm run local-setupThis installs dependencies, Playwright browsers and the Postgres client tools, restores a copy of production into your database (anonymised), and runs migrations. Postgres must be running first.
Copy .env.local.example to .env.local and fill in the values:
cp .env.local.example .env.localKey variables:
DATABASE_URL— Postgres connection URL (e.g.postgresql://postgres:postgres@localhost:5432/catalyse)RESEND_API_KEY— for email sendingGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET— for Google OAuthSTUB_EMAIL=true— suppress real emails in development
npm run devThe app will be available at http://localhost:3000.
To make yourself an admin, add your email to .env.local:
ADMIN_EMAILS=your@email.comOn next login, the app will automatically grant admin access. Multiple emails can be comma-separated.
The local dev database is a copy of prod, restored into whatever DATABASE_URL points at. Refresh it with:
npm run fetch-prod-db && npm run migratefetch-prod-db downloads the latest prod pg_dump from B2, drops and recreates the public schema of the target database, and restores into it. It then runs the same scrub as anonymise-db (PII replaced, dev accounts seeded) and empties the database again if that fails, so raw prod data is never left behind. Every account gets the same known password, so an anonymised copy must not sit behind a public URL. anonymise-db alone re-scrubs a database that is already restored. scripts/anonymise-columns.ts records how each text column is treated, and a test fails when a new column is missing from it. Both scripts refuse to run when RAILWAY_ENVIRONMENT_NAME=production, and fetch-prod-db refuses in any Railway environment unless ALLOW_DB_RESTORE=1 is set (set it only on the preview base environment). migrate runs prisma migrate deploy, which applies any unapplied migration files in order without drift-checking.
Each unit test file gets its own copy of the schema (vitest_*), made either by cloning a template database migrated once per run (TEST_DB_MODE=clone, needs a role with CREATEDB) or by replaying the migrations into a schema (TEST_DB_MODE=schema). Cloning is faster only on the db-test server, so the default is clone when TEST_DATABASE_URL is set and schema otherwise. E2E workers use the schemas e2e_<n>. Both go to TEST_DATABASE_URL when set, else DATABASE_URL; neither touches public of your dev data. docker compose up -d also starts db-test on port 5433, an in-memory Postgres with durability off that is only safe because it holds nothing but test data. Set TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5433/catalyse_test in .env.local to use it.
Both suites default to half the cores. Tune them with VITEST_MAX_WORKERS and WORKER_COUNT in .env.local, and find a good count for a machine with npx tsx scripts/tune-workers.ts unit 3 4 5 6.
Do not use prisma migrate dev — it checks for schema drift against the live DB and will fail. The correct workflow:
- Edit
prisma/schema.prisma - Generate the migration file:
This creates
npm run new-migration your_migration_name
prisma/migrations/YYYYMMDDHHMMSS_your_migration_name/migration.sqlwith the diff SQL. - Review the generated SQL — the diff may include unrelated pending changes from other branches. Remove any statements not relevant to your change.
- Apply it:
npm run migrate
- Regenerate the Prisma client:
npx prisma generate
Super admins can take the site down from Admin → Platform Settings → Maintenance mode. Everyone else then sees a "down for maintenance" page and every API call except signing in or out is refused with a 503. Super admins (emails in ADMIN_EMAILS) can still log in at /login and use the whole site as normal, with a banner at the top of every page reminding them it is on; switch the toggle off there to bring the site back.
npm run test:unit # Run unit tests only (vitest)
npm run test:e2e # Run e2e tests only (Playwright)
npm run test:e2e:headed # Run with a visible browser (single worker, slowed)
npm run test:e2e:ui # Open Playwright UI modeTests spin up an isolated Next.js server with a fresh database — your dev server doesn't need to be running.
The test:e2e:dev variants skip the build and use a dev server instead. These are for interactive development only — do not use them to verify correctness, as they skip type checking and build validation.
The repo is public, so pull requests from forks run .github/workflows/ci.yml with untrusted code. GitHub gives such runs a read-only token and no repository secrets; the workflow is written so it never needs either, and two checks keep it that way:
- zizmor runs in the
static-checksjob and fails on dangerous triggers, template injection, unpinned actions, broad permissions and leaked credentials. Run it locally withuvx zizmor .github/workflows/(needs uv). test/ci-workflow.test.ts(part ofnpm run test:unit) checks the project-specific rules zizmor can't know: onlypush/pull_requesttriggers, nosecrets.*anywhere, read-only permissions, GitHub-hosted runners, every*_URLpointing atlocalhost, and every credential-shaped variable holding a literal dummy.
Actions are pinned to commit SHAs with the version in a trailing comment; bump both together.
| Script | Description |
|---|---|
local-setup |
One-time local setup: install deps, browsers, Postgres client tools, fetch prod DB (anonymised), run migrations |
issue <number> |
Launch a sandboxed Claude session to work on a GitHub issue (creates branch, fetches issue, restricts CLI access). Usage: npm run issue -- 84 |
check-all |
Run typecheck, lint, format check, and tests — use before committing |
dev |
Start local dev server with Turbopack |
build |
Generate Prisma client and run Next.js production build |
start |
Start production server (requires prior build) |
typecheck |
Run TypeScript type checking without emitting files |
lint |
Run ESLint |
lint:fix |
Run ESLint with auto-fix |
format |
Format all files with Prettier |
format:check |
Check formatting without writing |
generate |
Regenerate Prisma client and run post-generation script |
build:railway |
Production build entrypoint used by Railway CI |
new-migration |
Create a new migration SQL file from schema diff |
migrate |
Apply pending migration files to the local database |
fetch-prod-db |
Restore latest prod backup into DATABASE_URL, anonymised, with dev accounts seeded |
anonymise-db |
Anonymise PII in DATABASE_URL and seed dev accounts |
install:browsers |
Install Playwright's Chromium browser |
test:unit |
Run unit tests with vitest |
test:unit:watch |
Run vitest in watch mode |
test:e2e |
Run all e2e tests (builds first, then spins up isolated servers) |
test:e2e:dev |
Run e2e tests against a dev server — skips build, for interactive development only |
test:e2e:log |
Run e2e tests and save full output to test-output.txt |
test:e2e:headed |
Run e2e tests with a visible browser, single worker |
test:e2e:ui |
Open Playwright UI mode for interactive test debugging |
cron:backup |
Run the database backup cron job |
demo |
Run the demo data seeding script |
demo:snapshot |
Take a snapshot of the current demo state |
demo:compare |
Compare current demo state against snapshot |
catalyse/
├── app/ # App Router pages and API routes
├── components/ # Shared React components
├── lib/ # Auth, email, Prisma, utilities
├── prisma/ # Prisma schema and migrations
├── public/ # Static assets
├── scripts/ # Build and utility scripts
└── e2e/ # Playwright end-to-end tests
AGENTS.md holds the working notes for coding agents and contributors, and
SELF-IMPROVE.md is the backlog of things about the repo itself that made work
harder than it needed to be, sorted by how often each has bitten.
MIT - Built for PauseAI