Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 101 additions & 198 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,243 +1,146 @@
# FieldTrack 2.0
# FieldTrack API

> Production-grade multi-tenant backend for real-time field workforce tracking β€” attendance, GPS, expense management, and admin analytics.
Production backend for FieldTrack workforce operations.

[![CI](https://github.com/fieldtrack-tech/api/actions/workflows/deploy.yml/badge.svg)](https://github.com/fieldtrack-tech/api/actions/workflows/deploy.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/node-%3E%3D24-brightgreen)](https://nodejs.org)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org)

---
This service is implemented with Fastify + TypeScript, Supabase (Postgres), Redis, and BullMQ workers.

## Overview

FieldTrack 2.0 is a production-ready REST API for managing field workforce operations. It provides secure, multi-tenant APIs for tracking employee attendance, real-time GPS location, expense workflows, and aggregate analytics.

**Boundaries:** This repository is the API only. Infrastructure (nginx, monitoring stack, VPS provisioning) lives in the infra repository.

---

## Features

- **Multi-tenant isolation** β€” every query is scoped to the authenticated organization; cross-tenant access is architecturally impossible
- **Attendance sessions** β€” check-in / check-out lifecycle with state machine enforcement
- **Real-time GPS ingestion** β€” single and batch endpoints (up to 100 points), idempotent upsert, per-user rate limiting
- **Async distance calculation** β€” BullMQ background worker computes Haversine distance after check-out; never blocks the HTTP response
- **Expense workflow** β€” PENDING β†’ APPROVED / REJECTED lifecycle, with re-review guard
- **Admin analytics** β€” org-wide summaries, per-user breakdowns, configurable leaderboard
- **Redis-backed rate limiting** β€” per-JWT-sub limits survive corporate NAT and horizontal scaling
- **Security** β€” Helmet, CORS, Redis rate limiter, brute-force detection
- **Distributed tracing** β€” OpenTelemetry β†’ OTLP; trace IDs injected into every Pino log line
- **Blue-green zero-downtime deployments** β€” nginx upstream swap, health-check gate, 5-SHA rollback history
- **Full test suite** β€” Vitest unit + integration coverage; CI blocks deploy on failure

---

## Tech Stack

| Layer | Technology |
|-------|------------|
| **Runtime** | Node.js 24 (Debian slim / distroless) |
| **Language** | TypeScript 5.9 (strict, ESM) |
| **Framework** | Fastify 5 |
| **Database** | PostgreSQL via [Supabase](https://supabase.com) |
| **Auth** | JWT (`@fastify/jwt`) β€” Supabase-issued tokens |
| **Job Queue** | [BullMQ](https://docs.bullmq.io/) + Redis |
| **Validation** | [Zod 4](https://zod.dev/) |
| **Tracing** | OpenTelemetry (OTLP export) |
| **Security** | `@fastify/helmet` Β· `@fastify/cors` Β· `@fastify/rate-limit` Β· `@fastify/compress` |
| **Testing** | [Vitest](https://vitest.dev/) |
| **CI/CD** | GitHub Actions β†’ GHCR β†’ Blue-Green VPS Deploy |

---

## Local Development

**Prerequisites:** Node.js β‰₯ 24, npm, a running Redis instance, a Supabase project

```bash
# Install dependencies
npm install

# Configure environment
cp .env.example .env
# Edit .env β€” fill in SUPABASE_URL, keys, REDIS_URL, and CORS_ORIGIN

# Start in development mode (hot reload)
npm run dev
```

The API will start on `http://localhost:3000`.

---
The API provides multi-tenant endpoints for:

## Environment Variables
- authentication and identity (`/auth/*`)
- attendance and location tracking
- expenses and approvals
- admin dashboards, analytics, and operational tooling
- webhook and API key management

All variables are validated at startup by `src/config/env.ts` (Zod schema, fail-fast).
All tenant data access is scoped by `organization_id` derived from authenticated identity.

### URLs
## Architecture

| Variable | Required | Purpose |
|----------|:---:|---------|
| `API_BASE_URL` | βœ… | Canonical public URL of this API (`https://…`, no trailing slash) |
| `APP_BASE_URL` | βœ… | Root URL of the application β€” used in email footers and redirects |
| `FRONTEND_BASE_URL` | βœ… prod | URL of the web frontend β€” used to build email links |
Core stack:

### Runtime
- Fastify 5 + TypeScript (ESM)
- Supabase Postgres clients (anon + service role)
- Redis + BullMQ
- OpenTelemetry + Prometheus metrics
- Zod validation

| Variable | Required | Default | Purpose |
|----------|:---:|---------|---------|
| `CONFIG_VERSION` | βœ… | `"1"` | Schema version guard β€” must be `"1"` |
| `APP_ENV` | βœ… | `development` | Application environment β€” drives all app-level logic |
| `PORT` | βœ… | `3000` | Container listen port |
Runtime layout:

### Auth & Data
- `src/routes`: route registration and system routes
- `src/modules`: domain modules (auth, attendance, expenses, analytics, admin, etc.)
- `src/middleware`: auth and role guards
- `src/db`: tenant-scoped query helpers
- `src/workers`: queue workers and scheduled jobs

| Variable | Required | Purpose |
|----------|:---:|---------|
| `SUPABASE_URL` | βœ… | Supabase project URL |
| `SUPABASE_ANON_KEY` | βœ… | Supabase public/anon key |
| `SUPABASE_SERVICE_ROLE_KEY` | βœ… | Service role key β€” bypasses RLS, never expose to clients |
| `SUPABASE_JWT_SECRET` | βœ… | JWT signing secret (β‰₯ 32 chars, HS256) |
| `REDIS_URL` | βœ… | Redis connection URL (`redis://` or `rediss://`) |
Workers started by `src/workers/startup.ts`:

### Security
- distance worker
- analytics worker
- webhook worker
- snapshot worker

| Variable | Required in Prod | Default | Purpose |
|----------|:---:|---------|---------|
| `CORS_ORIGIN` | βœ… | `""` | Comma-separated allowed CORS origins. Empty activates localhost fallback in dev |
| `METRICS_SCRAPE_TOKEN` | βœ… | β€” | Token required to scrape `/metrics`. Unset = open in dev/test |
| `TEMPO_ENDPOINT` | β€” | `http://tempo:4318` | OTLP HTTP endpoint for trace export |
Scheduled jobs:

> **Observability variables (`METRICS_SCRAPE_TOKEN`, `TEMPO_ENDPOINT`) are optional for standalone operation.** The API starts and handles requests without them. `METRICS_SCRAPE_TOKEN` gates the `/metrics` endpoint (unset = endpoint is open, safe in dev/test). `TEMPO_ENDPOINT` controls where traces are exported; if the Tempo collector is unreachable, traces are silently dropped with no impact to request handling. The monitoring stack that scrapes these endpoints is managed in the [infra repository].
- snapshot reconciliation job (`reconcile_snapshot_tables()` every 5 minutes)
- retry-intent cleanup job

---
## Key Features

## Scripts
- JWT and API key auth (`Authorization: Bearer ...` or `X-API-Key`)
- strict role-based authorization (`ADMIN`, `EMPLOYEE`)
- attendance lifecycle (`check-in` and `check-out`) with async post-processing
- location ingest (single + batch)
- expense lifecycle with admin review and CSV export
- admin SSE stream at `/admin/events`
- queue/system/internal admin observability endpoints

| Command | Purpose |
|---------|---------|
| `npm run dev` | Start development server with hot reload |
| `npm run typecheck` | TypeScript type check (no emit) |
| `npm test` | Run full test suite (Vitest) |
| `npm run build` | Compile TypeScript to `dist/` |
| `npm start` | Start compiled production server |
| `./scripts/deploy.sh <sha>` | Blue-green deploy a specific image SHA |
| `./scripts/deploy.sh --rollback` | Interactive rollback to previous SHA |
| `./scripts/deploy.sh --rollback --auto` | Non-interactive rollback (CI) |
## Environment Setup

---
Environment is validated by `src/config/env.ts` at startup.

## Health Endpoints
Required baseline variables:

| Endpoint | Purpose | Deploy Gate |
|----------|---------|-------------|
| `GET /health` | Liveness check β€” returns `{"status":"ok"}` once the server bootstraps | **YES** β€” used by deploy.sh and CI |
| `GET /ready` | Dependency check β€” verifies Redis and Supabase connectivity | NO β€” informational only, not a deploy gate |
- `APP_ENV`
- `PORT`
- `SUPABASE_URL`
- `SUPABASE_ANON_KEY`
- `SUPABASE_SERVICE_ROLE_KEY`
- `SUPABASE_JWT_SECRET`
- `REDIS_URL`
- `API_BASE_URL`
- `APP_BASE_URL`
- `CORS_ORIGIN`

`/health` returns 200 after server bootstrap regardless of dependency status. `/ready` failing does not block a deployment; a degraded-but-running API is preferred over a stuck deploy.
Common optional variables:

---
- `FRONTEND_BASE_URL`
- `METRICS_SCRAPE_TOKEN`
- `TEMPO_ENDPOINT`
- `WORKERS_ENABLED`

## Deployment Overview
## Local Run

> **First-deployment requirement:** The API container joins `api_network`. On a fresh VPS, **nginx** (reverse-proxy) and **Redis** must already be running and attached to that network via the infra repository before the first `deploy.sh` run. Subsequent deploys are fully self-contained.
Prerequisites:

## Infra Requirement
- Node.js 24+
- npm
- Redis
- Supabase project

This API requires an external infra repository.
Commands:

Expected on server (all under **`INFRA_ROOT=/opt/infra`**):
- `$INFRA_ROOT/docker-compose.nginx.yml` β€” operator runs nginx from here
- `$INFRA_ROOT/docker-compose.redis.yml` β€” operator runs Redis from here
- `$INFRA_ROOT/nginx/live`, `nginx/backup`, `nginx/api.conf` β€” layout enforced by `deploy.sh` and readiness check
- nginx container on `api_network`; Redis at `redis:6379` on `api_network`

Deployments run automatically via GitHub Actions on every push to `master` (after CodeQL scan passes).

```
CodeQL deep scan (master)
β†’ validate (typecheck + audit) ──┐
β†’ test-api ─────────────────────┼──► build-scan-push ──► vps-readiness-check ──► deploy
β”˜ β”‚
api-health-gate β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
health-and-smoke ──► rollback (on failure)
```

**Blue-green strategy:** The VPS always runs two containers (`api-blue`, `api-green`). On each deploy, the inactive slot is updated and nginx is reloaded to point at it. The previous slot is stopped only after the health gate passes.

**nginx is managed by the infra repository.** The API container joins `api_network`; nginx is expected to already be running and configured.

**Manual deploy:**
```bash
./scripts/deploy.sh <sha>
```

**Rollback:**
```bash
./scripts/deploy.sh --rollback
npm install
npm run dev
```

See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for full deployment details.
Useful scripts:

---
- `npm run dev`
- `npm run build`
- `npm start`
- `npm run typecheck`
- `npm run lint`
- `npm test`

## Project Structure
## API Surface

```
api/
β”œβ”€β”€ src/ # Application source
β”‚ β”œβ”€β”€ modules/ # Domain modules (attendance Β· locations Β· expenses Β· analytics)
β”‚ β”œβ”€β”€ plugins/ # Fastify plugins (JWT Β· metrics Β· security)
β”‚ β”œβ”€β”€ workers/ # BullMQ distance calculation worker
β”‚ β”œβ”€β”€ middleware/ # Auth + role guard
β”‚ └── utils/ # Shared utilities (errors Β· response Β· tenant)
β”œβ”€β”€ tests/ # Vitest unit and integration tests
β”œβ”€β”€ scripts/ # Deploy, rollback, and utility scripts
β”œβ”€β”€ docs/ # Project documentation
└── .github/workflows/ # GitHub Actions CI/CD
```
System endpoints:

> The web frontend is in a separate repository: [fieldtrack-tech/web](https://github.com/fieldtrack-tech/web)
> Infrastructure (nginx, monitoring, VPS setup) is in a separate infra repository.
- `GET /`
- `GET /health`
- `GET /ready`
- `GET /metrics`
- `GET /openapi.json`
- `GET /docs`

---
Full endpoint contract and examples: `docs/API_REFERENCE.md`.

## Documentation
## Deployment Summary

| Document | Description |
|----------|-------------|
| [Architecture](docs/ARCHITECTURE.md) | System design, component diagrams, data flows |
| [API Reference](docs/API_REFERENCE.md) | All endpoints, auth requirements, request/response schemas, error codes |
| [Deployment Guide](docs/DEPLOYMENT.md) | VPS provisioning, CI/CD setup, blue-green deploy, troubleshooting |
| [Rollback System](docs/ROLLBACK_SYSTEM.md) | Rollback architecture, deployment history, safety features |
| [Rollback Quick Reference](docs/ROLLBACK_QUICKREF.md) | Fast operator reference card |
| [Environment Contract](docs/env-contract.md) | All environment variables, naming rules |
| [Infra Contract](docs/infra-contract.md) | External infra responsibilities and path contract (`INFRA_ROOT`) |
| [Changelog](CHANGELOG.md) | Full history of every phase |
| [Contributing](CONTRIBUTING.md) | Contribution workflow, branching, code conventions |
| [Security Policy](SECURITY.md) | How to report vulnerabilities |
Production deploy is driven by GitHub Actions and `scripts/deploy.sh`.

---
High-level flow:

## Contributing
1. CodeQL deep scan gate
2. validate + tests
3. docker build, vulnerability scan, push to GHCR
4. VPS readiness check
5. blue-green deploy
6. health and smoke checks
7. rollback on failure

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, branch naming conventions, and commit format.
Liveness gate is `/health` (not `/ready`).

**Branch naming:**
```
feature/<description> # new functionality
fix/<description> # bug fixes
docs/<description> # documentation
test/<description> # test additions
chore/<description> # maintenance / deps
```
See `docs/DEPLOYMENT.md` for detailed CI/CD and rollback behavior.

**Commit format:**
```
type(scope): short imperative description
```
Allowed types: `feat` `fix` `refactor` `ci` `docs` `test` `chore`
## Documentation

All PRs require review from CODEOWNERS and must pass CI before merge.
- `docs/API_REFERENCE.md`
- `docs/ARCHITECTURE.md`
- `docs/DEPLOYMENT.md`
- `docs/ROLLBACK_SYSTEM.md`
- `docs/env-contract.md`
- `docs/infra-contract.md`
Loading
Loading