Records the two-environment layout and a numbered checklist every change follows, with QA review as a required stop rather than a judgement call. Production is not where a bad deploy should be found, which is what happened with the categories/tags release. Production promotion now retags the image QA reviewed rather than rebuilding, so what ships is exactly what was tested, and the README carries the full command sequence with a verification gate at each step. Also records the two gates that have already failed here: confirming a pushed commit is actually on the branch, and that a schema/code ordering problem cannot be caught by any local suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
292 lines
13 KiB
Markdown
Executable File
292 lines
13 KiB
Markdown
Executable File
# Redefined Designs
|
||
|
||
One-of-a-kind item storefront. React + TypeScript + antd frontend (Vite), Express + TypeScript backend, Postgres, PayPal checkout, customer accounts with GDPR-style consent handling, and an authentik-gated admin panel.
|
||
|
||
## Project layout
|
||
|
||
backend/ Express + TypeScript API, Postgres access, PayPal integration
|
||
frontend/ React + TypeScript + antd storefront and admin UI (Vite)
|
||
|
||
## Prerequisites
|
||
|
||
- Node.js 20+
|
||
- Docker (for a local, disposable Postgres instance)
|
||
- npm
|
||
|
||
## Clone
|
||
|
||
```powershell
|
||
git clone https://gitea.bermudalamb.synology.me/bermudalamb/redefined-designs.git
|
||
cd redefined-designs
|
||
```
|
||
|
||
## Running locally
|
||
|
||
Commands below are shown for **PowerShell** (Windows). A bash equivalent is noted wherever the syntax differs.
|
||
|
||
### 1. Start a local Postgres instance
|
||
|
||
The backend needs Postgres to talk to during local development. `backend/docker-compose.test.yml` spins up a throwaway, tmpfs-backed instance — no data persists between restarts, which is fine for local dev and tests.
|
||
|
||
```powershell
|
||
cd backend
|
||
npm run db:test:up
|
||
```
|
||
|
||
This starts Postgres on `localhost:55432`, database `redefined_test`.
|
||
|
||
### 2. Run migrations
|
||
|
||
**PowerShell / bash (same command, cross-platform via Node):**
|
||
|
||
cd backend
|
||
$env:PGHOST="localhost"; $env:PGPORT="55432"; $env:PGUSER="redefined_test"; $env:PGPASSWORD="redefined_test"; $env:PGDATABASE="redefined_test"
|
||
npm install
|
||
npm run migrate:up
|
||
|
||
|
||
This applies every migration in `backend/migrations/` in order, tracked in a `pgmigrations` table so re-running is always safe (already-applied migrations are skipped).
|
||
|
||
To add a new migration:
|
||
|
||
npm run migrate:create -- descriptive-name
|
||
|
||
This generates a timestamped file in `backend/migrations/` with `exports.up`/`exports.down` stubs — fill in `pgm.sql(...)` for both directions.
|
||
### 3. Configure environment variables
|
||
|
||
**PowerShell:**
|
||
```powershell
|
||
$env:PGHOST = "localhost"
|
||
$env:PGPORT = "55432"
|
||
$env:PGUSER = "redefined_test"
|
||
$env:PGPASSWORD = "redefined_test"
|
||
$env:PGDATABASE = "redefined_test"
|
||
$env:PORT = "3000"
|
||
$env:DEMO_MODE = "true"
|
||
$env:UPLOADS_DIR = "$env:TEMP\redefined-uploads"
|
||
New-Item -ItemType Directory -Force -Path $env:UPLOADS_DIR | Out-Null
|
||
```
|
||
|
||
**bash:**
|
||
```bash
|
||
export PGHOST=localhost
|
||
export PGPORT=55432
|
||
export PGUSER=redefined_test
|
||
export PGPASSWORD=redefined_test
|
||
export PGDATABASE=redefined_test
|
||
export PORT=3000
|
||
export DEMO_MODE=true
|
||
export UPLOADS_DIR=/tmp/redefined-uploads
|
||
mkdir -p /tmp/redefined-uploads
|
||
```
|
||
|
||
`DEMO_MODE=true` enables a "Buy Now (Demo)" button on the storefront that completes a purchase without needing real PayPal credentials — useful for local development and for the Playwright tests below. To exercise real PayPal checkout locally, also set `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, and `PAYPAL_ENV=sandbox`.
|
||
|
||
**Note (PowerShell):** environment variables set with `$env:` only last for the current terminal session/tab. If you close and reopen VS Code's terminal, you'll need to re-run step 3 before starting the backend again.
|
||
|
||
### 4. Run the backend
|
||
|
||
```powershell
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
Runs on `http://localhost:3000` with hot reload.
|
||
|
||
### 5. Run the frontend
|
||
|
||
Open a **second** terminal tab/window (the backend needs to keep running in the first one):
|
||
|
||
```powershell
|
||
cd frontend
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
Runs on `http://localhost:5173` and proxies `/api`, `/uploads`, `/webhooks` to the backend on port 3000.
|
||
|
||
### 6. Open it
|
||
|
||
- Storefront: http://localhost:5173
|
||
- Admin: http://localhost:5173/admin
|
||
|
||
Note: locally, `/admin` is directly reachable with no login gate — the authentik SSO protection only exists in the deployed environment (Nginx Proxy Manager + authentik forward-auth), not in local dev.
|
||
|
||
## Running tests
|
||
|
||
### Backend unit tests
|
||
|
||
No database required.
|
||
|
||
```powershell
|
||
cd backend
|
||
npm run test:unit
|
||
```
|
||
|
||
### Backend integration tests
|
||
|
||
Exercises the real Express app against a real (disposable) Postgres instance via `supertest`.
|
||
|
||
```powershell
|
||
cd backend
|
||
npm run db:test:up
|
||
npm run test:integration
|
||
npm run db:test:down # when finished
|
||
```
|
||
|
||
### Frontend Playwright e2e tests
|
||
|
||
Needs the backend running against a database with the schema loaded (steps 1–4 above), since these tests drive real registration/login/purchase flows through a live API.
|
||
|
||
```powershell
|
||
cd frontend
|
||
npm install
|
||
npx playwright install --with-deps # first time only — installs browser binaries
|
||
npm run test:e2e
|
||
```
|
||
|
||
## CI
|
||
|
||
Gitea Actions runs two workflows on every push to `main` and on pull requests:
|
||
|
||
- **SonarQube Analysis** (`.gitea/workflows/sonarqube.yml`) — static analysis scan, plus TypeScript build checks for both `backend` and `frontend`
|
||
- **Tests** (`.gitea/workflows/tests.yml`) — backend unit tests, backend integration tests (against a disposable Postgres service container), and frontend Playwright e2e tests, each posting a results summary to the job's Summary tab in Gitea Actions
|
||
|
||
## Branching and commits
|
||
|
||
- All changes go on a branch off `main`, named `feature/<short-description>` or `fix/<short-description>` — never commit directly to `main`
|
||
- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) syntax: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `ci:`, `refactor:`, etc.
|
||
- Branches are automatically deleted after a pull request is merged
|
||
|
||
## Shipping a change
|
||
|
||
Every change follows the same path. **QA is a required stop, not an optional one** — production is not the place to discover that a deploy is wrong.
|
||
|
||
| # | Step | Gate before moving on |
|
||
| --- | --- | --- |
|
||
| 1 | Implement on a branch, verify locally | Unit, integration, and e2e suites pass; `tsc --noEmit` and `npm run build` clean |
|
||
| 2 | Commit and push | `git branch -a --contains <sha>` lists the pushed branch — a commit made after a push is silently left behind otherwise |
|
||
| 3 | Open a PR and merge to `main` | The merge commit contains the expected commits |
|
||
| 4 | **Deploy to QA and review it** | The change behaves as intended in a browser, behind authentik |
|
||
| 5 | Promote the reviewed image to production | Migrations recorded, data intact, deployed bundle is the new one |
|
||
| 6 | Stop the QA stack | — |
|
||
|
||
Steps 4 and 5 are detailed under [QA environment](#qa-environment) and [Production deployment](#production-deployment). Step 5 promotes the *same image* that was reviewed in QA, so what ships is what was tested.
|
||
|
||
## Production deployment
|
||
|
||
Production runs as a single Docker image (multi-stage build — the frontend is built to static files and served directly by the backend), deployed via Portainer behind Nginx Proxy Manager, with authentik forward-auth gating `/admin`. That infrastructure is homelab-specific and documented separately outside this repo.
|
||
|
||
The container applies pending migrations before starting the server, so deployed code can never be ahead of the database schema. A failed migration stops the container rather than letting it serve against a schema it doesn't match — check `docker logs` on the app container if it doesn't come up.
|
||
|
||
### Promoting a reviewed change to production
|
||
|
||
Only after the change has been reviewed in QA.
|
||
|
||
```bash
|
||
# 1. Back up first — the container migrates the schema on its own.
|
||
sudo docker exec -t redefined-designs-db-syn pg_dump -U redefined -d redefined \
|
||
> /volume1/configs/redefined-designs/backup-$(date +%Y%m%d-%H%M).sql
|
||
ls -lh /volume1/configs/redefined-designs/backup-*.sql # size must be plausible
|
||
|
||
# 2. Promote the exact image QA reviewed, rather than rebuilding a second time.
|
||
sudo docker tag redefined-designs:qa redefined-designs:latest
|
||
|
||
# 3. Verify the image before touching the running container.
|
||
sudo docker inspect --format '{{.Config.Cmd}}' redefined-designs:latest
|
||
# must print: [sh -c node migrate.js up && node dist/server.js]
|
||
|
||
# 4. Recreate the container — restarting it would keep the old image.
|
||
sudo docker stop redefined-designs-syn
|
||
sudo docker rm redefined-designs-syn
|
||
# redeploy the stack in Portainer
|
||
|
||
# 5. Verify, in this order.
|
||
sudo docker logs redefined-designs-syn | head -30
|
||
# migration output appears BEFORE "listening on 3000",
|
||
# and "listening on 3000" appears exactly once (repeats = crash loop)
|
||
|
||
sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined \
|
||
-c "SELECT name, run_on FROM pgmigrations ORDER BY run_on;"
|
||
sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined \
|
||
-c "SELECT count(*) FROM items;"
|
||
|
||
# 6. Confirm the deployed artifact, not just the source.
|
||
sudo docker exec redefined-designs-syn sh -c "grep -l 'must have all' /app/public/assets/*.js"
|
||
```
|
||
|
||
Then load the storefront in a private window — aggressive bundle caching has produced false "still broken" reports on this project even after a correct deploy.
|
||
|
||
**Rollback:** migrations here have been additive, so the previous image still runs against the migrated schema — retag the old image and redeploy. Restoring the SQL dump is a last resort, not a first move.
|
||
|
||
## QA environment
|
||
|
||
`docker-compose.qa.yml` defines a disposable QA stack for reviewing merged-but-undeployed changes online. It runs alongside production on the same NAS with its own containers, database, volumes, and host port, and is started only when a review is needed.
|
||
|
||
It differs from production deliberately: `DEMO_MODE=true` with no PayPal credentials (so checkout is exercisable but cannot reach live PayPal), no SMTP configuration (so it cannot email anyone), and `restart: "no"` (so a NAS reboot doesn't quietly bring it back up).
|
||
|
||
### One-time setup on the NAS
|
||
|
||
**Build the image before creating the stack.** `redefined-designs:qa` exists only on the NAS — it is never pushed to a registry. If the stack is deployed first, Compose finds no local image, falls back to pulling from Docker Hub, and fails with a misleading `pull access denied ... repository does not exist or may require 'docker login'`.
|
||
|
||
```bash
|
||
cd /volume1/docker/redefined-designs
|
||
sudo docker build --no-cache -t redefined-designs:qa /volume1/docker/redefined-designs
|
||
sudo docker images | grep redefined-designs # confirm the qa tag is present
|
||
```
|
||
|
||
For the same reason, leave Portainer's **"Pull latest image"** toggle **off** when deploying or redeploying this stack. The compose file sets `pull_policy: never` so a missing image reports itself as missing rather than as a registry authentication problem.
|
||
|
||
Next, create the directory structure. These paths are deliberately **not** under `/volume1/configs/redefined-designs` — sharing production's Postgres directory would mean QA writing into production's database files.
|
||
|
||
```bash
|
||
sudo mkdir -p /volume1/configs/redefined-designs-qa/postgres
|
||
sudo mkdir -p /volume1/configs/redefined-designs-qa/uploads
|
||
|
||
# The official postgres image runs as uid/gid 999. Without this the DB
|
||
# container exits immediately with a data-directory permissions error.
|
||
sudo chown -R 999:999 /volume1/configs/redefined-designs-qa/postgres
|
||
sudo chmod 700 /volume1/configs/redefined-designs-qa/postgres
|
||
|
||
# Uploads are written by the app container, which runs as root.
|
||
sudo chown -R root:root /volume1/configs/redefined-designs-qa/uploads
|
||
sudo chmod 755 /volume1/configs/redefined-designs-qa/uploads
|
||
|
||
# Confirm the two environments are separate before going further.
|
||
ls -la /volume1/configs/redefined-designs-qa/
|
||
ls -la /volume1/configs/redefined-designs/
|
||
```
|
||
|
||
Then, in Portainer, create a stack **named `redefined-designs-qa`** from `docker-compose.qa.yml`, with one environment variable `QA_DB_PASSWORD`. The stack name matters: it becomes the compose project name, and reusing production's name would make compose reconcile the two stacks against each other and remove the production containers.
|
||
|
||
Finally, add an Nginx Proxy Manager host for `qa-redefined-designs` pointing at the NAS on port `32751`, with authentik forward-auth on `location /` — the whole site, not just `/admin`, so nothing unreleased is publicly reachable.
|
||
|
||
### Reviewing a change
|
||
|
||
```bash
|
||
cd /volume1/docker/redefined-designs
|
||
gitc() { sudo docker run --rm -it -v /volume1/docker/redefined-designs:/repo -v ~/.gitconfig-docker/.gitconfig:/root/.gitconfig -w /repo alpine/git "$@"; }
|
||
gitc pull origin main
|
||
gitc log --oneline -3
|
||
|
||
# Rebuild the QA image before restarting the stack — the stack does not build
|
||
# anything itself, and will otherwise run whatever was last tagged :qa.
|
||
sudo docker build --no-cache -t redefined-designs:qa /volume1/docker/redefined-designs
|
||
# start the redefined-designs-qa stack in Portainer
|
||
|
||
# Migrations run as the container starts — confirm before reviewing.
|
||
sudo docker logs redefined-designs-qa-syn | head -30
|
||
```
|
||
|
||
Stop the stack in Portainer when finished.
|
||
|
||
### Resetting QA data
|
||
|
||
QA data is disposable. To start from an empty database, stop the stack first, then:
|
||
|
||
```bash
|
||
# Check the path before running this. It must contain -qa.
|
||
sudo rm -rf /volume1/configs/redefined-designs-qa/postgres/pgdata
|
||
```
|
||
|
||
Restarting the stack recreates the database and re-runs every migration from scratch. |