A disposable stack for reviewing merged-but-undeployed changes online, running alongside production on the same NAS. Every value that could collide with production is changed: container names, host port (32751), volume paths, database name, and image tag. The Postgres data directory is the critical one — sharing production's would mean QA writing into production's database files. QA deliberately differs from production in three ways. DEMO_MODE=true with no PayPal credentials, so the full cart and checkout path is exercisable with no route to live PayPal. No SMTP configuration, so a QA run cannot email anyone; the mailer already degrades gracefully when unset. And restart: "no", so a NAS reboot does not quietly bring QA back up and leave it running. The stack must be named redefined-designs-qa in Portainer: the stack name becomes the compose project name, and reusing production's would make compose reconcile the two against each other and remove the production containers. README covers the one-time NAS directory setup, the review workflow, and how to reset QA data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
225 lines
8.8 KiB
Markdown
Executable File
225 lines
8.8 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
|
||
|
||
## 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.
|
||
|
||
## 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
|
||
|
||
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
|
||
|
||
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. |