The storefront showed no inventory after deploying the categories/tags release. No data was lost: the code queried categories/item_tags/ items.category_id against a database where the migration had not been run, and that failure was invisible at every layer. Three changes, each addressing one layer: Migrations now run at container start, so deployed code cannot be ahead of the schema and the easily-forgotten manual `docker exec migrate.js up` step disappears. migrate.js waits for Postgres to accept connections first, since the NAS brings the DB container up slower than the app, and still exits non-zero so a bad migration stops the container rather than serving a half-migrated schema. Express 4 does not forward a rejected async handler, and no error middleware was mounted, so a failing query never responded at all. Async routes are now wrapped and an error middleware guarantees a 500. A hung request is indistinguishable from an empty result in the UI, which is how a schema mismatch came to read as "the store has no items". The storefront now separates "request failed" from "no items" and offers a retry. fetchItems/fetchFilterOptions throw on a non-OK response rather than returning the parsed error body, which would have been set as the item list and crashed the grid on .map. Also restores the project-context update from 7fb5764, which was left out of PR #24 and ended up dangling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
165 lines
5.8 KiB
Markdown
Executable File
165 lines
5.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. |