8.4 KiB
8.4 KiB
LunarFront — Project Conventions
App
- Name: LunarFront
- Purpose: Small business management platform (POS, inventory, rentals, scheduling, repairs, accounting)
- Company: Lunarfront Tech LLC
Tech Stack
- Runtime: Bun
- Language: TypeScript (strict mode, end-to-end)
- API: Fastify with Pino JSON logging
- ORM: Drizzle ORM (PostgreSQL 16)
- Validation: Zod (shared schemas between frontend and backend)
- Queue: BullMQ (Valkey-backed)
- Cache: Valkey 8 (Redis-compatible fork)
- Monorepo: Turborepo with Bun workspaces
- Testing: bun test (built-in, uses bun:test imports)
- Linting: ESLint 9 flat config + Prettier
Package Namespace
@lunarfront/shared— types, Zod schemas, business logic, utils@lunarfront/backend— Fastify API server
Database
- Dev:
lunarfronton localhost:5432 - Test:
lunarfront_teston localhost:5432 - Each deployed instance has its own isolated database — no multi-tenancy, no
company_id - Migrations via Drizzle Kit (
bunx drizzle-kit generate,bunx drizzle-kit migrate)
Key Entity Names
account— billing entity (family, individual, or business)member— individual person on an account (NOT "student" — renamed to support multiple adults)member.is_minor— derived from date_of_birth, controls consent/portal rules
Commands
bun run dev— start all packages in dev modebun run test— run all testsbun run lint— lint all packagesbun run format— format all files with Prettier
API Conventions
- Every endpoint that returns a list must support pagination, search, and sorting — no exceptions unless the endpoint is explicitly a lightweight lookup (see below)
?page=1&limit=25— pagination (default: page 1, 25 per page, max 100)?q=search+term— full-text search across relevant columns?sort=name&order=asc— sorting by field name, asc or desc
- List responses always return
{ data: [...], pagination: { page, limit, total, totalPages } } - Search and filtering is ALWAYS server-side, never client-side
- Use
PaginationSchemafrom@lunarfront/shared/schemasto parse query params - Use pagination helpers from
packages/backend/src/utils/pagination.ts - Lookup endpoints (e.g.,
/roles/all,/statuses/all) are the exception — these return a flat unpaginated list for populating dropdowns/selects. Use a/allsuffix to distinguish from the paginated list endpoint for the same resource.
Frontend Table Conventions
- Every table that displays data must use the shared
DataTablecomponent (components/shared/data-table.tsx) - All tables must support: search (via
?q=), sortable columns, and server-side pagination - Use the
usePagination()hook (hooks/use-pagination.ts) — it manages page, search, and sort state via URL params - All data columns that make sense to sort by should be sortable (e.g., name, email, date, status) — don't limit to just 1-2 columns
- Sub-resource tables (e.g., members within an account, payment methods) follow the same rules — use
DataTablewith pagination, not raw<Table>with unbounded queries - Loading states should use skeleton loading (built into
DataTable), not plain "Loading..." text
Conventions
- Shared Zod schemas are the single source of truth for validation (used on both frontend and backend)
- Business logic lives in
@lunarfront/shared, not in individual app packages - API routes are thin — validate with Zod, call a service, return result
- All financial events must be auditable (append-only audit records)
- JSON structured logging with request IDs on every log line
Infrastructure
Overview
LunarFront runs on DigitalOcean Kubernetes (DOKS). Each customer gets an isolated namespace, database, and Helm release managed by ArgoCD.
Key Services
- Cluster:
lunarfrontDOKS cluster, region NYC - Registry:
registry.digitalocean.com/lunarfront(DOCR) — stores Docker images and Helm charts - Git:
git.lunarfront.tech— self-hosted Gitea, source of truth for code and charts - CI: Gitea Actions — builds Docker images and Helm charts on push to
main - CD: ArgoCD at
argocd.lunarfront.tech— deploys fromlunarfront-chartsrepo - Database: DO Managed PostgreSQL — one database per customer, plus manager DB
- Cache/Queue: DO Managed Valkey — shared across all customers (key-prefixed per customer)
- Ingress: nginx ingress controller with Cloudflare proxy in front
- TLS: cert-manager with Let's Encrypt (letsencrypt-prod cluster issuer)
- DNS: Cloudflare — wildcard
*.lunarfront.tech→ cluster LB IP167.99.21.170
Node Pools
system— 2x s-2vcpu-4gb, runs ingress, ArgoCD, manager, pgbouncercustomers— autoscales 0→N, s-4vcpu-8gb, runs customer app pods (taintedrole=customer)dev— autoscales 0→1, s-4vcpu-8gb, runs dev pod only (tainteddedicated=dev:NoSchedule)
Repos
lunarfront-app— main application code (this repo)lunarfront-charts— Helm charts and ArgoCD app definitionslunarfront-infra— Terraform for DO infrastructure (DOKS, managed DBs, registry, DNS)lunarfront-manager— internal ops tool for provisioning/deprovisioning customers
Build & Deploy Pipeline
How it works
- Push code to
mainonlunarfront-app - Gitea Actions runs
.gitea/workflows/build.yml:- Builds
lunarfront-appDocker image → pushes as0.1.{run_number},{sha},latest - Builds
lunarfront-frontendDocker image → same tags - Packages Helm chart → pushes as
0.1.{run_number}to DOCR OCI registry
- Builds
- ArgoCD image updater detects new image digests → updates customer deployments
- New customer provisions always get the latest chart version (queried from DOCR at provision time)
- Existing customers upgraded via
POST /customers/:slug/upgradeorPOST /customers/upgrade-allin the manager
Versioning
- Version format:
0.1.{gitea_run_number}— always incrementing, no git commit-back needed - No version stored in git — source of truth is DOCR tags
- Chart version and app version are kept in sync
Key files
Dockerfile— backend image (bun runtime, runspackages/backend/src/main.tsdirectly)Dockerfile.frontend— frontend nginx imagechart/— Helm chart for customer app deployments.gitea/workflows/build.yml— CI pipeline.gitea/workflows/build-devpod.yml— builds dev box image on Dockerfile.devpod changes
Dev Box
What it is
A persistent development pod running in the dev namespace on the cluster. Provides a full remote dev environment accessible from anywhere.
- VS Code in browser:
dev.lunarfront.tech(Cloudflare Access protected, OTP to email) - SSH:
ssh -p 2222 root@dev-ssh.lunarfront.tech - Storage: 100GB DO block storage PVC mounted at
/root— everything in home dir persists - Image:
registry.digitalocean.com/lunarfront/manager:devpod-latest - Tools: bun, Claude Code CLI, code-server, kubectl, helm, k9s, doctl, psql, redis-cli, git
Managing the dev pod
# Scale up (provisions node automatically)
kubectl scale deployment dev -n dev --replicas=1
# Scale down (node auto-terminates after ~15 min)
kubectl scale deployment dev -n dev --replicas=0
Running the app locally on the dev box (no containers)
The dev box runs the app as plain Bun processes, connecting to the same DO managed services as production.
Required env vars (create a .env file in the repo root or export in .bashrc):
DATABASE_URL=postgresql://... # DO managed postgres, lunarfront database
REDIS_URL=rediss://... # DO managed valkey
JWT_SECRET=... # any random hex string for local dev
PORT=8000
Start the app:
cd ~/lunarfront-app
bun run dev
Access the running backend at dev.lunarfront.tech/proxy/8000/ in the browser (code-server proxy), or via SSH port forward:
ssh -p 2222 -L 8000:localhost:8000 root@dev-ssh.lunarfront.tech
Run migrations against the dev database:
bunx drizzle-kit migrate
Workflow
- Edit code in VS Code at
dev.lunarfront.techor via SSH - Run and test locally with
bun run dev— app connects to DO managed postgres/valkey - Push to
main→ Gitea Actions builds and pushes new Docker image + Helm chart - ArgoCD deploys to the cluster automatically
- Use manager at
manager.lunarfront.techto upgrade customer instances if needed