Skip to content

Commit 59ca77a

Browse files
committed
docs: replace scaffold README with project documentation
Covers local development setup, database migrations, feature flags, CI/CD pipeline, release workflow, and deployment instructions.
1 parent 84c349c commit 59ca77a

1 file changed

Lines changed: 306 additions & 23 deletions

File tree

README.md

Lines changed: 306 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,325 @@
1-
# sv
1+
# PostGuard for Business
22

3-
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
3+
Business portal for organizations to manage PostGuard identity-based email signing. Built with SvelteKit, PostgreSQL, and Yivi/IRMA attribute-based authentication.
44

5-
## Creating a project
5+
**Production:** `business.postguard.eu`
6+
**Staging:** `business.staging.postguard.eu`
67

7-
If you're seeing this, you've probably already done this step. Congrats!
8+
## Features
89

9-
```sh
10-
# create a new project
11-
npx sv create my-app
10+
- **Landing page** with pricing and organization registration
11+
- **Portal** — API key management, organization info, email audit log, DNS verification
12+
- **Admin panel** — organization management, audit log, impersonation
13+
- **Yivi authentication** — attribute-based login for both org users and admins
14+
- **Feature flags** — every feature toggleable via environment variables
15+
16+
## Tech stack
17+
18+
- [SvelteKit](https://svelte.dev/docs/kit) with `adapter-node` (server-side rendering)
19+
- [Svelte 5](https://svelte.dev/docs/svelte) with runes (`$state`, `$derived`, `$props`)
20+
- [Drizzle ORM](https://orm.drizzle.team) with `postgres.js` driver
21+
- PostgreSQL 18
22+
- SCSS with CSS custom properties (purple colorway)
23+
- [svelte-i18n](https://github.com/kaisermann/svelte-i18n) (en-US, nl-NL)
24+
25+
## Quick start
26+
27+
### Prerequisites
28+
29+
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
30+
- [Node.js 24+](https://nodejs.org/) (for running checks locally)
31+
32+
### Running locally
33+
34+
```bash
35+
# 1. Clone and install dependencies
36+
git clone git@github.com:encryption4all/postguard-business.git
37+
cd postguard-business
38+
npm install
39+
40+
# 2. Copy the example environment file
41+
cp .env.example .env
42+
43+
# 3. Start everything
44+
docker compose up
45+
```
46+
47+
This starts:
48+
49+
| Service | URL | Purpose |
50+
|---------|-----|---------|
51+
| **App** | http://localhost:8080 | SvelteKit dev server (via nginx) |
52+
| **Adminer** | http://localhost:8081 | Database admin UI |
53+
| **MailCrab** | http://localhost:1080 | Email capture UI |
54+
| **IRMA server** | http://localhost:8088 | Yivi/IRMA dev server |
55+
56+
The `db-setup` service automatically runs migrations and seeds a demo admin account + example organization on first start.
57+
58+
### Demo credentials
59+
60+
The seed script creates demo accounts that work with `irma-demo` attributes:
61+
62+
| Role | Attribute | Value |
63+
|------|-----------|-------|
64+
| **Admin** | Email | `admin@postguard.eu` |
65+
| | Full name | `Jan de Admin` |
66+
| | Phone | `0612345678` |
67+
| **Org user** | Email | `info@acme.example.nl` |
68+
69+
Admin login is at `/auth/login/admin`. Org login is at `/auth/login`.
70+
71+
Override admin credentials via `ADMIN_EMAIL`, `ADMIN_FULL_NAME`, `ADMIN_PHONE` in `.env`.
72+
73+
### Feature flags
74+
75+
Toggle features via environment variables in `.env`:
76+
77+
| Flag | Controls |
78+
|------|----------|
79+
| `FF_PRICING_PAGE` | Pricing page visibility |
80+
| `FF_REGISTRATION` | Organization registration form |
81+
| `FF_PORTAL_API_KEYS` | API key management in portal |
82+
| `FF_PORTAL_ORG_INFO` | Organization info page |
83+
| `FF_PORTAL_EMAIL_LOG` | Email audit log |
84+
| `FF_PORTAL_DNS` | DNS verification page |
85+
| `FF_ADMIN_PANEL` | Entire admin panel |
86+
| `FF_ADMIN_ORG_STATUS` | Activate/suspend org buttons |
87+
| `FF_ADMIN_AUDIT_LOG` | Admin audit log page |
88+
| `FF_ADMIN_IMPERSONATION` | Admin impersonation feature |
89+
90+
In development mode, flags can also be toggled at runtime from the admin settings page.
91+
92+
## Database
93+
94+
### Schema
95+
96+
Defined in `src/lib/server/db/schema.ts` using Drizzle's `pgTable`. Tables:
97+
98+
- `organizations` — registered organizations
99+
- `business_api_keys` — API keys (named `business_` to avoid collision with PKG's `api_keys` table)
100+
- `sessions` — server-side sessions (hashed tokens)
101+
- `change_requests` — org data change requests pending admin approval
102+
- `email_audit_log` — signed email audit trail
103+
- `dns_verifications` — domain verification records
104+
- `admin_accounts` — admin users
105+
- `admin_audit_log` — admin action audit trail
106+
107+
### Migrations
108+
109+
We use file-based SQL migrations (not `drizzle-kit push`). Migration files live in `drizzle/migrations/` and are version-controlled.
110+
111+
**Creating a new migration:**
112+
113+
```bash
114+
# 1. Edit the schema in src/lib/server/db/schema.ts
115+
116+
# 2. Generate the SQL migration
117+
npm run db:generate
118+
119+
# 3. Review the generated SQL file in drizzle/migrations/
120+
121+
# 4. Commit the migration file alongside the schema change
122+
```
123+
124+
**Running migrations locally:**
125+
126+
```bash
127+
npm run db:migrate
128+
```
129+
130+
**Migration safety rules:**
131+
132+
All migrations are checked for backward compatibility (both in a pre-commit hook and in CI). The following patterns are blocked:
133+
134+
- `DROP TABLE` / `DROP COLUMN` / `RENAME COLUMN` / `RENAME TABLE`
135+
- `SET NOT NULL` without prior backfill
136+
- `ADD COLUMN NOT NULL` without a `DEFAULT`
137+
- `TRUNCATE`
138+
139+
Use the **expand/contract** pattern for breaking changes:
140+
141+
1. **Release N:** Add new column (nullable or with default). Old code ignores it.
142+
2. **Release N+1:** Code starts using the new column.
143+
3. **Release N+2:** Drop the old column (only after old code is fully gone).
144+
145+
Run the checker manually:
146+
147+
```bash
148+
npm run db:check
12149
```
13150

14-
To recreate this project with the same configuration:
151+
### Shared database
152+
153+
The business portal and the PKG server share the same PostgreSQL instance. To avoid table name collisions, business portal API keys are stored in `business_api_keys` (not `api_keys`).
154+
155+
## Development
156+
157+
### Available scripts
158+
159+
| Command | Description |
160+
|---------|-------------|
161+
| `npm run dev` | Start SvelteKit dev server |
162+
| `npm run build` | Production build |
163+
| `npm run check` | TypeScript + Svelte type checking |
164+
| `npm run lint` | Prettier + ESLint |
165+
| `npm run format` | Auto-format with Prettier |
166+
| `npm run test` | Run unit + E2E tests |
167+
| `npm run test:unit` | Vitest unit tests |
168+
| `npm run test:e2e` | Playwright E2E tests |
169+
| `npm run db:generate` | Generate SQL migration from schema changes |
170+
| `npm run db:migrate` | Run pending migrations |
171+
| `npm run db:push` | Push schema directly (dev only) |
172+
| `npm run db:seed` | Seed demo data |
173+
| `npm run db:studio` | Open Drizzle Studio |
174+
| `npm run db:check` | Check migrations for dangerous patterns |
175+
176+
### Pre-commit hooks
177+
178+
[Husky](https://typicode.github.io/husky/) runs two checks on every commit:
179+
180+
1. `svelte-check --threshold warning` — TypeScript + Svelte type checking
181+
2. `scripts/check-migrations.ts` — migration safety rules
182+
183+
### Project structure
15184

16-
```sh
17-
# recreate this project
18-
npx sv@0.15.1 create --template minimal --types ts --add vitest="usages:unit" playwright sveltekit-adapter="adapter:node" drizzle="database:postgresql+postgresql:postgres.js+docker:no" eslint prettier --no-install .
19185
```
186+
src/
187+
routes/
188+
(marketing)/ # Public pages (landing, pricing, register)
189+
(portal)/portal/ # Authenticated org portal
190+
(admin)/admin/ # Admin panel
191+
auth/ # Login/logout
192+
api/ # JSON API endpoints
193+
irma/[...path]/ # IRMA server proxy (adds auth token)
194+
health/ # Kubernetes health endpoint
195+
lib/
196+
server/
197+
db/ # Drizzle schema + client
198+
auth/ # Session + Yivi helpers
199+
services/ # Business logic
200+
components/ # Svelte components
201+
stores/ # Svelte 5 rune-based stores
202+
locales/ # i18n (en.json, nl.json)
203+
feature-flags.ts # Feature flag system
204+
global.scss # Design tokens (purple colorway)
205+
drizzle/
206+
migrations/ # SQL migration files (version-controlled)
207+
scripts/
208+
migrate.ts # Standalone migration runner
209+
seed.ts # Demo data seeder
210+
check-migrations.ts # Migration safety checker
211+
docker/
212+
Dockerfile # Production image
213+
dev.Dockerfile # Dev image (hot reload)
214+
nginx.dev.conf # Dev reverse proxy
215+
entrypoint.sh # Production entrypoint (seed + start)
216+
```
217+
218+
## CI/CD
219+
220+
### Pipeline (`.github/workflows/ci.yml`)
221+
222+
Runs on every push to `main` and on pull requests:
223+
224+
1. **Migration Safety** — checks SQL migrations for backward-incompatible patterns
225+
2. **Svelte Check** — TypeScript + Svelte type checking
226+
3. **Tests** — Vitest + Playwright (with PostgreSQL service container)
227+
4. **Release Please** — creates GitHub releases with semantic versioning (main only)
228+
5. **Build** — multi-platform Docker images (amd64 + arm64)
229+
6. **Finalize** — merges into a multi-platform manifest on GHCR
230+
231+
### Docker image tags
232+
233+
| Trigger | Tag | Example |
234+
|---------|-----|---------|
235+
| Push to main | `edge` | `ghcr.io/encryption4all/postguard-business:edge` |
236+
| Pull request | `pr-N` | `ghcr.io/encryption4all/postguard-business:pr-42` |
237+
| Release | `X.Y.Z` | `ghcr.io/encryption4all/postguard-business:1.2.0` |
20238

21-
## Developing
239+
## Releases
22240

23-
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
241+
This project uses [Release Please](https://github.com/googleapis/release-please) for automated semantic versioning based on [Conventional Commits](https://www.conventionalcommits.org/).
24242

25-
```sh
26-
npm run dev
243+
### Commit message format
27244

28-
# or start the server and open the app in a new browser tab
29-
npm run dev -- --open
245+
| Prefix | Version bump | Example |
246+
|--------|-------------|---------|
247+
| `fix:` | Patch (1.0.x) | `fix: resolve login redirect loop` |
248+
| `feat:` | Minor (1.x.0) | `feat: add email revocation` |
249+
| `feat!:` or `BREAKING CHANGE:` | Major (x.0.0) | `feat!: change API key format` |
250+
251+
Other prefixes (`chore:`, `docs:`, `refactor:`, `test:`) do not trigger a release.
252+
253+
### Release workflow
254+
255+
1. **Write code** using conventional commit messages
256+
2. **Push to main** — CI builds the `edge` image
257+
3. **Release Please** automatically opens/updates a release PR that:
258+
- Bumps the version in `package.json`
259+
- Updates `CHANGELOG.md`
260+
- Collects all commits since the last release
261+
4. **Merge the release PR** — this triggers:
262+
- A GitHub release + git tag
263+
- A Docker image tagged with the version (e.g., `1.2.0`)
264+
5. **Deploy** — update the image tag in `postguard-ops` and apply Terraform
265+
266+
### Deploying to staging
267+
268+
Staging automatically tracks the `edge` tag. After pushing to main:
269+
270+
```bash
271+
# In postguard-ops/
272+
terraform apply -var-file=environments/dev.tfvars
30273
```
31274

32-
## Building
275+
Terraform runs the migration Job first, then rolls out the new deployment.
276+
277+
### Deploying to production
33278

34-
To create a production version of your app:
279+
After merging a release PR:
35280

36-
```sh
37-
npm run build
281+
```bash
282+
# In postguard-ops/
283+
# Update postguard_business_image_tag in environments/prod.tfvars to the new version
284+
terraform apply -var-file=environments/prod.tfvars
38285
```
39286

40-
You can preview the production build with `npm run preview`.
287+
### Kubernetes migration Job
288+
289+
Migrations run as a Kubernetes Job (`business-migrate-{tag}`) before the deployment starts. The Job:
290+
291+
- Uses the same Docker image as the app
292+
- Runs `scripts/migrate.ts` against the production database
293+
- Must complete successfully before the deployment proceeds
294+
- Retries up to 3 times, times out after 2 minutes
295+
- Auto-cleans up after 5 minutes
296+
297+
If the migration fails, Terraform stops and the deployment does not proceed.
298+
299+
> **Note:** For `edge` deploys, the Job name is `business-migrate-edge` and won't be recreated on subsequent applies with the same tag. Delete the old Job first: `kubectl delete job business-migrate-edge -n <namespace>`
300+
301+
## Infrastructure
302+
303+
Managed in [postguard-ops](https://github.com/encryption4all/postguard-ops) via Terraform.
304+
305+
### Key Terraform variables
306+
307+
| Variable | Description |
308+
|----------|-------------|
309+
| `deploy_business` | Enable/disable business portal |
310+
| `postguard_business_image_tag` | Docker image tag to deploy |
311+
| `business_host` | Public hostname |
312+
| `business_database_user` | PostgreSQL user (key in K8s `postgres` secret) |
313+
| `business_admin_secret_id` | Scaleway secret with admin credentials |
314+
315+
### Architecture
316+
317+
```
318+
Internet → Ingress → business-svc:3000 → business-deployment
319+
320+
PostgreSQL (Scaleway Managed)
321+
322+
business-migrate Job (pre-deploy)
323+
```
41324

42-
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
325+
The IRMA/Yivi server is accessed through SvelteKit's backend proxy (`/irma/[...path]`), which adds the authentication token. The browser never communicates directly with the IRMA server.

0 commit comments

Comments
 (0)