Document production database access and admin setup.
Some checks failed
CI / Lint, type check, unit tests, coverage (push) Successful in 1m54s
CI / E2E browser tests (push) Failing after 1m49s

Operators need IntelliJ-style GUI access to Docker Postgres and clear
steps for manual prod cleanup without wiping volumes.

- Add Database access section with IntelliJ, DBeaver, and SSH tunnel steps
- Document dev-only accounts, manual SQL cleanup, and hashPassword task
- Note Flyway dev-migration split and admin bootstrap in AGENTS.md
This commit is contained in:
Joakim Mörling 2026-05-21 15:14:14 +02:00
parent 93ece8128a
commit 350dfcfd7b
2 changed files with 99 additions and 3 deletions

View file

@ -76,6 +76,10 @@ live in `backend/src/main/resources/db/migration/`. Naming: `V<number>__descript
To reset: `docker compose down -v && docker compose up -d`.
Flyway schema migrations live in `db/migration/`; dev-only seeds (test users,
sample orders) are in `db/dev-migration/` and run only without the `prod` profile.
Production admin is created from `ADMIN_EMAIL` / `ADMIN_PASSWORD` on first boot.
---
## Project Structure

View file

@ -73,8 +73,8 @@ No CORS configuration needed in development — the browser never calls the back
| Profile | Datasource | Use |
|---------|-----------|-----|
| default | H2 in-memory | Local IDE dev (`./gradlew :backend:bootRun`) |
| `docker` | PostgreSQL via `docker-compose.yml` | Docker Compose dev |
| `prod` | PostgreSQL (production config) | Deploy (`docker-compose.prod.yml`) |
| `docker` | PostgreSQL + dev Flyway seeds | Docker Compose dev / CI |
| `docker,prod` | PostgreSQL, schema only, admin bootstrap | Deploy (`docker-compose.prod.yml`) |
---
@ -91,6 +91,89 @@ Copy `.env.example` to `.env` and fill in:
| `STRIPE_SECRET_KEY` | Stripe secret key |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret |
| `STRIPE_PRICE_ID` | Stripe price ID for single letter |
| `SWISH_NUMBER` | Swish number for payment instructions |
| `ADMIN_EMAIL` | Production admin login (e.g. `admin@bilhej.se`) |
| `ADMIN_PASSWORD` | Strong production admin password (not `test1234`) |
**Dev-only accounts** (from `db/dev-migration`, not used in production):
| Email | Password | Role |
|-------|----------|------|
| `test@bilhej.se` | `test1234` | user (e2e / local) |
| `admin@bilhalsning.se` | `test1234` | admin (local docker only) |
---
## Database access
PostgreSQL runs in Docker. Any GUI client works (IntelliJ IDEA, DBeaver, TablePlus,
pgAdmin) — same idea as a normal remote database.
### Local dev (`docker compose up`)
| Setting | Value |
|---------|--------|
| Host | `localhost` |
| Port | `5432` |
| Database | from `.env``POSTGRES_DB` (default `bilhej`) |
| User / password | `POSTGRES_USER` / `POSTGRES_PASSWORD` |
**IntelliJ IDEA:** Database tool window → `+` → Data Source → PostgreSQL → fill in above →
Test Connection → OK.
**CLI:**
```bash
docker exec -it bilhej-postgres psql -U bilhej -d bilhej
```
### Production (server)
Postgres is bound to **localhost only** on the server (`127.0.0.1:5433`) so it is not
exposed to the internet. Use an **SSH tunnel** from your laptop, then point IntelliJ (or
DBeaver) at `localhost`.
1. On the server, recreate the stack once so the port mapping is active (after deploy).
2. From your laptop:
```bash
ssh -N -L 5433:127.0.0.1:5433 you@srvr.nu
```
3. In IntelliJ / DBeaver:
| Setting | Value |
|---------|--------|
| Host | `localhost` |
| Port | `5433` |
| Database | prod `POSTGRES_DB` |
| User / password | prod secrets |
**CLI on the server** (no GUI):
```bash
docker exec -it bilhej-postgres-prod psql -U bilhej -d bilhej
```
### Manual prod cleanup (keep data, remove dev seeds)
```sql
DELETE FROM orders
WHERE user_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
DELETE FROM users
WHERE email IN ('test@bilhalsning.se', 'test@bilhej.se', 'admin@bilhalsning.se');
```
Then deploy with `ADMIN_EMAIL` / `ADMIN_PASSWORD` set — the app creates the production
admin on startup. No need to insert a password hash by hand.
To generate a bcrypt hash manually (optional):
```bash
./gradlew hashPassword -Ppassword='your-strong-password'
```
---
@ -187,6 +270,15 @@ Before the first deploy, complete these steps on the production server (`srvr.nu
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret |
| `STRIPE_PRICE_ID` | Stripe price ID for single letter |
| `SWISH_NUMBER` | Swish phone number for payment instructions |
| `ADMIN_EMAIL` | Production admin email (e.g. `admin@bilhej.se`) |
| `ADMIN_PASSWORD` | Strong unique admin password (password manager) |
Production does **not** seed `test@bilhej.se` or demo orders. On first start, the
backend creates one admin from `ADMIN_EMAIL` / `ADMIN_PASSWORD` if no admin exists.
If prod already has dev seed users, clean them with SQL (see [Database access](#database-access))
instead of wiping the volume. Then redeploy with the new secrets so bootstrap can create
`ADMIN_EMAIL`.
2. **Point DNS**
@ -225,7 +317,7 @@ Before the first deploy, complete these steps on the production server (`srvr.nu
| Tag | Git tag `v0.1.0` is created and pushed |
| Build | Production backend JAR and frontend bundle are built |
| Images | Multi-stage Docker images are built locally on the server |
| Start | `docker compose -f docker-compose.prod.yml up -d` |
| Start | `docker compose -f docker-compose.prod.yml up -d` (`SPRING_PROFILES_ACTIVE=docker,prod`) |
| Verify | Health checks confirm backend API and frontend are responding |
### Architecture on Server