Add README and technical docs
- README with quick start, package overview, links to docs - docs/setup.md — prerequisites, env vars, installation, running, testing - docs/architecture.md — monorepo structure, backend/frontend design - docs/api.md — full endpoint reference with permissions - docs/database.md — schema overview, migrations, multi-tenancy - docs/testing.md — test runner, suites, writing tests - Updated .env.example with all supported variables
This commit is contained in:
29
.env.example
29
.env.example
@@ -1,13 +1,14 @@
|
|||||||
# Forte — Development Environment Variables
|
# Forte — Environment Variables
|
||||||
# These are used inside Docker Compose (docker-compose.dev.yml overrides most of these)
|
# Copy to .env and adjust values for your setup.
|
||||||
|
# Docker Compose overrides host values (postgres, valkey) automatically.
|
||||||
|
|
||||||
# Database
|
# Database (PostgreSQL 16)
|
||||||
DATABASE_URL=postgresql://forte:forte@postgres:5432/forte
|
DATABASE_URL=postgresql://forte:forte@localhost:5432/forte
|
||||||
|
|
||||||
# Valkey (Redis-compatible)
|
# Valkey / Redis
|
||||||
REDIS_URL=redis://valkey:6379
|
REDIS_URL=redis://localhost:6379
|
||||||
|
|
||||||
# JWT
|
# JWT — use a strong random secret in production
|
||||||
JWT_SECRET=change-me-in-production-use-a-long-random-string
|
JWT_SECRET=change-me-in-production-use-a-long-random-string
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
@@ -16,3 +17,17 @@ HOST=0.0.0.0
|
|||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# Logging (optional)
|
||||||
|
# LOG_LEVEL=info
|
||||||
|
# LOG_FILE=./logs/forte.log
|
||||||
|
|
||||||
|
# File Storage (optional — defaults to local)
|
||||||
|
# STORAGE_PROVIDER=local
|
||||||
|
# STORAGE_LOCAL_PATH=./data/files
|
||||||
|
|
||||||
|
# CORS (optional — defaults to * in development)
|
||||||
|
# CORS_ORIGINS=https://admin.example.com
|
||||||
|
|
||||||
|
# Frontend URL (used in password reset links)
|
||||||
|
# APP_URL=http://localhost:5173
|
||||||
|
|||||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Forte
|
||||||
|
|
||||||
|
Music store management platform — POS, inventory, rentals, lessons, repairs, and accounting.
|
||||||
|
|
||||||
|
Built by [Lunarfront Tech LLC](https://lunarfront.com).
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Runtime:** Bun
|
||||||
|
- **Language:** TypeScript (end-to-end)
|
||||||
|
- **API:** Fastify + Drizzle ORM + PostgreSQL 16
|
||||||
|
- **Frontend:** React + TanStack Router + TanStack Query
|
||||||
|
- **Validation:** Zod (shared schemas)
|
||||||
|
- **Queue/Cache:** BullMQ + Valkey 8
|
||||||
|
- **Monorepo:** Turborepo + Bun workspaces
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
cp .env.example .env # configure DATABASE_URL, REDIS_URL, JWT_SECRET
|
||||||
|
cd packages/backend && bunx drizzle-kit migrate
|
||||||
|
bun run dev # starts backend (:8000) + admin UI (:5173)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
| Package | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `packages/backend` | Fastify API server |
|
||||||
|
| `packages/admin` | Admin UI (React + Vite) |
|
||||||
|
| `packages/shared` | Zod schemas, types, shared utils |
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Doc | Description |
|
||||||
|
|-----|-------------|
|
||||||
|
| [Setup](docs/setup.md) | Prerequisites, environment, installation, running |
|
||||||
|
| [Architecture](docs/architecture.md) | Monorepo structure, backend/frontend design, state management |
|
||||||
|
| [API Reference](docs/api.md) | All endpoints, pagination, auth, permissions |
|
||||||
|
| [Database](docs/database.md) | Schema overview, migrations, multi-tenancy |
|
||||||
|
| [Testing](docs/testing.md) | Test runner, suites, writing tests, assertions |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run dev # start all packages in dev mode
|
||||||
|
bun run test # run all tests
|
||||||
|
bun run lint # lint all packages
|
||||||
|
bun run format # format with Prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Proprietary. All rights reserved.
|
||||||
146
docs/api.md
Normal file
146
docs/api.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# API Reference
|
||||||
|
|
||||||
|
Base URL: `http://localhost:8000/v1`
|
||||||
|
|
||||||
|
All authenticated endpoints require `Authorization: Bearer <token>`. Registration and login require `X-Company-ID` header.
|
||||||
|
|
||||||
|
## Pagination
|
||||||
|
|
||||||
|
Every list endpoint accepts:
|
||||||
|
|
||||||
|
| Param | Default | Description |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| `page` | `1` | Page number |
|
||||||
|
| `limit` | `25` | Items per page (max 100) |
|
||||||
|
| `q` | — | Search query (ilike across relevant columns) |
|
||||||
|
| `sort` | varies | Sort field name |
|
||||||
|
| `order` | `asc` | `asc` or `desc` |
|
||||||
|
|
||||||
|
Response shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [...],
|
||||||
|
"pagination": {
|
||||||
|
"page": 1,
|
||||||
|
"limit": 25,
|
||||||
|
"total": 142,
|
||||||
|
"totalPages": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
| Method | Path | Auth | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| POST | `/auth/register` | No (needs `X-Company-ID`) | Create user account |
|
||||||
|
| POST | `/auth/login` | No | Login, returns JWT |
|
||||||
|
| GET | `/auth/me` | Yes | Current user profile |
|
||||||
|
| PATCH | `/auth/me` | Yes | Update profile (firstName, lastName) |
|
||||||
|
| POST | `/auth/change-password` | Yes | Change password |
|
||||||
|
| POST | `/auth/reset-password/:userId` | Yes (`users.admin`) | Generate password reset link |
|
||||||
|
|
||||||
|
## Accounts
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/accounts` | `accounts.view` | List accounts (paginated, searchable) |
|
||||||
|
| POST | `/accounts` | `accounts.edit` | Create account |
|
||||||
|
| GET | `/accounts/:id` | `accounts.view` | Get account |
|
||||||
|
| PATCH | `/accounts/:id` | `accounts.edit` | Update account |
|
||||||
|
| DELETE | `/accounts/:id` | `accounts.admin` | Soft-delete account |
|
||||||
|
|
||||||
|
## Members
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/members` | `accounts.view` | List all members (paginated) |
|
||||||
|
| GET | `/accounts/:id/members` | `accounts.view` | List members for account |
|
||||||
|
| POST | `/accounts/:id/members` | `accounts.edit` | Create member |
|
||||||
|
| GET | `/members/:id` | `accounts.view` | Get member |
|
||||||
|
| PATCH | `/members/:id` | `accounts.edit` | Update member |
|
||||||
|
| DELETE | `/members/:id` | `accounts.admin` | Delete member |
|
||||||
|
| POST | `/members/:id/move` | `accounts.edit` | Move member to another account |
|
||||||
|
|
||||||
|
## Member Sub-Resources
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/members/:id/identifiers` | `accounts.view` | List identifiers |
|
||||||
|
| POST | `/members/:id/identifiers` | `accounts.edit` | Create identifier |
|
||||||
|
| PATCH | `/identifiers/:id` | `accounts.edit` | Update identifier |
|
||||||
|
| DELETE | `/identifiers/:id` | `accounts.admin` | Delete identifier |
|
||||||
|
| GET | `/accounts/:id/payment-methods` | `accounts.view` | List payment methods |
|
||||||
|
| POST | `/accounts/:id/payment-methods` | `accounts.edit` | Create payment method |
|
||||||
|
| PATCH | `/payment-methods/:id` | `accounts.edit` | Update payment method |
|
||||||
|
| DELETE | `/payment-methods/:id` | `accounts.admin` | Delete payment method |
|
||||||
|
| GET | `/accounts/:id/tax-exemptions` | `accounts.view` | List tax exemptions |
|
||||||
|
| POST | `/accounts/:id/tax-exemptions` | `accounts.edit` | Create tax exemption |
|
||||||
|
| PATCH | `/tax-exemptions/:id` | `accounts.edit` | Update tax exemption |
|
||||||
|
| DELETE | `/tax-exemptions/:id` | `accounts.admin` | Delete tax exemption |
|
||||||
|
| GET | `/accounts/:id/processor-links` | `accounts.view` | List processor links |
|
||||||
|
| POST | `/accounts/:id/processor-links` | `accounts.edit` | Create processor link |
|
||||||
|
| PATCH | `/processor-links/:id` | `accounts.edit` | Update processor link |
|
||||||
|
| DELETE | `/processor-links/:id` | `accounts.admin` | Delete processor link |
|
||||||
|
|
||||||
|
## Users & RBAC
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/users` | `users.view` | List users (paginated, includes roles) |
|
||||||
|
| PATCH | `/users/:id/status` | `users.admin` | Enable/disable user |
|
||||||
|
| GET | `/users/:id/roles` | `users.view` | Get user's roles |
|
||||||
|
| POST | `/users/:id/roles` | `users.edit` | Assign role to user |
|
||||||
|
| DELETE | `/users/:id/roles/:roleId` | `users.edit` | Remove role from user |
|
||||||
|
| GET | `/permissions` | `users.view` | List all permissions |
|
||||||
|
| GET | `/roles` | `users.view` | List roles (paginated) |
|
||||||
|
| GET | `/roles/all` | `users.view` | List all roles (unpaginated, for dropdowns) |
|
||||||
|
| GET | `/roles/:id` | `users.view` | Get role with permissions |
|
||||||
|
| POST | `/roles` | `users.admin` | Create custom role |
|
||||||
|
| PATCH | `/roles/:id` | `users.admin` | Update role |
|
||||||
|
| DELETE | `/roles/:id` | `users.admin` | Delete custom role |
|
||||||
|
| GET | `/me/permissions` | Yes | Current user's permissions + roles |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/files?entityType=&entityId=` | `files.view` | List files for entity |
|
||||||
|
| POST | `/files` | `files.upload` | Upload file (multipart) |
|
||||||
|
| GET | `/files/:id` | `files.view` | Get file metadata |
|
||||||
|
| GET | `/files/serve/*` | `files.view` | Serve file content |
|
||||||
|
| DELETE | `/files/:id` | `files.delete` | Delete file |
|
||||||
|
|
||||||
|
Upload accepts multipart form with fields: `file`, `entityType`, `entityId`, `category`.
|
||||||
|
|
||||||
|
Valid entity types: `user`, `member`, `member_identifier`, `product`, `rental_agreement`, `repair_ticket`.
|
||||||
|
|
||||||
|
## Products & Inventory
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/products` | `inventory.view` | List products (paginated) |
|
||||||
|
| POST | `/products` | `inventory.edit` | Create product |
|
||||||
|
| GET | `/products/:id` | `inventory.view` | Get product |
|
||||||
|
| PATCH | `/products/:id` | `inventory.edit` | Update product |
|
||||||
|
| DELETE | `/products/:id` | `inventory.admin` | Delete product |
|
||||||
|
| GET | `/categories` | `inventory.view` | List categories |
|
||||||
|
| POST | `/categories` | `inventory.edit` | Create category |
|
||||||
|
| GET | `/suppliers` | `inventory.view` | List suppliers |
|
||||||
|
| POST | `/suppliers` | `inventory.edit` | Create supplier |
|
||||||
|
|
||||||
|
## Lookup Tables
|
||||||
|
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | `/lookups/unit-statuses` | `inventory.view` | List unit statuses |
|
||||||
|
| POST | `/lookups/unit-statuses` | `inventory.admin` | Create custom status |
|
||||||
|
| GET | `/lookups/item-conditions` | `inventory.view` | List item conditions |
|
||||||
|
| POST | `/lookups/item-conditions` | `inventory.admin` | Create custom condition |
|
||||||
|
|
||||||
|
## Health
|
||||||
|
|
||||||
|
| Method | Path | Auth | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| GET | `/health` | No | Health check |
|
||||||
120
docs/architecture.md
Normal file
120
docs/architecture.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Monorepo Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
forte/
|
||||||
|
packages/
|
||||||
|
shared/ @forte/shared — Zod schemas, types, business logic, utils
|
||||||
|
backend/ @forte/backend — Fastify API server
|
||||||
|
admin/ @forte/admin — Admin UI (React + Vite)
|
||||||
|
planning/ Domain planning docs (01-26)
|
||||||
|
docs/ Technical documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
Managed with Turborepo and Bun workspaces. `@forte/shared` is a dependency of both `backend` and `admin`.
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
**Fastify** API server with a plugin-based architecture.
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
main.ts App entry, plugin registration, server start
|
||||||
|
plugins/
|
||||||
|
database.ts Drizzle ORM + PostgreSQL connection
|
||||||
|
auth.ts JWT auth, permission checking, inheritance
|
||||||
|
redis.ts Valkey/Redis (ioredis)
|
||||||
|
cors.ts CORS configuration
|
||||||
|
storage.ts File storage provider registration
|
||||||
|
error-handler.ts Centralized error → HTTP response mapping
|
||||||
|
dev-auth.ts Dev-only auth bypass
|
||||||
|
routes/v1/
|
||||||
|
auth.ts Register, login, password change, reset, profile
|
||||||
|
accounts.ts Accounts, members, identifiers, payment methods, etc.
|
||||||
|
rbac.ts Users list, roles CRUD, role assignments, permissions
|
||||||
|
files.ts File upload/download/delete
|
||||||
|
services/
|
||||||
|
account.service.ts Account + member business logic
|
||||||
|
rbac.service.ts Roles, permissions, user role management
|
||||||
|
file.service.ts File validation, storage, metadata
|
||||||
|
product.service.ts Products + inventory
|
||||||
|
lookup.service.ts Lookup table management
|
||||||
|
inventory.service.ts Stock receipts, unit tracking
|
||||||
|
db/
|
||||||
|
schema/ Drizzle table definitions
|
||||||
|
migrations/ SQL migrations (drizzle-kit)
|
||||||
|
seeds/ System permission + role definitions
|
||||||
|
storage/
|
||||||
|
provider.ts StorageProvider interface
|
||||||
|
local.ts Local filesystem provider
|
||||||
|
s3.ts S3 provider (placeholder)
|
||||||
|
utils/
|
||||||
|
pagination.ts withPagination, withSort, buildSearchCondition, paginatedResponse
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
1. Fastify receives request
|
||||||
|
2. `onRequest` hook sets `companyId` from header
|
||||||
|
3. `authenticate` preHandler verifies JWT, loads permissions, checks `is_active`
|
||||||
|
4. `requirePermission` preHandler checks user has required permission slug
|
||||||
|
5. Route handler validates input with Zod, calls service, returns response
|
||||||
|
6. Error handler catches typed errors and maps to HTTP status codes
|
||||||
|
|
||||||
|
### Permission Inheritance
|
||||||
|
|
||||||
|
Permissions follow a hierarchy: `admin` implies `edit`, which implies `view` for the same domain. Having `accounts.admin` automatically grants `accounts.edit` and `accounts.view`. Non-hierarchical actions (`upload`, `delete`, `send`, `export`) don't cascade.
|
||||||
|
|
||||||
|
## Frontend (Admin UI)
|
||||||
|
|
||||||
|
**React** SPA with TanStack Router (file-based routing) and TanStack Query (data fetching).
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
routes/
|
||||||
|
_authenticated.tsx Layout with sidebar, permission-gated nav
|
||||||
|
_authenticated/
|
||||||
|
accounts/ Account list + detail pages
|
||||||
|
members/ Member list + detail pages
|
||||||
|
users.tsx Users admin page
|
||||||
|
roles/ Roles list + create/edit pages
|
||||||
|
profile.tsx User profile + settings
|
||||||
|
help.tsx In-app wiki
|
||||||
|
login.tsx Login page
|
||||||
|
api/ React Query options + mutations per domain
|
||||||
|
components/
|
||||||
|
ui/ shadcn/ui primitives
|
||||||
|
shared/ DataTable, AvatarUpload
|
||||||
|
accounts/ Domain-specific forms
|
||||||
|
stores/
|
||||||
|
auth.store.ts Zustand — token, user, permissions
|
||||||
|
theme.store.ts Zustand — color theme + light/dark mode
|
||||||
|
hooks/
|
||||||
|
use-pagination.ts URL-based pagination state
|
||||||
|
lib/
|
||||||
|
api-client.ts Fetch wrapper with JWT + error handling
|
||||||
|
themes.ts Color theme definitions
|
||||||
|
wiki/
|
||||||
|
index.ts In-app help content
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
| Concern | Solution |
|
||||||
|
|---------|----------|
|
||||||
|
| Auth + permissions | Zustand store, persisted to sessionStorage |
|
||||||
|
| Server data | TanStack Query (cache, refetch, invalidation) |
|
||||||
|
| URL state (pagination) | TanStack Router search params |
|
||||||
|
| Theme | Zustand store, persisted to localStorage |
|
||||||
|
| Component state | React `useState` |
|
||||||
|
|
||||||
|
## Multi-Tenancy
|
||||||
|
|
||||||
|
Every domain table has a `company_id` column. All queries filter by the authenticated user's company. Location-scoped tables (inventory, transactions) additionally filter by `location_id`.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
PostgreSQL 16 with Drizzle ORM. Migrations are generated with `bunx drizzle-kit generate` and applied with `bunx drizzle-kit migrate`. Schema files live in `packages/backend/src/db/schema/`.
|
||||||
|
|
||||||
|
Key tables: `company`, `location`, `user`, `account`, `member`, `member_identifier`, `product`, `inventory_unit`, `role`, `permission`, `user_role_assignment`, `role_permission`, `file`.
|
||||||
86
docs/database.md
Normal file
86
docs/database.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Database
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
PostgreSQL 16. Two databases:
|
||||||
|
|
||||||
|
| Database | Port | Usage |
|
||||||
|
|----------|------|-------|
|
||||||
|
| `forte` | 5432 | Development |
|
||||||
|
| `forte_api_test` | 5432 | API integration tests (auto-created by test runner) |
|
||||||
|
|
||||||
|
## Migrations
|
||||||
|
|
||||||
|
Migrations are managed by Drizzle Kit and live in `packages/backend/src/db/migrations/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/backend
|
||||||
|
|
||||||
|
# Generate a migration from schema changes
|
||||||
|
bunx drizzle-kit generate
|
||||||
|
|
||||||
|
# Apply pending migrations
|
||||||
|
bunx drizzle-kit migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Schema files: `packages/backend/src/db/schema/`
|
||||||
|
|
||||||
|
## Multi-Tenancy
|
||||||
|
|
||||||
|
All domain tables include `company_id` (uuid FK to `company`). Every query filters by the authenticated user's company. Location-scoped tables additionally include `location_id`.
|
||||||
|
|
||||||
|
## Schema Overview
|
||||||
|
|
||||||
|
### Core
|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `company` | Tenant (music store business) |
|
||||||
|
| `location` | Physical store location |
|
||||||
|
| `user` | Staff/admin user account |
|
||||||
|
|
||||||
|
### Accounts & Members
|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `account` | Billing entity (family, individual, business) |
|
||||||
|
| `member` | Individual person on an account |
|
||||||
|
| `member_identifier` | ID documents (DL, passport, school ID) |
|
||||||
|
| `payment_method` | Stored payment methods |
|
||||||
|
| `processor_link` | Payment processor integrations |
|
||||||
|
| `tax_exemption` | Tax exemption records |
|
||||||
|
|
||||||
|
### Inventory
|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `product` | Product catalog entry |
|
||||||
|
| `inventory_unit` | Individual serialized/non-serialized unit |
|
||||||
|
| `stock_receipt` | Incoming stock records |
|
||||||
|
| `category` | Product categories |
|
||||||
|
| `supplier` | Product suppliers |
|
||||||
|
| `inventory_unit_status` | Lookup: unit statuses |
|
||||||
|
| `item_condition` | Lookup: item conditions |
|
||||||
|
|
||||||
|
### RBAC
|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `permission` | System permissions (global, seeded) |
|
||||||
|
| `role` | Company-scoped roles (system + custom) |
|
||||||
|
| `role_permission` | Role-to-permission mapping |
|
||||||
|
| `user_role_assignment` | User-to-role mapping |
|
||||||
|
|
||||||
|
### Files
|
||||||
|
|
||||||
|
| Table | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `file` | File metadata (path, type, size, entity reference) |
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
- UUIDs for all primary keys (`defaultRandom()`)
|
||||||
|
- `created_at` and `updated_at` timestamps with timezone on all tables
|
||||||
|
- Soft deletes via `is_active` boolean where applicable
|
||||||
|
- Auto-generated sequential numbers: `account_number` (6-digit), `member_number`
|
||||||
|
- Lookup tables support both system (immutable) and custom (company-scoped) values
|
||||||
89
docs/setup.md
Normal file
89
docs/setup.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Development Setup
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [Bun](https://bun.sh) v1.1+
|
||||||
|
- PostgreSQL 16
|
||||||
|
- Valkey 8 (or Redis 7+)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> && cd forte
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Create a `.env` file in the project root:
|
||||||
|
|
||||||
|
```env
|
||||||
|
DATABASE_URL=postgresql://forte:forte@localhost:5432/forte
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
JWT_SECRET=your-secret-here
|
||||||
|
NODE_ENV=development
|
||||||
|
```
|
||||||
|
|
||||||
|
### All Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `DATABASE_URL` | `postgresql://forte:forte@localhost:5432/forte` | PostgreSQL connection string |
|
||||||
|
| `REDIS_URL` | `redis://localhost:6379` | Valkey/Redis connection string |
|
||||||
|
| `JWT_SECRET` | (auto-generated in dev) | Secret for signing JWTs. **Required in production.** |
|
||||||
|
| `PORT` | `8000` | Backend API port |
|
||||||
|
| `HOST` | `0.0.0.0` | Backend bind address |
|
||||||
|
| `NODE_ENV` | — | `development`, `test`, or `production` |
|
||||||
|
| `LOG_LEVEL` | `info` | Pino log level (`debug`, `info`, `warn`, `error`, `silent`) |
|
||||||
|
| `LOG_FILE` | — | Path to log file (production only, also logs to stdout) |
|
||||||
|
| `STORAGE_PROVIDER` | `local` | File storage provider (`local` or `s3`) |
|
||||||
|
| `STORAGE_LOCAL_PATH` | `./data/files` | Local file storage directory |
|
||||||
|
| `CORS_ORIGINS` | `*` (dev) | Comma-separated allowed origins |
|
||||||
|
| `APP_URL` | `http://localhost:5173` | Frontend URL (used in password reset links) |
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create the database
|
||||||
|
createdb forte
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
cd packages/backend
|
||||||
|
source ../.env # or source .env from project root
|
||||||
|
bunx drizzle-kit migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From project root — starts all packages
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# Or individually:
|
||||||
|
cd packages/backend && source .env && bun run dev # API on :8000
|
||||||
|
cd packages/admin && bun run dev # UI on :5173
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API integration tests (starts a backend, seeds DB, runs HTTP tests)
|
||||||
|
cd packages/backend
|
||||||
|
source .env
|
||||||
|
bun run api-test
|
||||||
|
|
||||||
|
# Filter by suite or tag
|
||||||
|
bun run api-test --suite accounts
|
||||||
|
bun run api-test --tag crud
|
||||||
|
|
||||||
|
# Unit tests
|
||||||
|
bun run test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run lint # ESLint across all packages
|
||||||
|
bun run format # Prettier write
|
||||||
|
bun run build # Build all packages
|
||||||
|
```
|
||||||
81
docs/testing.md
Normal file
81
docs/testing.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
## API Integration Tests
|
||||||
|
|
||||||
|
The primary test suite lives at `packages/backend/api-tests/`. It uses a custom runner that:
|
||||||
|
|
||||||
|
1. Creates/migrates a `forte_api_test` database
|
||||||
|
2. Seeds company, lookup tables, RBAC permissions/roles
|
||||||
|
3. Starts the backend on port 8001
|
||||||
|
4. Registers a test user with admin role
|
||||||
|
5. Runs test suites via HTTP requests
|
||||||
|
6. Tears down the server
|
||||||
|
|
||||||
|
### Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/backend
|
||||||
|
source .env # needs DB credentials
|
||||||
|
bun run api-test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filtering
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run api-test --suite accounts # run only the accounts suite
|
||||||
|
bun run api-test --tag crud # run tests tagged 'crud'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suites
|
||||||
|
|
||||||
|
| Suite | Tests | Coverage |
|
||||||
|
|-------|-------|----------|
|
||||||
|
| `accounts` | 17 | CRUD, search, pagination, sort, billing mode |
|
||||||
|
| `members` | 19 | CRUD, search, move, isMinor, address inheritance |
|
||||||
|
| `files` | 7 | Upload, list, get, delete, validation, profile pictures |
|
||||||
|
| `rbac` | 21 | Permission enforcement, role CRUD, user status, pagination |
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
Tests are defined in `api-tests/suites/`. Each file exports a suite:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { suite } from '../lib/context.js'
|
||||||
|
|
||||||
|
suite('MyDomain', { tags: ['domain'] }, (t) => {
|
||||||
|
t.test('does something', { tags: ['crud'] }, async () => {
|
||||||
|
const res = await t.api.post('/v1/endpoint', { name: 'Test' })
|
||||||
|
t.assert.status(res, 201)
|
||||||
|
t.assert.equal(res.data.name, 'Test')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Available on `t`:
|
||||||
|
- `t.api` — HTTP client (`get`, `post`, `patch`, `del`) with auth token
|
||||||
|
- `t.token` — JWT token string
|
||||||
|
- `t.baseUrl` — Backend URL
|
||||||
|
- `t.assert` — Assertion helpers (`status`, `equal`, `ok`, `includes`, `greaterThan`, etc.)
|
||||||
|
|
||||||
|
### Assert Methods
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `status(res, code)` | Check HTTP status |
|
||||||
|
| `equal(a, b)` | Strict equality |
|
||||||
|
| `notEqual(a, b)` | Strict inequality |
|
||||||
|
| `ok(value)` | Truthy check |
|
||||||
|
| `includes(arr, value)` | Array includes |
|
||||||
|
| `contains(str, sub)` | String contains |
|
||||||
|
| `greaterThan(a, b)` | Numeric comparison |
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
Unit tests use `bun:test` and live alongside the code or in `__tests__/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/backend
|
||||||
|
bun run test
|
||||||
|
```
|
||||||
|
|
||||||
|
Current unit test coverage: shared utils (date helpers, currency formatting, state normalization).
|
||||||
Reference in New Issue
Block a user