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
111 changes: 44 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -26,22 +26,22 @@ 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/<legacy routes> temporary compatibility aliases to `/v1`
src/components/ site chrome and UI primitives
src/lib/ i18n dictionaries, client auth helpers
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
```

Expand Down Expand Up @@ -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 <https://github.com/settings/developers> → **New OAuth App**
- Homepage URL: `http://localhost:3000`
Expand All @@ -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/<login>` → `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 <githubUsername>` mints a real session cookie for a seeded
member so the profile editor and admin screens can be tested offline:
`bun run dev:session <githubUsername>` 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 <your-github-username>
```

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

Expand Down Expand Up @@ -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` |
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
31 changes: 5 additions & 26 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 /
Expand Down Expand Up @@ -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`
Expand Down
79 changes: 18 additions & 61 deletions docs/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <githubUsername>` 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 <your-github-username>` 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

Expand All @@ -93,21 +52,19 @@ 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/<pending-username>` → "Profile not available"; with that member's session cookie → visible with a status banner |
| Username + GitHub id parity | `curl /v1/members/<username>` and `curl /v1/members/id/<githubId>` 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/<number>` 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)

- 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.
Loading