# eLegal Software All-in-one practice management for law firms. Single Node app that serves both the React SPA and the API on one port — designed to run behind Plesk's Node.js extension on a single domain. ## Stack - **Frontend** — Vite + React 18 + TypeScript + Tailwind + Framer Motion + Recharts + lucide-react - **API** — Fastify 5 + TypeScript + Zod - **DB** — Drizzle ORM → DigitalOcean Managed Postgres (TLS) - **Auth** — local: argon2id passwords + Postgres-backed sessions in httpOnly cookies (no third-party auth provider) - **Storage** — DigitalOcean Spaces (S3-compatible, presigned uploads) - **Email** — Resend - **Payments** — Stripe - **Hosting** — Plesk + Phusion Passenger (Node 20 LTS) ## Repository layout ``` . ├── apps/ │ ├── api/ # Fastify server (also serves built web/dist in prod) │ └── web/ # Vite + React SPA ├── packages/ │ └── db/ # Drizzle schema + migrations (shared) ├── certs/ # DO Postgres CA cert (do-ca.crt) — not in git ├── scripts/ │ └── plesk-deploy.sh ├── tmp/restart.txt # touched by deploy script to bounce Passenger └── app.js # Plesk entrypoint (loads apps/api/dist/server.js) ``` ## Local development Prerequisites: Node 20+, pnpm 9+, a Postgres database (managed DO instance, or local). ```bash cp .env.example .env # fill in DATABASE_URL, SESSION_SECRET, CSRF_SECRET (generate with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`) pnpm install pnpm db:generate # generate SQL migrations from schema pnpm db:migrate # apply to the DB pnpm dev # starts api on :8080 and web on :5173 (proxies /api → :8080) ``` Visit http://localhost:5173. ## Production build ```bash pnpm build # builds packages/db → apps/web → apps/api pnpm start # runs node app.js → apps/api/dist/server.js ``` The API serves `apps/web/dist` at `/` with SPA fallback and routes `/api/*` to Fastify handlers. ## Plesk deployment (single domain) 1. **Create the domain** in Plesk and enable **Let's Encrypt** TLS. 2. **Install Node.js extension** (Plesk → Extensions → "Node.js"). Set Node version to **20.x** in the domain's Node.js settings. 3. **Pull the repo** into the domain's document root via Plesk → Git, or `git clone` over SSH into `/var/www/vhosts/yourdomain.com/httpdocs`. 4. **Node.js settings** in the Plesk panel for that domain: - **Application root** → the repo root - **Document root** → leave as default; nginx will proxy to Passenger - **Application startup file** → `app.js` - **Custom environment variables** → set every entry from `.env.example` (Passenger does **not** read `.env` files) 5. **Add DigitalOcean's Postgres CA** to `certs/do-ca.crt` (download from the DO Postgres dashboard) and set `DATABASE_CA_CERT_PATH=./certs/do-ca.crt`. 6. **Run the deploy script** over SSH: ```bash bash scripts/plesk-deploy.sh ``` This installs deps, builds, runs migrations, then `touch tmp/restart.txt` to bounce Passenger. 7. **Stripe webhook** — add `https://yourdomain.com/api/stripe/webhook` in the Stripe dashboard. In Plesk → Apache & nginx → "Additional nginx directives" add: ```nginx location /api/stripe/webhook { proxy_request_buffering off; } ``` 8. **Auto-deploy on push** (optional) — in Plesk → Git, enable "Enable additional deploy actions" and set the script to `bash scripts/plesk-deploy.sh`. ## Environment variables See `.env.example` for the full list. Highlights: | Var | Purpose | |---|---| | `DATABASE_URL` | DO Managed Postgres connection string (`?sslmode=require`) | | `DATABASE_CA_CERT_PATH` | Path to DO CA cert (recommended for `rejectUnauthorized: true`) | | `SESSION_SECRET` | 32+ byte hex used to sign cookies and as Fastify cookie secret | | `CSRF_SECRET` | 32+ byte hex for CSRF token derivation | | `SPACES_*` | DigitalOcean Spaces credentials + bucket | | `RESEND_API_KEY` | Transactional email | | `STRIPE_*` | Billing | | `PORT` | Port for Fastify (Plesk usually injects this; falls back to 8080) | | `COOKIE_DOMAIN` | Set to your apex domain in production (e.g. `lawdesk.com`); leave blank in dev | ## Database commands ```bash pnpm db:generate # create a new migration from schema changes pnpm db:migrate # apply pending migrations pnpm --filter @lawdesk/db studio # open Drizzle Studio ``` ## Auth model - Passwords hashed with **argon2id** (64MB memory cost). - Cookie holds a 32-byte random token; the DB stores its **SHA-256 hash** (so a DB read can't impersonate users). - Sessions are 30-day sliding (touched on every request). - Login rate limited: 5 failed attempts per email per 15 minutes. - All `/api/*` requests automatically attach `req.user` if a valid session cookie is present. Use `app.requireAuth` / `app.requireFirm` as preHandler guards on protected routes. ## What's next - Wire DO Spaces upload routes for documents - Build the `/app` dashboard (cases, time tracking, invoices) - Free public tools (`/tools/*`) - Stripe checkout + webhook - Email templates via Resend - pg-boss background jobs