1 Architecture
dlawler489 edited this page 2026-06-20 07:29:33 +10:00

Architecture

Stack

  • Frontend: React + Redux Toolkit + Tailwind, built with Vite, served by nginx
  • Backend: Node.js / Express + Mongoose, port 8080
  • Database: MongoDB, per-user isolation (userId on every document)
  • Auth: JWT (7-day tokens) in localStorage
  • Hosting: Docker on the home server, behind Traefik (see Deployment & CI)

Repo layout

App/
  client/      React app (pages/, components/, store/ slices, utils/)
  server/      Express API (routes/, models/, services/, middleware/)
  Dockerfile   multi-stage: client-build, server-build, production (API), nginx-frontend
  docker-compose.deploy.yml   the file Dockhand deploys
  .forgejo/workflows/         CI

Data model (MongoDB)

  • OrderorderNumber (= Etsy receipt id), items[{title, quantity, price, sku, variant, productId, printingCost, costOfGoods}], total, refundTotal, tax, status, customer, shippingAddress, tracking. Costs are snapshotted on the order at sync time so later catalogue edits don't rewrite history.
  • Product — title, price, costOfGoods, printingCost, sku, category, tags, aliases[] (past order titles that map here), etsyListingId, inventory.
  • Expense — category, description, amount, date, vendor, reference, taxDeductible. Etsy fees use reference: etsy-ledger-<entry_id> for idempotency.
  • Customer — name, email, address, etsyUserId, totalOrders, totalSpent.
  • EtsyConnection — OAuth tokens, shopId, lastSyncedAt.
  • EtsySettings — per-user API keystring, shared secret, redirect URI.

Matching logic

Order items are matched to catalogue products by, in order: exact title/alias (variant-aware, so …Size: Large beats the bare title) → etsyListingId (size-disambiguated). Matched titles are saved as aliases and listing IDs are learned, so matching gets more deterministic over time. Unmatched items surface in Settings for resolution.