Files
redefined-designs/.claude/project-context.md
T
bermudalamb 7f4479605a
SonarQube Analysis / sonarqube (pull_request) Successful in 2m42s
Tests / backend-unit (pull_request) Successful in 34s
Tests / backend-integration (pull_request) Successful in 50s
Tests / frontend-e2e (pull_request) Failing after 38s
chore: replace manual SQL migrations with node-pg-migrate
2026-08-14 10:05:09 -05:00

64 lines
6.8 KiB
Markdown

# Redefined Designs — Project Context
## What this is
A storefront for one-of-a-kind items (each item has quantity 1 — once sold, it's gone). Built solo, deployed to a personal Synology NAS homelab via Docker/Portainer, behind Nginx Proxy Manager + authentik SSO for the admin panel.
**Repo**: `https://gitea.bermudalamb.synology.me/bermudalamb/redefined-designs`
**Production URL**: `https://redefined-designs.bermudalamb.synology.me`
**Local dev**: see `README.md`
## Tech stack
- **Backend**: Express + TypeScript, Postgres (`pg`), single Docker image
- **Frontend**: React + TypeScript + antd (Vite build), served as static files by the backend in production
- **Auth**: two separate systems —
- **Customers**: email/password, bcrypt, session cookie (`rd_session`), custom-built (not authentik)
- **Admin**: authentik SSO via NPM forward-auth, gated only on `/admin` and `/api/admin/*` — the storefront itself is public
- **Payments**: PayPal Orders v2 API (multi-item cart checkout, single transaction)
- **Shipping validation**: USPS Addresses API (OAuth2/REST, not the deprecated Web Tools XML API) — optional, degrades gracefully if unconfigured
- **Email**: nodemailer via Gmail SMTP — optional, degrades gracefully if unconfigured (logs a warning, skips sending)
- **Testing**: Jest (unit + integration against a disposable tmpfs Postgres), Playwright (e2e)
- **CI**: Gitea Actions — two workflows, `sonarqube.yml` (static analysis + TS build check) and `tests.yml` (unit/integration/e2e with job summaries)
## Data model (key tables)
- `items` — one-of-a-kind inventory. `status`: `available | reserved | sold`
- `item_images` — multiple images per item (front/back/etc.), ordered
- `customers` — accounts, with `marketing_consent` (explicit opt-in, GDPR-style — unchecked by default, timestamped, unsubscribe token)
- `carts` / `cart_items` — one cart per customer, items reserved with an admin-configurable expiry (`admin_settings.cart_expiry_hours`, default 24h)
- `shipping_addresses` — per-customer, USPS-validated when configured
- `checkouts` / `checkout_items` — one row per multi-item PayPal/demo transaction, snapshotting item+price at purchase time
- `orders` — per-item purchase records, linked to `checkout_id` and `customer_id`
## Key architectural decisions
1. **Cart replaced the old single-item "Buy Now" flow entirely.** Old `routes/paypal.ts` / `routes/demo.ts` (single-item) exist on disk but are unmounted — superseded by `routes/cartCheckout.ts` which does real multi-item PayPal orders (one `purchase_unit` with an itemized `items[]` breakdown, single transaction, single capture).
2. **One-of-a-kind + double-sell prevention**: adding to cart immediately flips `item.status` to `reserved` inside a DB transaction with `FOR UPDATE` locking — no two customers can hold the same item. Expired cart holds are swept every 5 minutes back to `available`.
3. **Demo mode** (`DEMO_MODE=true` env var): lets the whole site — including checkout — work with zero PayPal/USPS credentials configured, useful for local dev and just generally having a working fallback. This "degrade gracefully when a third-party integration isn't configured" pattern is used consistently: PayPal, USPS, SMTP all no-op safely if unset rather than crashing.
4. **GDPR/consent handling**: marketing consent is never pre-checked, is timestamped with the exact consent text shown, and customers have working self-service data export + account deletion endpoints (`/api/customers/me/export`, `DELETE /api/customers/me`).
5. **Admin panel security boundary**: NPM's Advanced nginx config only wraps `auth_request` around `location ~ ^/(admin|api/admin)` — everything else (storefront, cart, checkout, PayPal webhook) bypasses authentik entirely. This is a deliberate split, not an oversight — don't accidentally widen or narrow that regex without checking both directions.
6. **Cookie `secure` flag** is gated on `NODE_ENV === 'production'`, not hardcoded `true` — otherwise integration tests (plain HTTP, no TLS) silently fail to persist sessions. Learned this the hard way once already.
## Conventions (apply to all future work on this repo)
- **Every commit message follows [Conventional Commits](https://www.conventionalcommits.org/)**: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `ci:`, `refactor:`, etc.
- **Never commit directly to `main`.** Always branch: `feature/<short-description>` or `fix/<short-description>`. Open a PR, merge, branch auto-deletes (repo setting is on).
- File edits are made directly in VS Code (project is cloned locally) and pushed via PowerShell `git`, not the old NAS-SSH-with-Docker-wrapped-git workflow from earlier in this project's history (that was only used before the repo existed locally in VS Code).
- Production deploys are manual: `git pull` on the NAS copy → `docker build --no-cache` → stop/rm the container → redeploy the Portainer stack. No CD pipeline exists yet.
## Known gaps / natural next steps
- **Tests don't cover the cart/checkout/shipping feature yet** — only the pre-cart backend surface has unit/integration tests. Worth adding before trusting this in front of real customers.
- **No CD** — the Gitea Actions workflows run tests/analysis but don't deploy. Manual NAS rebuild is still required after every merge.
- **SonarQube CI still uses the `admin` token**, not a dedicated `gitea-ci` user (that was flagged as a to-do on the original .NET project this pattern was copied from, never circled back to for this repo).
- **USPS/PayPal credentials**: check whether these are actually populated in the Portainer stack env vars before assuming checkout works end-to-end in production — `DEMO_MODE` may still be the only thing actually exercised.
- **Double opt-in for marketing email** was discussed and deliberately deferred (single opt-in + easy unsubscribe was judged sufficient for now) — revisit if EU customer volume grows.
- **Daily cart reminder emails** fire via `node-cron` inside the app process at 9am container-local time — if the container restarts frequently or memory pressure causes crashes (a known NAS constraint per the broader homelab), reminders could silently stop firing with no alerting on that failure mode.
## Where to look first for common tasks
- Add/change a DB table → `backend/init.sql` (fresh deploys) **and** a one-off migration run manually via `docker exec ... psql` against the live DB (no migration framework — see `backend/migrations/` for the pattern used so far)
- Change cart/checkout behavior → `backend/src/routes/cartCheckout.ts` (the whole reserve → checkout → complete lifecycle lives here)
- Change admin-configurable settings → `admin_settings` table + `backend/src/routes/adminSettings.ts` + `frontend/src/admin/Settings.tsx`
- NPM/authentik/DSM reverse-proxy config for this app → not in this repo; documented in the homelab's broader Claude Project knowledge base, not here