diff --git a/README.md b/README.md index febba84..c399b00 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ backend are separate code boundaries, not separate deployments. |---|---| | Runtime | Node 24 LTS (Bun for package management and scripts) | | Framework | Next.js 16 App Router — pages + route handlers in one app | -| UI | React 19, DevNepal design system (Primer CSS vendored + tokens), react-markdown | +| UI | React 19, Tailwind CSS 4, shadcn/ui on Base UI, SWR, react-markdown | | Database | PostgreSQL 17 | | ORM | Drizzle ORM + drizzle-kit (plain-SQL migrations) | | Auth | Auth.js v5 (`next-auth@5` beta) — GitHub OAuth, JWT sessions, no adapter | @@ -26,7 +26,7 @@ backend are separate code boundaries, not separate deployments. ``` apps/api/ - src/app/(site)/[locale]/ UI pages (en/ne): home, project, issues, members, profile, admin, about + src/app/(site)/[locale]/ UI pages (en/ne): home, project, issues, members, profile, welcome, admin, about src/app/v1/ versioned REST route handlers src/app/ temporary compatibility aliases to `/v1` src/components/ site chrome and UI primitives @@ -34,14 +34,14 @@ apps/api/ src/server/ services, repositories, authorization seam, storage, errors src/db/ Drizzle schema + client drizzle/ generated SQL migrations (committed) - src/scripts/ seed, GitHub sync, dev-session tools + src/scripts/ project init, GitHub sync, dev-session tools tests/ unit + integration tests, test DB bootstrap packages/api-contract/ canonical OpenAPI description packages/api-client/ generated API types + browser HTTP client packages/shared/src/ runtime Zod validation + internal service DTOs docs/frontend.md frontend onboarding and verification matrix scripts/setup.ts one-command local bootstrap -compose.yaml PostgreSQL for development (and a full api profile) +compose.yaml PostgreSQL for development, plus api and migrate services .github/workflows/ci.yml Lint, typecheck, tests, build on every PR ``` @@ -118,30 +118,12 @@ bun run setup 5. verifies `SDOC-Team/devnepal` through the GitHub API, initializes the project row, and syncs its real issues -Nothing else is required — the design assets, translations, fonts, and vendored -styles all ship with the repository. +Translations and design assets ship with the repository. -### 2. Run +### 2. Create a GitHub OAuth App -```sh -bun run dev # UI + API on one port → http://localhost:3000/en -``` - -### 3. Verify the scaffold - -| Check | Expected | -|---|---| -| `curl localhost:3000/health` | `{"status":"ok"}` | -| http://localhost:3000/en | home page with the state strip, hero, and project card | -| http://localhost:3000/ne | the same page in Nepali | -| http://localhost:3000/en/issues | 8 seeded issues; label filter and search work | -| http://localhost:3000/en/members | 3 approved members; pending/rejected/hidden are absent | -| `curl 'localhost:3000/v1/project/issues?perPage=2'` | JSON with `"total": 8` | -| `bun run test` | the full Vitest suite passes against `refined_test` | - -### 4. Optional: sign in with GitHub - -Sign-in is required only for the profile editor and admin screens. +The app checks its configuration at startup and refuses to start without +GitHub OAuth credentials, so this step is required even for the public pages. 1. Create an OAuth App at → **New OAuth App** - Homepage URL: `http://localhost:3000` @@ -151,24 +133,42 @@ Sign-in is required only for the profile editor and admin screens. - `AUTH_GITHUB_SECRET` — generate a client secret and paste it 3. To get the admin queue, add your numeric GitHub ID to `ADMIN_GITHUB_IDS` (`https://api.github.com/users/` → `id`); comma-separate several admins. -4. Restart the dev server. Never commit these values; share team development credentials out-of-band. The provider requests **`read:user` only** — email is never requested or stored. -### 5. Optional: test signed-in screens without GitHub +### 3. Run + +```sh +bun run dev # UI + API on one port → http://localhost:3000/en +``` + +### 4. Verify the scaffold + +| Check | Expected | +|---|---| +| `curl localhost:3000/health` | `{"status":"ok"}` | +| http://localhost:3000/en | home page with the state strip, hero, and project card | +| http://localhost:3000/ne | the same page in Nepali | +| http://localhost:3000/en/issues | the repository's open issues; label filter and search work | +| http://localhost:3000/en/members | approved members only (empty until someone signs in and is approved) | +| `curl 'localhost:3000/v1/project/issues?perPage=2'` | JSON whose `total` matches the repository's open issues | +| `bun run test` | the full Vitest suite passes against `refined_test` | + +### 5. Optional: test signed-in screens without repeating OAuth -`bun run dev:session ` mints a real session cookie for a seeded -member so the profile editor and admin screens can be tested offline: +`bun run dev:session ` mints a real session cookie for an +existing member (sign in once with GitHub to create one), so the profile editor +and admin screens can be tested without repeating the OAuth flow: ```sh -bun run dev:session nisha-tamang +bun run dev:session ``` The command prints the `authjs.session-token` value and the member's GitHub ID. Add the cookie in DevTools → Application → Cookies → `http://localhost:3000`, and put the printed ID in `ADMIN_GITHUB_IDS` (then restart) for admin access. -See [`docs/frontend.md`](docs/frontend.md) for the full verification matrix. +See [`docs/frontend.md`](docs/frontend.md) for the verification matrix. ### 6. Refresh GitHub issues @@ -210,10 +210,10 @@ until then. Contribution indexing is a later phase. |---|---|---| | `DATABASE_URL` | yes | PostgreSQL connection string | | `AUTH_SECRET` | yes | Auth.js session encryption, ≥ 32 chars | -| `AUTH_GITHUB_ID` | for sign-in | GitHub OAuth App client ID | -| `AUTH_GITHUB_SECRET` | for sign-in | GitHub OAuth App client secret | +| `AUTH_GITHUB_ID` | yes | GitHub OAuth App client ID; the app does not start without it | +| `AUTH_GITHUB_SECRET` | yes | GitHub OAuth App client secret; the app does not start without it | | `ADMIN_GITHUB_IDS` | for admin | Comma-separated admin GitHub numeric IDs (may be empty) | -| `GITHUB_PROJECT_REPOSITORY` | yes | Public GitHub repository to index; defaults to `SDOC-Team/devnepal` | +| `GITHUB_PROJECT_REPOSITORY` | optional | Public GitHub repository that `db:init` indexes; defaults to `SDOC-Team/devnepal` | | `STORAGE_DIR` | yes | Directory for stored avatars (persistent volume) | | `WEB_ORIGIN` | external clients | CORS allowlist for non-browser clients (mobile); the UI is same-origin | | `GITHUB_TOKEN` | optional | Raises the GitHub API rate limit for `sync:github` | @@ -295,18 +295,14 @@ Server-rendered pages under `/en` and `/ne` (English default; `/` redirects to sanitized Markdown, member directory, member profiles, own profile editor, admin moderation, and a how-to-contribute page. -Primary navigation is: Open issues · Members · How to contribute — the project -itself is reached from the home hero and the project card. When a member is -signed in, the header shows **My profile**, and **Admin** appears for accounts -listed in `ADMIN_GITHUB_IDS`. +Primary navigation is: How to contribute · Projects · Members · About. Open +issues are reached from the project page and the home page. When a member is +signed in, the header's account menu offers **My profile**, plus **Admin** for +accounts listed in `ADMIN_GITHUB_IDS`. -The visual language is ported from the DevNepal frontend -([`voidash/DevNepal`, branch `demo/minimal-validated-flow`](https://github.com/voidash/DevNepal/tree/demo/minimal-validated-flow)): -the government state strip with the emblem, the black condensed headings, the -blue action ramp, and the component styles in -`apps/api/public/assets/devnepal/` (provenance and licences in that folder's -README). Primer CSS is vendored underneath as the base layer. Translations live -in `apps/api/src/lib/i18n.ts`. +Styling is Tailwind CSS with the design tokens defined in +`apps/api/src/app/tailwind.css`; UI primitives live in +`apps/api/src/components/ui`. Translations live in `apps/api/src/lib/i18n.ts`. ## Security invariants (tested) @@ -348,24 +344,5 @@ with mocked HTTP, and Auth.js CORS handling. Override the database with ## Deployment -See [`docs/deployment.md`](docs/deployment.md) for the full checklist, known -problems and fixes, k2 options, backups, and the cutover plan. - -```sh -docker build -f apps/api/Dockerfile -t gov-portal . -``` - -The image runs the Next.js standalone server as a non-root user (`node`), serves -the UI and the REST API on port 3000, and expects the environment variables -above. Avatars live in `/app/storage` — mount a persistent volume there. Run -migrations against the target database before starting the new image -(`bun run db:migrate` from a checkout with the production `DATABASE_URL`). - -## Notes - -- `/v1` is the canonical product API. The unversioned aliases are deprecated - migration aids and can be removed after all known consumers move to `/v1`. -- Live webhook delivery and contribution indexing are a later phase; issues - are reconciled on demand with `bun run sync:github`, and the signed webhook - endpoint is ready when delivery is wired. -- Dark mode: the design tokens ship with light mode only for now. +See [`docs/deployment.md`](docs/deployment.md) for the checklist, known +problems and their fixes, and backups. diff --git a/docs/deployment.md b/docs/deployment.md index 6b1ce75..72d24b1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -16,14 +16,11 @@ read at runtime from the process environment. ## Before you deploy — checklist 1. **Postgres 17** reachable from the app. Set `DATABASE_URL`. -2. **Apply migrations before starting the new image**, against the target - database. Either from a checkout (`bun run db:migrate`) or with the shipped - migration runner: - ```sh - docker compose run --rm migrate - ``` - The app never migrates on boot by design; a crash-looping deploy must not - half-migrate a database. +2. **Migrations run before the app starts.** With Compose, the `api` service + waits for the one-shot `migrate` service to finish successfully. Outside + Compose, run `bun run db:migrate` against the target database first. The app + process itself never migrates, so a crash-looping app cannot half-migrate a + database. 3. **`AUTH_SECRET`** ≥ 32 characters, generated fresh per environment (`openssl rand -base64 48`). Changing it signs everyone out. 4. **GitHub OAuth**: add the production callback URL to the OAuth App / @@ -61,24 +58,6 @@ read at runtime from the process environment. | Rate limits reset on restart | In-memory counters, single instance | Accept for one replica; move to a shared store before scaling out | | Webhook deliveries fail silently | Ephemeral hostnames (quick tunnels) change | Use the stable production hostname; `sync:github` reconciles gaps | -## Deploy on k2 (current host) - -k2 is a macOS machine with Caddy, `cloudflared`, and launchd; the old Django -site currently owns `devnepal.zapper.cloud`. Two viable modes: - -- **Docker (recommended for parity)**: install colima or Docker Desktop on k2, - then `docker compose up -d db api` with a production `.env.local`. The compose - file already has restart policies; put the database port behind the host - firewall or remove its `ports:` mapping for production. -- **launchd (no Docker)**: run Postgres (brew) plus the standalone server via a - launchd unit that sources an env file and runs `node apps/api/server.js`. - This mirrors the old deployment but loses image parity. - -**Cutover without downtime**: deploy the new stack on a separate hostname -(e.g. `next.devnepal.zapper.cloud` → Caddy → `127.0.0.1:3000`), validate it, -then switch the `devnepal.zapper.cloud` tunnel ingress to the new upstream and -keep the old service running until the DNS/TLS is confirmed. - ## Backups - **Database**: `docker compose exec db pg_dump -U refined refined > backup.sql` diff --git a/docs/frontend.md b/docs/frontend.md index f67bac4..bb32b11 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -12,62 +12,21 @@ Auth.js internals, or environment configuration; the linter enforces this. ## Design -The visual language is the DevNepal design system, ported from -`voidash/DevNepal` (branch `demo/minimal-validated-flow`) and served from -`apps/api/public/assets/devnepal/` — tokens, base, components, devnepal and -public-discovery stylesheets, the vendored Primer base layer, Inter/Barlow -fonts, and the emblem assets. See the README in that folder for provenance and -licences. Use the existing `dn-*`, `btn`, `card`, `Label`, `tag` and -`field` classes rather than inventing new ones. - -## Environment - -Everything the UI needs is created by `bun run setup`. For reference: - -| Variable | Where | Purpose | -|---|---|---| -| `DATABASE_URL` | `apps/api/.env.local` | PostgreSQL connection | -| `AUTH_SECRET` | `apps/api/.env.local` | session encryption (generated) | -| `AUTH_GITHUB_ID` / `AUTH_GITHUB_SECRET` | `apps/api/.env.local` | GitHub sign-in (optional for public pages) | -| `ADMIN_GITHUB_IDS` | `apps/api/.env.local` | comma-separated GitHub numeric ids that may use `/en/admin` | -| `STORAGE_DIR` | `apps/api/.env.local` | where avatars are stored | -| `WEB_ORIGIN` | `apps/api/.env.local` | only relevant to external clients; UI and API are same-origin now | +Styling is Tailwind CSS with the design tokens in `apps/api/src/app/tailwind.css`. +UI primitives are in `apps/api/src/components/ui`; reuse them before adding new +ones. ## Run it -```sh -bun run setup # env files, database, migrations, seed data (safe to rerun) -bun run dev # UI + API at http://localhost:3000/en -``` - -Public pages work with the seed alone. Seeded members: - -| Username | Status | -|---|---| -| `aashish-khanal`, `priya-sharma`, `bikash-gurung` | approved | -| `nisha-tamang` | pending | -| `rejected-sample` | rejected | -| `hidden-sample` | hidden | - -## Sign in without GitHub - -`bun run dev:session ` mints a real session cookie for a seeded -member, so you can test the profile editor and admin screens without OAuth: - -```sh -bun run dev:session nisha-tamang -``` - -It prints `authjs.session-token=…`. Add it in DevTools → Application → Cookies → -`http://localhost:3000`, then reload. For admin screens, put the printed GitHub -id into `ADMIN_GITHUB_IDS` and restart the dev server: - -```sh -bun run dev:session voidash # prints the id to add -``` +Setup, environment variables and GitHub sign-in are described in the +[README](../README.md). The GitHub OAuth App credentials are required: the app +does not start without them. -Real GitHub sign-in needs OAuth credentials in `apps/api/.env.local` (see the -README). +The setup creates no member accounts, so sign in with GitHub once to create +yours. After that, `bun run dev:session ` prints a session +cookie for that member, so you can test the profile editor and admin screens +without going through GitHub sign-in each time. It does not replace the OAuth +credentials. ## Routes @@ -93,15 +52,15 @@ handler, and request frontend and backend review. Runtime validation remains in | Behaviour | How to verify | |---|---| -| Directory lists approved members only | `/en/members` shows 3 seeded members; `nisha-tamang` absent | -| Non-public profile hidden | `/en/members/nisha-tamang` → "Profile not available"; with her session cookie → visible with a status banner | -| Username + GitHub id parity | `curl /v1/members/bikash-gurung` and `curl /v1/members/id/900103` return the same member | -| Issue filters | `/en/issues` → filter by `good first issue` (only matching rows), search `nepali` (matches title and body) | -| Issue detail | `/en/issues/101` renders labels, author, sanitized Markdown, GitHub link | +| Directory lists approved members only | a newly signed-in (pending) member is absent from `/en/members` | +| Non-public profile hidden | `/en/members/` → "Profile not available"; with that member's session cookie → visible with a status banner | +| Username + GitHub id parity | `curl /v1/members/` and `curl /v1/members/id/` return the same member | +| Issue filters | `/en/issues` → filter by a label (only matching rows), search a word from an issue title or body | +| Issue detail | `/en/issues/` renders labels, author, sanitized Markdown, GitHub link | | Profile validation | `/en/profile`: empty display name, 6 links, `http://` link, unknown skill → inline errors; save persists | -| Moderation | `/en/admin`: approve `nisha-tamang` → she appears in `/en/members`; reject → 404 publicly; priority reorders | +| Moderation | `/en/admin`: approve a pending member → they appear in `/en/members`; reject → 404 publicly; priority reorders | | Non-admin denial | sign in without the id in `ADMIN_GITHUB_IDS` → `/en/admin` shows "Not authorized" | -| Bilingual | switch `EN | ने`; paths keep the locale and copy changes | +| Bilingual | switch between English and नेपाली in the language menu; paths keep the locale and copy changes | | Sign in / out | header button completes the GitHub flow; sign-out returns to the page | ## Known gaps (intentional) @@ -109,5 +68,3 @@ handler, and request frontend and backend review. Runtime validation remains in - Live webhook delivery is deferred; issues come from `bun run sync:github` (a signed webhook endpoint exists and is tested, but nothing is wired to it). - Contribution indexing and recognition are a later phase. -- The seed's GitHub usernames are fictional; real avatars appear after real - sign-ins.