Reported from testing: the item form said "Item added" for a save that never happened. saveItem and the other admin calls returned res.json() without checking res.ok, so a 4xx/5xx resolved normally and every caller reported success for a write the server had rejected. That is worse than failing outright, because nothing prompts the user to look for the missing row. All admin calls now throw on a non-OK response, and the handlers report the error, keep the form open so entered values survive, and only claim success once the server has accepted the write. Mark sold/available previously did nothing visible on failure at all. Categories can now be created from the item form, as tags already could. Previously a category that did not exist yet meant abandoning a half-filled form for the Categories tab. New categories are created at the top level; nesting stays in the Categories tab. The control lives in its own component: inline, every keystroke re-rendered the whole Inventory component and rebuilt the category tree, which visibly jittered the open popup. It sits above the tree rather than below it, where a long list both hid it and made its position depend on the list's measured height. The tree no longer expands everything on open, which does not scale past a screenful; it has search instead. The app now honours prefers-reduced-motion by disabling antd transitions, and the e2e suite runs with that preference set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
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.
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:
$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:
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
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):
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.
cd backend
npm run test:unit
Backend integration tests
Exercises the real Express app against a real (disposable) Postgres instance via supertest.
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.
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 bothbackendandfrontend - 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, namedfeature/<short-description>orfix/<short-description>— never commit directly tomain - Commit messages follow Conventional Commits 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.