From 6fc45b12dd18d20a13db182eb95d2711fc73efc0 Mon Sep 17 00:00:00 2001 From: rajashish147 Date: Sun, 29 Mar 2026 18:15:54 +0530 Subject: [PATCH 1/9] feat: implement snapshot tables and worker for optimized metrics retrieval - Added snapshot tables: employee_last_state, pending_expenses, employee_metrics_snapshot, and active_users to improve read performance. - Created a snapshot worker to handle updates to these tables based on attendance and expense events. - Updated profile service to utilize snapshot data for faster response times. - Introduced a BullMQ queue for managing snapshot jobs, ensuring idempotency and efficient processing. - Implemented rollback SQL for safely reverting the snapshot migration if necessary. --- .../modules/attendance/attendance.service.ts | 23 + .../modules/employees/employees.controller.ts | 15 +- .../modules/employees/employees.repository.ts | 98 +++++ .../modules/expenses/expenses.controller.ts | 47 +- .../modules/expenses/expenses.repository.ts | 80 ++++ .../src/modules/expenses/expenses.service.ts | 25 ++ .../modules/locations/locations.service.ts | 30 ++ .../src/modules/profile/profile.repository.ts | 52 +++ .../src/modules/profile/profile.service.ts | 71 ++- apps/api/src/workers/snapshot.queue.ts | 188 ++++++++ apps/api/src/workers/snapshot.worker.ts | 406 ++++++++++++++++++ apps/api/src/workers/startup.ts | 5 +- .../20260329000200_feat1_rollback.sql | 89 ++++ .../20260329000200_feat1_snapshot_tables.sql | 392 +++++++++++++++++ 14 files changed, 1507 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/workers/snapshot.queue.ts create mode 100644 apps/api/src/workers/snapshot.worker.ts create mode 100644 supabase/migrations/20260329000200_feat1_rollback.sql create mode 100644 supabase/migrations/20260329000200_feat1_snapshot_tables.sql diff --git a/apps/api/src/modules/attendance/attendance.service.ts b/apps/api/src/modules/attendance/attendance.service.ts index d9da3bb..0a5c0bf 100644 --- a/apps/api/src/modules/attendance/attendance.service.ts +++ b/apps/api/src/modules/attendance/attendance.service.ts @@ -2,6 +2,7 @@ import type { FastifyRequest } from "fastify"; import { attendanceRepository } from "./attendance.repository.js"; import { enqueueDistanceJob } from "../../workers/distance.queue.js"; import { enqueueAnalyticsJob } from "../../workers/analytics.queue.js"; +import { enqueueCheckIn, enqueueCheckOut } from "../../workers/snapshot.queue.js"; import { getCached } from "../../utils/cache.js"; import { EmployeeAlreadyCheckedIn, @@ -53,6 +54,17 @@ export const attendanceService = { request.log.warn({ sessionId: session.id, error: msg }, "Failed to upsert latest session snapshot after check-in"); }); + // feat-1: enqueue snapshot update (fire-and-forget, non-blocking) + enqueueCheckIn({ + employeeId, + organizationId: request.organizationId, + sessionId: session.id, + checkinAt: session.checkin_at, + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ sessionId: session.id, error: msg }, "feat-1: failed to enqueue CHECK_IN snapshot job (non-fatal)"); + }); + sseEventBus.emitOrgEvent(request.organizationId, "session.checkin", { sessionId: session.id, employeeId, @@ -95,6 +107,17 @@ export const attendanceService = { request.log.warn({ sessionId: closedSession.id, error: msg }, "Failed to upsert latest session snapshot after check-out"); }); + // feat-1: enqueue snapshot update (fire-and-forget, non-blocking) + enqueueCheckOut({ + employeeId, + organizationId: request.organizationId, + sessionId: closedSession.id, + checkoutAt: closedSession.checkout_at ?? new Date().toISOString(), + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ sessionId: closedSession.id, error: msg }, "feat-1: failed to enqueue CHECK_OUT snapshot job (non-fatal)"); + }); + sseEventBus.emitOrgEvent(request.organizationId, "session.checkout", { sessionId: closedSession.id, employeeId, diff --git a/apps/api/src/modules/employees/employees.controller.ts b/apps/api/src/modules/employees/employees.controller.ts index 11c37d6..63b6dce 100644 --- a/apps/api/src/modules/employees/employees.controller.ts +++ b/apps/api/src/modules/employees/employees.controller.ts @@ -49,11 +49,24 @@ export const employeesController = { /** * GET /admin/employees * Paginated list of employees in the org with optional name search. + * feat-1: enriched with real-time check-in state from employee_last_state snapshot. */ async list(request: FastifyRequest, reply: FastifyReply): Promise { try { const query = employeeListQuerySchema.parse(request.query); - const result = await employeesRepository.listEmployees(request, query); + const t0 = Date.now(); + const result = await employeesRepository.listWithLastState(request, query); + const durationMs = Date.now() - t0; + request.log.info( + { route: "/admin/employees", durationMs, source: result.source, total: result.total }, + "feat1:admin-employees query", + ); + if (durationMs > 50) { + request.log.warn( + { route: "/admin/employees", durationMs }, + "feat1: slow snapshot read — expected <50ms", + ); + } reply.status(200).send(paginated(result.data, query.page, query.limit, result.total)); } catch (error) { handleError(error, request, reply, "Unexpected error listing employees"); diff --git a/apps/api/src/modules/employees/employees.repository.ts b/apps/api/src/modules/employees/employees.repository.ts index 2b4c73f..6c35bdc 100644 --- a/apps/api/src/modules/employees/employees.repository.ts +++ b/apps/api/src/modules/employees/employees.repository.ts @@ -132,5 +132,103 @@ export const employeesRepository = { if (error) throw new Error(`Failed to update employee status: ${error.message}`); return data as Employee; }, + + /** + * feat-1: Paginated employee list enriched with real-time check-in state. + * + * Joins employee_last_state so the admin employees view shows is_checked_in, + * last_check_in_at, and last location — all from O(employees) index scans + * rather than joining attendance_sessions or gps_locations. + * + * Falls back to `listEmployees` when the LEFT JOIN returns no state rows + * (snapshot not yet seeded). + */ + async listWithLastState( + request: FastifyRequest, + query: EmployeeListQuery, + ): Promise<{ + data: (Employee & { + is_checked_in: boolean; + last_check_in_at: string | null; + last_check_out_at: string | null; + last_latitude: number | null; + last_longitude: number | null; + last_location_at: string | null; + })[]; + total: number; + source: "snapshot" | "employees"; + }> { + const { page, limit, active, search } = query; + const offset = (page - 1) * limit; + + let q = supabase + .from("employees") + .select( + `${EMPLOYEE_COLS}, + employee_last_state!employee_last_state_employee_id_fkey( + is_checked_in, last_check_in_at, last_check_out_at, + last_latitude, last_longitude, last_location_at + )`, + { count: "exact" }, + ) + .eq("organization_id", request.organizationId) + .order("name", { ascending: true }) + .range(offset, offset + limit - 1); + + if (active !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + q = (q as any).eq("is_active", active === "true"); + } + if (search) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + q = (q as any).ilike("name", `%${search}%`); + } + + const { data, error, count } = await q; + + if (error) { + // Fallback: snapshot join failed — return plain employee list + const fallback = await this.listEmployees(request, query); + return { + ...fallback, + data: fallback.data.map((e) => ({ + ...e, + is_checked_in: false, + last_check_in_at: null, + last_check_out_at: null, + last_latitude: null, + last_longitude: null, + last_location_at: null, + })), + source: "employees", + }; + } + + type EmployeeWithState = Employee & { + employee_last_state: { + is_checked_in: boolean; + last_check_in_at: string | null; + last_check_out_at: string | null; + last_latitude: number | null; + last_longitude: number | null; + last_location_at: string | null; + } | null; + }; + + const enriched = ((data ?? []) as unknown as EmployeeWithState[]).map((row) => { + const { employee_last_state: state, ...employee } = row; + return { + ...(employee as Employee), + is_checked_in: state?.is_checked_in ?? false, + last_check_in_at: state?.last_check_in_at ?? null, + last_check_out_at: state?.last_check_out_at ?? null, + last_latitude: state?.last_latitude ?? null, + last_longitude: state?.last_longitude ?? null, + last_location_at: state?.last_location_at ?? null, + }; + }); + + return { data: enriched, total: count ?? 0, source: "snapshot" as const }; + }, }; diff --git a/apps/api/src/modules/expenses/expenses.controller.ts b/apps/api/src/modules/expenses/expenses.controller.ts index 58e1f01..87254c3 100644 --- a/apps/api/src/modules/expenses/expenses.controller.ts +++ b/apps/api/src/modules/expenses/expenses.controller.ts @@ -1,5 +1,6 @@ import type { FastifyRequest, FastifyReply } from "fastify"; import { expensesService } from "./expenses.service.js"; +import { expensesRepository } from "./expenses.repository.js"; import { createExpenseBodySchema, updateExpenseStatusBodySchema, @@ -60,6 +61,10 @@ export const expensesController = { * GET /admin/expenses * Returns all expenses across the organization (ADMIN only, paginated). * Accepts optional ?employee_id= to scope to a single employee. + * + * feat-1: When ?status=pending (or no status), the fast path reads from the + * pending_expenses snapshot table (O(1) index scan). For all other status + * values the full expenses table is queried (backward-compatible). */ async getOrgAll( request: FastifyRequest, @@ -69,16 +74,56 @@ export const expensesController = { const parsed = expensePaginationSchema.parse(request.query); const query = request.query as Record; const employeeId = query.employee_id; + const statusFilter = query.status; // optional: "pending" | "approved" | "rejected" + const t0 = Date.now(); + + // feat-1: fast path for PENDING expenses view (most common admin use case) + if (!statusFilter || statusFilter === "pending") { + const snapResult = await expensesRepository.findPendingFromSnapshot( + request, + parsed.page, + parsed.limit, + employeeId, + ); + + const durationMs = Date.now() - t0; + request.log.info( + { + route: "/admin/expenses", + source: snapResult.source, + durationMs, + expenseCount: snapResult.data.length, + payloadBytes: Buffer.byteLength(JSON.stringify(snapResult.data)), + }, + "feat1:admin-expenses query", + ); + if (durationMs > 50) { + request.log.warn( + { route: "/admin/expenses", durationMs, source: snapResult.source }, + "feat1: slow snapshot read — expected <50ms", + ); + } + + if (snapResult.source === "snapshot") { + const response = paginated(snapResult.data, parsed.page, parsed.limit, snapResult.total); + reply.status(200).send(response); + return; + } + // Snapshot read failed — fall through to full table query below + } + + // Full table query (non-pending status or snapshot unavailable) const result = await expensesService.getOrgExpenses( request, parsed.page, parsed.limit, employeeId, ); + const durationMs = Date.now() - t0; const response = paginated(result.data, parsed.page, parsed.limit, result.total); const payloadBytes = Buffer.byteLength(JSON.stringify(response)); request.log.info( - { route: "/admin/expenses", payloadBytes, expenseCount: result.data.length }, + { route: "/admin/expenses", payloadBytes, expenseCount: result.data.length, durationMs, source: "full_table" }, "phase30:admin-expenses", ); reply.status(200).send(response); diff --git a/apps/api/src/modules/expenses/expenses.repository.ts b/apps/api/src/modules/expenses/expenses.repository.ts index b8dfcbc..7648799 100644 --- a/apps/api/src/modules/expenses/expenses.repository.ts +++ b/apps/api/src/modules/expenses/expenses.repository.ts @@ -206,4 +206,84 @@ export const expensesRepository = { const safeOffset = (Math.max(1, page) - 1) * safeLimit; return { data: groups.slice(safeOffset, safeOffset + safeLimit), total }; }, + + /** + * feat-1: Fast paginated list of PENDING expenses from the denormalised snapshot. + * + * Reads from `pending_expenses` which is maintained by the snapshot worker. + * This is an O(1) index scan on (organization_id, submitted_at DESC) instead of + * the O(all_expenses) scan on the full expenses table. + * + * Falls back to `findExpensesByOrg` when a read error occurs so the API + * remains functional even if the snapshot table is temporarily stale. + * + * Returns enriched rows (employee_name, employee_code) via a join so the + * admin UI receives the same shape as the full expenses endpoint. + */ + async findPendingFromSnapshot( + request: FastifyRequest, + page: number, + limit: number, + employeeId?: string, + ): Promise<{ data: EnrichedExpense[]; total: number; source: "snapshot" | "fallback" }> { + const t0 = Date.now(); + const offset = (page - 1) * limit; + + let q = supabase + .from("pending_expenses") + .select( + "id, organization_id, employee_id, amount, submitted_at, employees!pending_expenses_employee_id_fkey(name, employee_code)", + { count: "exact" }, + ) + .eq("organization_id", request.organizationId) + .order("submitted_at", { ascending: false }) + .range(offset, offset + limit - 1); + + if (employeeId) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + q = (q as any).eq("employee_id", employeeId); + } + + const { data, error, count } = await q; + + const durationMs = Date.now() - t0; + if (durationMs > 50) { + // Phase 6 logging: snapshot reads must be <50 ms + // (logged at caller level with full route context) + } + + if (error) { + // Snapshot unavailable — return sentinel so caller can log and fall back. + return { data: [], total: 0, source: "fallback" }; + } + + type PendingRow = { + id: string; + organization_id: string; + employee_id: string; + amount: number; + submitted_at: string; + employees: { name?: string; employee_code?: string } | null; + }; + + const enriched: EnrichedExpense[] = ((data ?? []) as PendingRow[]).map((row) => ({ + id: row.id, + organization_id: row.organization_id, + employee_id: row.employee_id, + amount: row.amount, + description: "", // not stored in snapshot — admin expense list doesn't need it + status: "PENDING" as const, + receipt_url: null, + submitted_at: row.submitted_at, + reviewed_at: null, + reviewed_by: null, + rejection_comment: null, + created_at: row.submitted_at, + updated_at: row.submitted_at, + employee_name: row.employees?.name ?? null, + employee_code: row.employees?.employee_code ?? null, + })); + + return { data: enriched, total: count ?? 0, source: "snapshot" }; + }, }; diff --git a/apps/api/src/modules/expenses/expenses.service.ts b/apps/api/src/modules/expenses/expenses.service.ts index b909dba..153a745 100644 --- a/apps/api/src/modules/expenses/expenses.service.ts +++ b/apps/api/src/modules/expenses/expenses.service.ts @@ -14,6 +14,7 @@ import { invalidateOrgAnalytics } from "../../utils/cache.js"; import { sseEventBus } from "../../utils/sse-emitter.js"; import { emitEvent } from "../../utils/event-bus.js"; import { supabaseServiceClient } from "../../config/supabase.js"; +import { enqueueExpenseCreated, enqueueExpenseResolved } from "../../workers/snapshot.queue.js"; /** * Expenses service — business rules for expense management. @@ -103,6 +104,18 @@ export const expensesService = { }, }); + // feat-1: insert into pending_expenses snapshot (fire-and-forget) + enqueueExpenseCreated({ + employeeId, + organizationId: request.organizationId, + expenseId: expense.id, + amount: Number(expense.amount), + submittedAt: expense.submitted_at, + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ expenseId: expense.id, error: msg }, "feat-1: failed to enqueue EXPENSE_CREATED snapshot job (non-fatal)"); + }); + return expense; }, @@ -231,6 +244,18 @@ export const expensesService = { }); } + // feat-1: remove from pending_expenses snapshot; update metrics on approval (fire-and-forget) + enqueueExpenseResolved({ + employeeId: updated.employee_id, + organizationId: request.organizationId, + expenseId: updated.id, + amount: Number(updated.amount), + resolution: body.status === "APPROVED" ? "EXPENSE_APPROVED" : "EXPENSE_REJECTED", + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ expenseId: updated.id, error: msg }, "feat-1: failed to enqueue EXPENSE_RESOLVED snapshot job (non-fatal)"); + }); + return updated; }, diff --git a/apps/api/src/modules/locations/locations.service.ts b/apps/api/src/modules/locations/locations.service.ts index 5f1da40..a6ba47c 100644 --- a/apps/api/src/modules/locations/locations.service.ts +++ b/apps/api/src/modules/locations/locations.service.ts @@ -9,6 +9,7 @@ import type { CreateLocationBatchBody, } from "./locations.schema.js"; import { profileRepository } from "../profile/profile.repository.js"; +import { enqueueLocationUpdate } from "../../workers/snapshot.queue.js"; import { performance } from "perf_hooks"; @@ -56,6 +57,19 @@ export const locationsService = { // Update last_activity_at (fire-and-forget) profileRepository.updateLastActivity(request, employeeId).catch(() => {}); + // feat-1: update snapshot with latest GPS fix (fire-and-forget, non-blocking) + enqueueLocationUpdate({ + employeeId, + organizationId: request.organizationId, + sessionId: body.session_id, + latitude: body.latitude, + longitude: body.longitude, + recordedAt: body.recorded_at, + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ sessionId: body.session_id, error: msg }, "feat-1: failed to enqueue LOCATION_UPDATE snapshot job (non-fatal)"); + }); + metrics.incrementLocationsInserted(1); request.log.info( @@ -119,6 +133,22 @@ export const locationsService = { // Update last_activity_at (fire-and-forget — lightweight, no analytics tables touched) profileRepository.updateLastActivity(request, employeeId).catch(() => {}); + // feat-1: update snapshot with the latest GPS fix in the batch (fire-and-forget) + const latestPoint = body.points.reduce((latest, p) => + new Date(p.recorded_at) > new Date(latest.recorded_at) ? p : latest, + ); + enqueueLocationUpdate({ + employeeId, + organizationId: request.organizationId, + sessionId: body.session_id, + latitude: latestPoint.latitude, + longitude: latestPoint.longitude, + recordedAt: latestPoint.recorded_at, + }).catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + request.log.warn({ sessionId: body.session_id, error: msg }, "feat-1: failed to enqueue LOCATION_UPDATE snapshot job (non-fatal)"); + }); + request.log.info( { userId: request.user.sub, diff --git a/apps/api/src/modules/profile/profile.repository.ts b/apps/api/src/modules/profile/profile.repository.ts index aeb37c9..bb9d595 100644 --- a/apps/api/src/modules/profile/profile.repository.ts +++ b/apps/api/src/modules/profile/profile.repository.ts @@ -1,4 +1,5 @@ import { orgTable } from "../../db/query.js"; +import { supabaseServiceClient as supabase } from "../../config/supabase.js"; import type { FastifyRequest } from "fastify"; import type { ActivityStatus } from "@fieldtrack/types"; @@ -143,4 +144,55 @@ export const profileRepository = { ); } }, + + /** + * feat-1: Read the precomputed cumulative metrics snapshot for an employee. + * + * Returns null when the snapshot row doesn't exist yet (e.g. the first + * few seconds after a new employee's first check-in). Callers fall back + * to the legacy employee_daily_metrics aggregation in that case. + * + * Uses the service-role client so this method is also safe to call from + * admin contexts where the RLS-bound orgTable might reject the read. + */ + async getMetricsSnapshot( + employeeId: string, + organizationId: string, + ): Promise<{ + totalSessions: number; + totalDistanceKm: number; + totalDurationSeconds: number; + totalExpenses: number; + lastActiveAt: string | null; + } | null> { + const { data, error } = await supabase + .from("employee_metrics_snapshot") + .select("total_sessions, total_hours, total_distance, total_expenses, last_active_at") + .eq("employee_id", employeeId) + .eq("organization_id", organizationId) + .maybeSingle(); + + if (error) { + // Non-fatal: fall through to daily_metrics aggregation + return null; + } + if (!data) return null; + + const row = data as { + total_sessions: number; + total_hours: number; + total_distance: number; + total_expenses: number; + last_active_at: string | null; + }; + + return { + totalSessions: row.total_sessions ?? 0, + // total_hours is stored as hours; callers expect totalDurationSeconds + totalDurationSeconds: Math.round((row.total_hours ?? 0) * 3600), + totalDistanceKm: row.total_distance ?? 0, + totalExpenses: row.total_expenses ?? 0, + lastActiveAt: row.last_active_at, + }; + }, }; diff --git a/apps/api/src/modules/profile/profile.service.ts b/apps/api/src/modules/profile/profile.service.ts index e6ce1d5..30779db 100644 --- a/apps/api/src/modules/profile/profile.service.ts +++ b/apps/api/src/modules/profile/profile.service.ts @@ -30,10 +30,65 @@ export const profileService = { throw new NotFoundError("Employee not found"); } - const [stats, expenseStats] = await Promise.all([ - profileRepository.getEmployeeStats(request, employeeId), - profileRepository.getEmployeeExpenseStats(request, employeeId), - ]); + // feat-1: attempt single-row snapshot read first (O(1) PK lookup). + // Falls back to daily_metrics scan when snapshot hasn't been seeded yet. + const t0 = Date.now(); + const snapshot = await profileRepository.getMetricsSnapshot( + employeeId, + request.organizationId, + ); + const snapshotMs = Date.now() - t0; + + let stats: { + totalSessions: number; + totalDistanceKm: number; + totalDurationSeconds: number; + expensesSubmitted: number; + expensesApproved: number; + }; + + if (snapshot) { + // Snapshot hit: all totals from one PK lookup — typically <5 ms. + if (snapshotMs > 50) { + request.log.warn( + { employeeId, snapshotMs, route: "profile" }, + "feat-1: slow snapshot read — expected <50ms", + ); + } + const expenseStats = await profileRepository.getEmployeeExpenseStats(request, employeeId); + stats = { + totalSessions: snapshot.totalSessions, + totalDistanceKm: snapshot.totalDistanceKm, + totalDurationSeconds: snapshot.totalDurationSeconds, + expensesSubmitted: expenseStats.expensesSubmitted, + expensesApproved: expenseStats.expensesApproved, + }; + } else { + // Snapshot miss (first run / not yet seeded): fall back to legacy aggregation. + request.log.info({ employeeId }, "feat-1: snapshot miss — falling back to daily_metrics"); + const [legacyStats, expenseStats] = await Promise.all([ + profileRepository.getEmployeeStats(request, employeeId), + profileRepository.getEmployeeExpenseStats(request, employeeId), + ]); + stats = { + totalSessions: legacyStats.totalSessions, + totalDistanceKm: legacyStats.totalDistanceKm, + totalDurationSeconds: legacyStats.totalDurationSeconds, + expensesSubmitted: expenseStats.expensesSubmitted, + expensesApproved: expenseStats.expensesApproved, + }; + } + + request.log.info( + { + employeeId, + snapshotMs, + totalMs: Date.now() - t0, + source: snapshot ? "snapshot" : "daily_metrics", + route: "feat1:profile", + }, + "feat1:profile query", + ); return { id: employee.id, @@ -44,13 +99,7 @@ export const profileService = { activityStatus: computeActivityStatusFromTimestamp(employee.last_activity_at), last_activity_at: employee.last_activity_at, created_at: employee.created_at, - stats: { - totalSessions: stats.totalSessions, - totalDistanceKm: stats.totalDistanceKm, - totalDurationSeconds: stats.totalDurationSeconds, - expensesSubmitted: expenseStats.expensesSubmitted, - expensesApproved: expenseStats.expensesApproved, - }, + stats, }; }, }; diff --git a/apps/api/src/workers/snapshot.queue.ts b/apps/api/src/workers/snapshot.queue.ts new file mode 100644 index 0000000..757f489 --- /dev/null +++ b/apps/api/src/workers/snapshot.queue.ts @@ -0,0 +1,188 @@ +/** + * snapshot.queue.ts — BullMQ queue for feat-1 snapshot table maintenance. + * + * Every significant domain event (check-in, check-out, location update, + * expense lifecycle) emits a typed job to this queue. The snapshot worker + * processes each job idempotently to keep the denormalised snapshot tables + * (employee_last_state, pending_expenses, employee_metrics_snapshot, + * active_users) up to date. + * + * Queue name: "snapshot-engine" + * DLQ name: "snapshot-failed" + * + * Retry policy: 5 attempts, exponential 1 s → 16 s. + * removeOnComplete: true (keep Redis lean) + * removeOnFail: false (retain for operator inspection) + * + * Idempotency: every job carries a stable `jobId` so BullMQ silently drops + * duplicate enqueues. The worker itself uses UPSERT / ON CONFLICT so + * processing the same job twice produces the same result. + */ + +import { Queue } from "bullmq"; +import { getRedisConnectionOptions } from "../config/redis.js"; +import { standardJobOptions } from "../lib/queue.js"; + +// ─── Job Payload Types ──────────────────────────────────────────────────────── + +export interface CheckInJobData { + type: "CHECK_IN"; + employeeId: string; + organizationId: string; + sessionId: string; + checkinAt: string; +} + +export interface CheckOutJobData { + type: "CHECK_OUT"; + employeeId: string; + organizationId: string; + sessionId: string; + checkoutAt: string; +} + +export interface LocationUpdateJobData { + type: "LOCATION_UPDATE"; + employeeId: string; + organizationId: string; + sessionId: string; + latitude: number; + longitude: number; + recordedAt: string; +} + +export interface ExpenseCreatedJobData { + type: "EXPENSE_CREATED"; + employeeId: string; + organizationId: string; + expenseId: string; + amount: number; + submittedAt: string; +} + +export interface ExpenseResolvedJobData { + type: "EXPENSE_APPROVED" | "EXPENSE_REJECTED"; + employeeId: string; + organizationId: string; + expenseId: string; + amount: number; // original amount — used to update total_expenses on APPROVED +} + +export type SnapshotJobData = + | CheckInJobData + | CheckOutJobData + | LocationUpdateJobData + | ExpenseCreatedJobData + | ExpenseResolvedJobData; + +export interface SnapshotFailedJobData { + originalData: SnapshotJobData; + failedAt: string; + reason: string; +} + +// ─── Lazy Singletons ───────────────────────────────────────────────────────── + +let _snapshotQueue: Queue | undefined; +let _snapshotFailedQueue: Queue | undefined; + +function getSnapshotQueue(): Queue { + if (!_snapshotQueue) { + _snapshotQueue = new Queue("snapshot-engine", { + connection: getRedisConnectionOptions(), + defaultJobOptions: standardJobOptions, + }); + } + return _snapshotQueue; +} + +function getSnapshotFailedQueue(): Queue { + if (!_snapshotFailedQueue) { + _snapshotFailedQueue = new Queue( + "snapshot-failed", + { + connection: getRedisConnectionOptions(), + defaultJobOptions: { + removeOnComplete: { count: 500 }, + removeOnFail: false, + }, + }, + ); + } + return _snapshotFailedQueue; +} + +// ─── Dead-Letter Helper ─────────────────────────────────────────────────────── + +export async function moveSnapshotToDeadLetter( + jobData: SnapshotJobData, + reason: string, +): Promise { + await getSnapshotFailedQueue().add("dead-letter", { + originalData: jobData, + failedAt: new Date().toISOString(), + reason, + }); +} + +// ─── Enqueue Helpers ───────────────────────────────────────────────────────── + +/** + * Deterministic jobId prevents duplicate jobs for the same logical event. + * The snapshot worker is idempotent so even if a duplicate slips through, + * the result is correct. + */ + +export async function enqueueCheckIn(data: Omit): Promise { + const full: CheckInJobData = { type: "CHECK_IN", ...data }; + await getSnapshotQueue().add("snapshot", full, { + jobId: `checkin:${data.sessionId}`, + }); +} + +export async function enqueueCheckOut(data: Omit): Promise { + const full: CheckOutJobData = { type: "CHECK_OUT", ...data }; + await getSnapshotQueue().add("snapshot", full, { + jobId: `checkout:${data.sessionId}`, + }); +} + +/** + * Location updates are high-frequency — the jobId uses recordedAt so that + * only the latest point per session-timestamp pair is kept in the queue. + * If the same point is received twice (client retry), BullMQ deduplicates it. + */ +export async function enqueueLocationUpdate( + data: Omit, +): Promise { + const full: LocationUpdateJobData = { type: "LOCATION_UPDATE", ...data }; + await getSnapshotQueue().add("snapshot", full, { + jobId: `loc:${data.sessionId}:${data.recordedAt}`, + }); +} + +export async function enqueueExpenseCreated( + data: Omit, +): Promise { + const full: ExpenseCreatedJobData = { type: "EXPENSE_CREATED", ...data }; + await getSnapshotQueue().add("snapshot", full, { + jobId: `exp-created:${data.expenseId}`, + }); +} + +export async function enqueueExpenseResolved( + data: Omit & { + resolution: "EXPENSE_APPROVED" | "EXPENSE_REJECTED"; + }, +): Promise { + const full: ExpenseResolvedJobData = { + type: data.resolution, + employeeId: data.employeeId, + organizationId: data.organizationId, + expenseId: data.expenseId, + amount: data.amount, + }; + await getSnapshotQueue().add("snapshot", full, { + jobId: `exp-resolved:${data.expenseId}`, + }); +} diff --git a/apps/api/src/workers/snapshot.worker.ts b/apps/api/src/workers/snapshot.worker.ts new file mode 100644 index 0000000..5e7d949 --- /dev/null +++ b/apps/api/src/workers/snapshot.worker.ts @@ -0,0 +1,406 @@ +/** + * snapshot.worker.ts — feat-1 snapshot table maintenance worker. + * + * Processes events from the "snapshot-engine" BullMQ queue and keeps the + * four denormalised snapshot tables up to date: + * + * employee_last_state ← CHECK_IN, CHECK_OUT, LOCATION_UPDATE + * active_users ← CHECK_IN, CHECK_OUT + * employee_metrics_snapshot← CHECK_IN, CHECK_OUT, EXPENSE_APPROVED + * pending_expenses ← EXPENSE_CREATED, EXPENSE_APPROVED, EXPENSE_REJECTED + * + * IDEMPOTENCY STRATEGY + * ──────────────────── + * All DB writes use UPSERT (ON CONFLICT DO UPDATE) or conditional DELETEs: + * + * • CHECK_IN / CHECK_OUT state fields → UPSERT via SET (not +=) + * • employee_metrics_snapshot totals → full recompute from + * employee_daily_metrics + expenses + * (SET, not increment) + * • pending_expenses insert → ON CONFLICT DO NOTHING + * • pending_expenses delete → DELETE WHERE id = ? (safe if row absent) + * • active_users insert → ON CONFLICT DO UPDATE (last_seen_at) + * • active_users delete → DELETE WHERE employee_id = ? + * + * Every job carries a deterministic jobId so BullMQ deduplicates at enqueue. + * The worker is safe to retry any number of times. + */ + +import { Worker } from "bullmq"; +import type { Job } from "bullmq"; +import type { FastifyInstance } from "fastify"; +import { redisConnectionOptions } from "../config/redis.js"; +import { supabaseServiceClient as supabase } from "../config/supabase.js"; +import type { SnapshotJobData } from "./snapshot.queue.js"; +import { moveSnapshotToDeadLetter } from "./snapshot.queue.js"; + +// ─── Guard ──────────────────────────────────────────────────────────────────── + +let workerStarted = false; + +// ─── Slow job threshold ─────────────────────────────────────────────────────── + +const SLOW_JOB_MS = 200; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +/** + * Recompute and UPSERT employee_metrics_snapshot from employee_daily_metrics + * (sessions, distance, hours) and the expenses table (total approved amount). + * + * Full-recompute strategy: reads aggregate values and sets them — never + * increments — so the result is deterministic regardless of retry count. + * + * Only errors that truly block the snapshot update are re-thrown. + * If the daily_metrics aggregate is missing (worker ran before analytics job) + * we still write whatever is available; the next CHECK_OUT will fix it. + */ +async function recomputeMetricsSnapshot( + employeeId: string, + organizationId: string, + app: FastifyInstance, +): Promise { + const [metricsResult, expensesResult] = await Promise.all([ + supabase + .from("employee_daily_metrics") + .select("sessions, distance_km, duration_seconds") + .eq("employee_id", employeeId) + .eq("organization_id", organizationId), + + supabase + .from("expenses") + .select("amount") + .eq("employee_id", employeeId) + .eq("organization_id", organizationId) + .eq("status", "APPROVED"), + ]); + + if (metricsResult.error) { + app.log.warn( + { employeeId, error: metricsResult.error.message }, + "snapshot-worker: failed to fetch daily_metrics for snapshot recompute", + ); + // Don't throw — partial update is better than no update. + } + if (expensesResult.error) { + app.log.warn( + { employeeId, error: expensesResult.error.message }, + "snapshot-worker: failed to fetch approved expenses for snapshot recompute", + ); + } + + const metrics = (metricsResult.data ?? []) as Array<{ + sessions: number; + distance_km: number; + duration_seconds: number; + }>; + + let totalSessions = 0; + let totalDurationSeconds = 0; + let totalDistanceKm = 0; + for (const row of metrics) { + totalSessions += row.sessions ?? 0; + totalDurationSeconds += row.duration_seconds ?? 0; + totalDistanceKm += row.distance_km ?? 0; + } + + const approvedExpenses = (expensesResult.data ?? []) as Array<{ amount: number }>; + const totalExpenses = approvedExpenses.reduce((sum, e) => sum + Number(e.amount), 0); + + const { error: upsertErr } = await supabase + .from("employee_metrics_snapshot") + .upsert( + { + employee_id: employeeId, + organization_id: organizationId, + total_sessions: totalSessions, + total_hours: Math.round((totalDurationSeconds / 3600) * 100) / 100, + total_distance: Math.round(totalDistanceKm * 10_000) / 10_000, + total_expenses: Math.round(totalExpenses * 100) / 100, + last_active_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }, + { onConflict: "employee_id" }, + ); + + if (upsertErr) { + throw new Error( + `snapshot-worker: failed to upsert employee_metrics_snapshot: ${upsertErr.message}`, + ); + } +} + +// ─── Job Handlers ───────────────────────────────────────────────────────────── + +async function handleCheckIn( + data: Extract, + app: FastifyInstance, +): Promise { + // 1. Upsert employee_last_state: is_checked_in = true + const { error: stateErr } = await supabase + .from("employee_last_state") + .upsert( + { + employee_id: data.employeeId, + organization_id: data.organizationId, + last_session_id: data.sessionId, + is_checked_in: true, + last_check_in_at: data.checkinAt, + updated_at: new Date().toISOString(), + }, + { onConflict: "employee_id" }, + ); + + if (stateErr) { + throw new Error(`snapshot-worker [CHECK_IN] state upsert failed: ${stateErr.message}`); + } + + // 2. Insert into active_users (upsert: re-check_in replaces stale session) + const { error: activeErr } = await supabase + .from("active_users") + .upsert( + { + employee_id: data.employeeId, + organization_id: data.organizationId, + session_id: data.sessionId, + last_seen_at: new Date().toISOString(), + }, + { onConflict: "employee_id" }, + ); + + if (activeErr) { + throw new Error(`snapshot-worker [CHECK_IN] active_users upsert failed: ${activeErr.message}`); + } + + // 3. Recompute cumulative metrics (total_sessions will now reflect the new session + // once the analytics worker has processed the daily_metrics row). + // We still call it here for last_active_at update; total_sessions will be + // accurate once analytics is done (within ~15 s after checkout). + await recomputeMetricsSnapshot(data.employeeId, data.organizationId, app); +} + +async function handleCheckOut( + data: Extract, + app: FastifyInstance, +): Promise { + // 1. Update employee_last_state: is_checked_in = false + const { error: stateErr } = await supabase + .from("employee_last_state") + .upsert( + { + employee_id: data.employeeId, + organization_id: data.organizationId, + last_session_id: data.sessionId, + is_checked_in: false, + last_check_out_at: data.checkoutAt, + updated_at: new Date().toISOString(), + }, + { onConflict: "employee_id" }, + ); + + if (stateErr) { + throw new Error(`snapshot-worker [CHECK_OUT] state upsert failed: ${stateErr.message}`); + } + + // 2. Remove from active_users + const { error: deleteErr } = await supabase + .from("active_users") + .delete() + .eq("employee_id", data.employeeId) + .eq("session_id", data.sessionId); + + if (deleteErr) { + // Non-fatal: row may already be gone from a previous attempt + app.log.warn( + { employeeId: data.employeeId, sessionId: data.sessionId, error: deleteErr.message }, + "snapshot-worker [CHECK_OUT]: active_users delete error (non-fatal)", + ); + } + + // 3. Recompute cumulative metrics. + // The analytics worker (10 s delay) will have updated employee_daily_metrics + // before this job typically runs. If not, BullMQ retries with backoff, + // ensuring eventually-consistent totals. + await recomputeMetricsSnapshot(data.employeeId, data.organizationId, app); +} + +async function handleLocationUpdate( + data: Extract, + app: FastifyInstance, +): Promise { + // 1. Update employee_last_state with latest GPS fix + const { error: stateErr } = await supabase + .from("employee_last_state") + .upsert( + { + employee_id: data.employeeId, + organization_id: data.organizationId, + last_latitude: data.latitude, + last_longitude: data.longitude, + last_location_at: data.recordedAt, + updated_at: new Date().toISOString(), + }, + { onConflict: "employee_id" }, + ); + + if (stateErr) { + throw new Error( + `snapshot-worker [LOCATION_UPDATE] state upsert failed: ${stateErr.message}`, + ); + } + + // 2. Update last_seen_at in active_users (heartbeat) + const { error: activeErr } = await supabase + .from("active_users") + .update({ last_seen_at: data.recordedAt }) + .eq("employee_id", data.employeeId) + .eq("session_id", data.sessionId); + + if (activeErr) { + // Non-fatal: employee may have checked out between location write and this job. + app.log.warn( + { employeeId: data.employeeId, sessionId: data.sessionId, error: activeErr.message }, + "snapshot-worker [LOCATION_UPDATE]: active_users heartbeat error (non-fatal)", + ); + } +} + +async function handleExpenseCreated( + data: Extract, + _app: FastifyInstance, +): Promise { + // Insert row into pending_expenses. ON CONFLICT DO NOTHING makes this safe + // to retry if the row already exists from a previous attempt. + const { error } = await supabase + .from("pending_expenses") + .upsert( + { + id: data.expenseId, + organization_id: data.organizationId, + employee_id: data.employeeId, + amount: data.amount, + submitted_at: data.submittedAt, + }, + { onConflict: "id", ignoreDuplicates: true }, + ); + + if (error) { + throw new Error(`snapshot-worker [EXPENSE_CREATED] insert failed: ${error.message}`); + } +} + +async function handleExpenseResolved( + data: Extract, + app: FastifyInstance, +): Promise { + // 1. Remove from pending_expenses (safe if already gone) + const { error: deleteErr } = await supabase + .from("pending_expenses") + .delete() + .eq("id", data.expenseId); + + if (deleteErr) { + // Row may already be absent from a previous attempt — log and continue. + app.log.warn( + { expenseId: data.expenseId, error: deleteErr.message }, + "snapshot-worker [EXPENSE_RESOLVED]: pending_expenses delete error (non-fatal)", + ); + } + + // 2. Recompute cumulative metrics only on approval (total_expenses is + // sum of approved amounts; rejections don't change it). + if (data.type === "EXPENSE_APPROVED") { + await recomputeMetricsSnapshot(data.employeeId, data.organizationId, app); + } +} + +// ─── Worker Factory ─────────────────────────────────────────────────────────── + +export function startSnapshotWorker(app: FastifyInstance): void { + if (workerStarted) { + app.log.warn("snapshot-worker already started — skipping duplicate start"); + return; + } + + const worker = new Worker( + "snapshot-engine", + async (job: Job) => { + const t0 = Date.now(); + const { data } = job; + + switch (data.type) { + case "CHECK_IN": + await handleCheckIn(data, app); + break; + case "CHECK_OUT": + await handleCheckOut(data, app); + break; + case "LOCATION_UPDATE": + await handleLocationUpdate(data, app); + break; + case "EXPENSE_CREATED": + await handleExpenseCreated(data, app); + break; + case "EXPENSE_APPROVED": + case "EXPENSE_REJECTED": + await handleExpenseResolved(data, app); + break; + default: { + // Exhaustive check — TypeScript will error if a new type is added + // without a corresponding case. + const _exhaustive: never = data; + app.log.error( + { type: (_exhaustive as SnapshotJobData).type }, + "snapshot-worker: unknown job type", + ); + } + } + + const durationMs = Date.now() - t0; + app.log.info( + { + jobId: job.id, + type: data.type, + durationMs, + slow: durationMs > SLOW_JOB_MS, + }, + "snapshot-worker: job complete", + ); + }, + { + connection: redisConnectionOptions, + concurrency: 5, + }, + ); + + worker.on("failed", async (job, err) => { + if (!job) return; + const isExhausted = (job.attemptsMade ?? 0) >= (job.opts?.attempts ?? 5); + app.log.error( + { + jobId: job.id, + type: job.data?.type, + attemptsMade: job.attemptsMade, + exhausted: isExhausted, + error: err.message, + }, + "snapshot-worker: job failed", + ); + + if (isExhausted) { + await moveSnapshotToDeadLetter(job.data, err.message).catch((dlqErr: unknown) => { + app.log.error( + { jobId: job.id, dlqError: String(dlqErr) }, + "snapshot-worker: failed to move job to DLQ", + ); + }); + } + }); + + worker.on("error", (err) => { + app.log.error({ error: err.message }, "snapshot-worker: worker-level error"); + }); + + workerStarted = true; + app.log.info("snapshot-worker started (queue: snapshot-engine, concurrency: 5)"); +} diff --git a/apps/api/src/workers/startup.ts b/apps/api/src/workers/startup.ts index aa17096..413351f 100644 --- a/apps/api/src/workers/startup.ts +++ b/apps/api/src/workers/startup.ts @@ -8,7 +8,7 @@ import { env } from "../config/env.js"; * Adding a new worker here automatically propagates the expected count to * /ready, /admin/system-health, and all boot logs — no manual number updates. */ -export const WORKER_TYPES = ["distance", "analytics", "webhook"] as const; +export const WORKER_TYPES = ["distance", "analytics", "webhook", "snapshot"] as const; export type WorkerType = (typeof WORKER_TYPES)[number]; /** Expected number of background workers in a fully-started process. */ @@ -65,14 +65,17 @@ export async function startWorkers(app: FastifyInstance): Promise { { startDistanceWorker }, { startAnalyticsWorker }, { startWebhookWorker }, + { startSnapshotWorker }, ] = await Promise.all([ import("./distance.worker.js"), import("./analytics.worker.js"), import("./webhook.worker.js"), + import("./snapshot.worker.js"), ]); startDistanceWorker(app); startAnalyticsWorker(app); startWebhookWorker(app); + startSnapshotWorker(app); workersStarted = true; } diff --git a/supabase/migrations/20260329000200_feat1_rollback.sql b/supabase/migrations/20260329000200_feat1_rollback.sql new file mode 100644 index 0000000..1c29da4 --- /dev/null +++ b/supabase/migrations/20260329000200_feat1_rollback.sql @@ -0,0 +1,89 @@ +-- ============================================================ +-- feat-1 ROLLBACK — Snapshot Tables +-- ============================================================ +-- Run this ONLY to undo migration 20260329000200_feat1_snapshot_tables.sql. +-- This script is destructive: it drops the four snapshot tables and all +-- associated indexes, triggers, policies, and functions. +-- +-- Pre-conditions before running: +-- 1. Deploy the previous API version (revert the git commit that +-- contains the snapshot.worker.ts and service changes). +-- 2. Confirm no live traffic is reading from the snapshot tables. +-- 3. Run SELECT COUNT(*) on each table to confirm row counts for audit. +-- +-- Post-rollback: the API will return to the original query-time computation +-- paths (employee_daily_metrics aggregation, expenses table full scans). +-- +-- ⚠ This script is idempotent (uses IF EXISTS) and is safe to run +-- multiple times. +-- ============================================================ + +-- ── Drop back-fill function ───────────────────────────────── +DROP FUNCTION IF EXISTS public.backfill_feat1_snapshots(); + +-- ── Drop triggers ─────────────────────────────────────────── +DROP TRIGGER IF EXISTS trg_updated_at_employee_last_state + ON public.employee_last_state; + +DROP TRIGGER IF EXISTS trg_updated_at_employee_metrics_snapshot + ON public.employee_metrics_snapshot; + +-- ── Drop RLS policies ─────────────────────────────────────── + +-- employee_last_state +DROP POLICY IF EXISTS els2_service_role ON public.employee_last_state; +DROP POLICY IF EXISTS els2_admin_all ON public.employee_last_state; +DROP POLICY IF EXISTS els2_employee_self ON public.employee_last_state; + +-- pending_expenses +DROP POLICY IF EXISTS pending_exp_service_role ON public.pending_expenses; +DROP POLICY IF EXISTS pending_exp_admin_all ON public.pending_expenses; +DROP POLICY IF EXISTS pending_exp_employee_self ON public.pending_expenses; + +-- employee_metrics_snapshot +DROP POLICY IF EXISTS ems_service_role ON public.employee_metrics_snapshot; +DROP POLICY IF EXISTS ems_admin_all ON public.employee_metrics_snapshot; +DROP POLICY IF EXISTS ems_employee_self ON public.employee_metrics_snapshot; + +-- active_users +DROP POLICY IF EXISTS active_users_service_role ON public.active_users; +DROP POLICY IF EXISTS active_users_admin_all ON public.active_users; +DROP POLICY IF EXISTS active_users_employee_self ON public.active_users; + +-- ── Drop indexes ──────────────────────────────────────────── +DROP INDEX IF EXISTS public.idx_els_org; +DROP INDEX IF EXISTS public.idx_els_org_checked_in; +DROP INDEX IF EXISTS public.idx_pending_org; +DROP INDEX IF EXISTS public.idx_pending_org_submitted; +DROP INDEX IF EXISTS public.idx_pending_emp; +DROP INDEX IF EXISTS public.idx_metrics_org; +DROP INDEX IF EXISTS public.idx_active_org; + +-- ── Drop tables (CASCADE removes dependent triggers/policies) +DROP TABLE IF EXISTS public.active_users CASCADE; +DROP TABLE IF EXISTS public.pending_expenses CASCADE; +DROP TABLE IF EXISTS public.employee_metrics_snapshot CASCADE; +DROP TABLE IF EXISTS public.employee_last_state CASCADE; + +-- ============================================================ +-- Application rollback (alongside this SQL): +-- +-- git revert +-- # or +-- git checkout -- apps/api/src/workers/snapshot.queue.ts +-- git checkout -- apps/api/src/workers/snapshot.worker.ts +-- git checkout -- apps/api/src/workers/startup.ts +-- git checkout -- apps/api/src/modules/attendance/attendance.service.ts +-- git checkout -- apps/api/src/modules/locations/locations.service.ts +-- git checkout -- apps/api/src/modules/expenses/expenses.service.ts +-- git checkout -- apps/api/src/modules/expenses/expenses.controller.ts +-- git checkout -- apps/api/src/modules/expenses/expenses.repository.ts +-- git checkout -- apps/api/src/modules/employees/employees.repository.ts +-- git checkout -- apps/api/src/modules/employees/employees.controller.ts +-- git checkout -- apps/api/src/modules/profile/profile.repository.ts +-- git checkout -- apps/api/src/modules/profile/profile.service.ts +-- +-- The BullMQ "snapshot-engine" queue in Redis will drain naturally. +-- Any jobs still in the queue when workers are stopped can be safely +-- discarded — they only update derived tables, not source tables. +-- ============================================================ diff --git a/supabase/migrations/20260329000200_feat1_snapshot_tables.sql b/supabase/migrations/20260329000200_feat1_snapshot_tables.sql new file mode 100644 index 0000000..4a3b695 --- /dev/null +++ b/supabase/migrations/20260329000200_feat1_snapshot_tables.sql @@ -0,0 +1,392 @@ +-- ============================================================ +-- feat-1: Snapshot Tables for Read-Optimised Architecture +-- ============================================================ +-- Goal: Move all dashboard reads from query-time computation to +-- precomputed snapshot tables so every API returns in <50 ms. +-- +-- New tables: +-- 1. employee_last_state — real-time check-in state per employee +-- 2. pending_expenses — denormalised view of PENDING expenses +-- 3. employee_metrics_snapshot — cumulative per-employee totals +-- 4. active_users — currently checked-in employees +-- +-- All tables have: +-- • RLS enabled +-- • service_role bypass (workers write via service role key) +-- • admin_all policy (ADMIN reads all rows in their org) +-- • employee_self policy (EMPLOYEE reads only their own row) +-- • updated_at trigger +-- +-- Idempotent: every DDL statement uses IF NOT EXISTS / OR REPLACE. +-- ============================================================ + +-- ══════════════════════════════════════════════════════════════ +-- 1. employee_last_state +-- ══════════════════════════════════════════════════════════════ +-- Tracks the most-recent real-time state for each field employee. +-- Written by the snapshot worker on CHECK_IN, CHECK_OUT, and +-- LOCATION_UPDATE events. Replaces scatter-gather joins on +-- attendance_sessions + gps_locations for live employee views. + +CREATE TABLE IF NOT EXISTS public.employee_last_state ( + employee_id UUID PRIMARY KEY + REFERENCES public.employees(id) ON DELETE CASCADE, + organization_id UUID NOT NULL + REFERENCES public.organizations(id) ON DELETE CASCADE, + + -- Session linkage + last_session_id UUID REFERENCES public.attendance_sessions(id) ON DELETE SET NULL, + is_checked_in BOOLEAN NOT NULL DEFAULT false, + + -- Real-time location (NULL until first GPS report) + last_latitude DOUBLE PRECISION, + last_longitude DOUBLE PRECISION, + last_location_at TIMESTAMPTZ, + + -- Attendance timestamps + last_check_in_at TIMESTAMPTZ, + last_check_out_at TIMESTAMPTZ, + + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +COMMENT ON TABLE public.employee_last_state IS + 'feat-1 snapshot: one row per employee, updated on every attendance/location event. + Powers the live employee list and map without scanning attendance_sessions.'; + +CREATE INDEX IF NOT EXISTS idx_els_org + ON public.employee_last_state (organization_id); + +CREATE INDEX IF NOT EXISTS idx_els_org_checked_in + ON public.employee_last_state (organization_id) + WHERE is_checked_in = true; + +-- updated_at auto-stamp +CREATE TRIGGER trg_updated_at_employee_last_state + BEFORE UPDATE ON public.employee_last_state + FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); + +-- ══════════════════════════════════════════════════════════════ +-- 2. pending_expenses +-- ══════════════════════════════════════════════════════════════ +-- Denormalised projection of expenses WHERE status = 'PENDING'. +-- Inserted by the snapshot worker on EXPENSE_CREATED. +-- Deleted by the snapshot worker on EXPENSE_APPROVED / EXPENSE_REJECTED. +-- The primary key mirrors expenses.id for direct lookups. + +CREATE TABLE IF NOT EXISTS public.pending_expenses ( + id UUID PRIMARY KEY + REFERENCES public.expenses(id) ON DELETE CASCADE, + organization_id UUID NOT NULL + REFERENCES public.organizations(id) ON DELETE CASCADE, + employee_id UUID NOT NULL + REFERENCES public.employees(id) ON DELETE CASCADE, + + amount NUMERIC NOT NULL, + category TEXT, -- future: expense category field + submitted_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +COMMENT ON TABLE public.pending_expenses IS + 'feat-1 snapshot: mirror of expenses WHERE status=PENDING. + Enables O(1) admin pending-expense queries without scanning the full expenses table.'; + +CREATE INDEX IF NOT EXISTS idx_pending_org + ON public.pending_expenses (organization_id); + +CREATE INDEX IF NOT EXISTS idx_pending_org_submitted + ON public.pending_expenses (organization_id, submitted_at DESC); + +CREATE INDEX IF NOT EXISTS idx_pending_emp + ON public.pending_expenses (organization_id, employee_id); + +-- ══════════════════════════════════════════════════════════════ +-- 3. employee_metrics_snapshot +-- ══════════════════════════════════════════════════════════════ +-- Cumulative (all-time) totals per employee. Updated by the snapshot +-- worker after checkout and expense approval by recomputing from +-- employee_daily_metrics + expenses — fully idempotent via SET not +=. + +CREATE TABLE IF NOT EXISTS public.employee_metrics_snapshot ( + employee_id UUID PRIMARY KEY + REFERENCES public.employees(id) ON DELETE CASCADE, + organization_id UUID NOT NULL + REFERENCES public.organizations(id) ON DELETE CASCADE, + + total_sessions INT NOT NULL DEFAULT 0, + total_hours NUMERIC(12,2) NOT NULL DEFAULT 0, + total_distance NUMERIC(12,4) NOT NULL DEFAULT 0, + total_expenses NUMERIC(12,2) NOT NULL DEFAULT 0, -- sum of APPROVED expense amounts + + last_active_at TIMESTAMPTZ, + + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +COMMENT ON TABLE public.employee_metrics_snapshot IS + 'feat-1 snapshot: cumulative all-time metrics per employee. + Powers GET /profile/me with a single PK lookup instead of full daily_metrics scan. + Recomputed idempotently by the snapshot worker (full SET strategy).'; + +CREATE INDEX IF NOT EXISTS idx_metrics_org + ON public.employee_metrics_snapshot (organization_id); + +-- updated_at auto-stamp +CREATE TRIGGER trg_updated_at_employee_metrics_snapshot + BEFORE UPDATE ON public.employee_metrics_snapshot + FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); + +-- ══════════════════════════════════════════════════════════════ +-- 4. active_users +-- ══════════════════════════════════════════════════════════════ +-- One row per currently checked-in employee. Inserted on CHECK_IN, +-- deleted on CHECK_OUT. Enables O(employees_checked_in) active-user +-- queries rather than O(all_sessions). + +CREATE TABLE IF NOT EXISTS public.active_users ( + employee_id UUID PRIMARY KEY + REFERENCES public.employees(id) ON DELETE CASCADE, + organization_id UUID NOT NULL + REFERENCES public.organizations(id) ON DELETE CASCADE, + + session_id UUID REFERENCES public.attendance_sessions(id) ON DELETE CASCADE, + last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +COMMENT ON TABLE public.active_users IS + 'feat-1 snapshot: one row per currently checked-in employee. + Enables sub-10ms "how many people are in the field right now?" queries.'; + +CREATE INDEX IF NOT EXISTS idx_active_org + ON public.active_users (organization_id); + +-- ══════════════════════════════════════════════════════════════ +-- RLS — PHASE 2 +-- ══════════════════════════════════════════════════════════════ +-- +-- Pattern (same as the rest of the codebase, see baseline_schema.sql): +-- service_role bypass — unrestricted (workers write via service role key) +-- admin_all policy — ADMIN can SELECT on all rows in their org +-- employee_self policy — EMPLOYEE can SELECT only their own row +-- No client writes — snapshot tables are maintained exclusively by workers + +-- ── employee_last_state ────────────────────────────────────── + +ALTER TABLE public.employee_last_state ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "els2_service_role" + ON public.employee_last_state + FOR ALL TO service_role + USING (true) WITH CHECK (true); + +CREATE POLICY "els2_admin_all" + ON public.employee_last_state + FOR SELECT TO authenticated + USING ( + (SELECT organization_id = employee_last_state.organization_id + AND role = 'ADMIN' + FROM public.users + WHERE id = auth.uid()) + ); + +CREATE POLICY "els2_employee_self" + ON public.employee_last_state + FOR SELECT TO authenticated + USING ( + employee_id = (SELECT id FROM public.employees WHERE user_id = auth.uid()) + ); + +-- ── pending_expenses ───────────────────────────────────────── + +ALTER TABLE public.pending_expenses ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "pending_exp_service_role" + ON public.pending_expenses + FOR ALL TO service_role + USING (true) WITH CHECK (true); + +CREATE POLICY "pending_exp_admin_all" + ON public.pending_expenses + FOR SELECT TO authenticated + USING ( + (SELECT organization_id = pending_expenses.organization_id + AND role = 'ADMIN' + FROM public.users + WHERE id = auth.uid()) + ); + +CREATE POLICY "pending_exp_employee_self" + ON public.pending_expenses + FOR SELECT TO authenticated + USING ( + employee_id = (SELECT id FROM public.employees WHERE user_id = auth.uid()) + ); + +-- ── employee_metrics_snapshot ──────────────────────────────── + +ALTER TABLE public.employee_metrics_snapshot ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "ems_service_role" + ON public.employee_metrics_snapshot + FOR ALL TO service_role + USING (true) WITH CHECK (true); + +CREATE POLICY "ems_admin_all" + ON public.employee_metrics_snapshot + FOR SELECT TO authenticated + USING ( + (SELECT organization_id = employee_metrics_snapshot.organization_id + AND role = 'ADMIN' + FROM public.users + WHERE id = auth.uid()) + ); + +CREATE POLICY "ems_employee_self" + ON public.employee_metrics_snapshot + FOR SELECT TO authenticated + USING ( + employee_id = (SELECT id FROM public.employees WHERE user_id = auth.uid()) + ); + +-- ── active_users ───────────────────────────────────────────── + +ALTER TABLE public.active_users ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "active_users_service_role" + ON public.active_users + FOR ALL TO service_role + USING (true) WITH CHECK (true); + +CREATE POLICY "active_users_admin_all" + ON public.active_users + FOR SELECT TO authenticated + USING ( + (SELECT organization_id = active_users.organization_id + AND role = 'ADMIN' + FROM public.users + WHERE id = auth.uid()) + ); + +CREATE POLICY "active_users_employee_self" + ON public.active_users + FOR SELECT TO authenticated + USING ( + employee_id = (SELECT id FROM public.employees WHERE user_id = auth.uid()) + ); + +-- ══════════════════════════════════════════════════════════════ +-- BACK-FILL FUNCTION (run once after migration) +-- ══════════════════════════════════════════════════════════════ +-- Seed the snapshot tables from the existing live data so the +-- very first API requests after deploying this migration hit +-- populated snapshots rather than empty tables. +-- +-- Safe to run multiple times (idempotent UPSERTs). + +CREATE OR REPLACE FUNCTION public.backfill_feat1_snapshots() +RETURNS void +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + + -- ── 1. employee_last_state ────────────────────────────────── + -- Seed from the most-recent attendance_session per employee. + INSERT INTO public.employee_last_state ( + employee_id, organization_id, + last_session_id, is_checked_in, + last_check_in_at, last_check_out_at, + updated_at + ) + SELECT DISTINCT ON (s.employee_id) + s.employee_id, + s.organization_id, + s.id AS last_session_id, + (s.checkout_at IS NULL) AS is_checked_in, + s.checkin_at AS last_check_in_at, + s.checkout_at AS last_check_out_at, + now() + FROM public.attendance_sessions s + ORDER BY s.employee_id, s.checkin_at DESC + ON CONFLICT (employee_id) DO UPDATE SET + last_session_id = EXCLUDED.last_session_id, + is_checked_in = EXCLUDED.is_checked_in, + last_check_in_at = EXCLUDED.last_check_in_at, + last_check_out_at = EXCLUDED.last_check_out_at, + updated_at = now(); + + -- ── 2. active_users ───────────────────────────────────────── + INSERT INTO public.active_users (employee_id, organization_id, session_id, last_seen_at) + SELECT s.employee_id, s.organization_id, s.id, now() + FROM public.attendance_sessions s + WHERE s.checkout_at IS NULL + ON CONFLICT (employee_id) DO UPDATE SET + session_id = EXCLUDED.session_id, + last_seen_at = now(); + + -- ── 3. pending_expenses ───────────────────────────────────── + INSERT INTO public.pending_expenses (id, organization_id, employee_id, amount, submitted_at) + SELECT id, organization_id, employee_id, amount, submitted_at + FROM public.expenses + WHERE status = 'PENDING' + ON CONFLICT (id) DO NOTHING; + + -- ── 4. employee_metrics_snapshot ──────────────────────────── + -- Aggregate from employee_daily_metrics (already computed by analytics worker). + -- total_expenses = sum of APPROVED expense amounts. + INSERT INTO public.employee_metrics_snapshot ( + employee_id, organization_id, + total_sessions, total_hours, total_distance, total_expenses, + last_active_at, updated_at + ) + SELECT + e.id AS employee_id, + e.organization_id, + COALESCE(m.total_sessions, 0) AS total_sessions, + ROUND((COALESCE(m.total_duration_seconds, 0) / 3600.0)::numeric, 2) + AS total_hours, + ROUND(COALESCE(m.total_distance_km, 0)::numeric, 4) AS total_distance, + ROUND(COALESCE(ex.total_approved, 0)::numeric, 2) AS total_expenses, + e.last_activity_at AS last_active_at, + now() + FROM public.employees e + LEFT JOIN ( + SELECT employee_id, + SUM(sessions) AS total_sessions, + SUM(duration_seconds) AS total_duration_seconds, + SUM(distance_km) AS total_distance_km + FROM public.employee_daily_metrics + GROUP BY employee_id + ) m ON m.employee_id = e.id + LEFT JOIN ( + SELECT employee_id, SUM(amount) AS total_approved + FROM public.expenses + WHERE status = 'APPROVED' + GROUP BY employee_id + ) ex ON ex.employee_id = e.id + ON CONFLICT (employee_id) DO UPDATE SET + total_sessions = EXCLUDED.total_sessions, + total_hours = EXCLUDED.total_hours, + total_distance = EXCLUDED.total_distance, + total_expenses = EXCLUDED.total_expenses, + last_active_at = EXCLUDED.last_active_at, + updated_at = now(); + +END; +$$; + +COMMENT ON FUNCTION public.backfill_feat1_snapshots() IS + 'feat-1: Seed snapshot tables from live data. Idempotent — safe to run multiple times. + Call once after applying this migration: SELECT public.backfill_feat1_snapshots();'; + +-- Run the back-fill immediately as part of this migration. +-- On a fresh DB with no data this is a no-op. +-- On a live DB this seeds the snapshots from historical data. +SELECT public.backfill_feat1_snapshots(); + +-- ══════════════════════════════════════════════════════════════ +-- GRANTS +-- ══════════════════════════════════════════════════════════════ + +GRANT EXECUTE ON FUNCTION public.backfill_feat1_snapshots() TO service_role; From ed52371928c893150b80a798d074214249f0d3a9 Mon Sep 17 00:00:00 2001 From: rajashish147 Date: Thu, 2 Apr 2026 23:49:12 +0530 Subject: [PATCH 2/9] chore: codebase audit cleanup and env contract hardening - Delete dead code: dedup.ts (never imported), errors.txt dump - Delete dead config: .env.monitoring.ci (never used in CI), apps/web/.env.example - Delete stale runbooks: cert-renewal-failure, container-crash, deploy-failure, monitoring-failure - Fix deploy.yml: fieldtrack.conf -> api.conf (critical nginx sync bug) - Add env.ts guard comment block (schema change checklist for all 4 sync targets) - Add process.env contract guard to pr.yml and deploy.yml (CI gate) - Add chmod 600 for .env.monitoring in deploy-bluegreen.sh (defense-in-depth) - Update .gitignore: remove stale .env.monitoring.ci whitelist entry - Clarify SUPABASE_JWT_SECRET as test-only in .env.example - Fix env.ts: APP_ENV valid values comment, superRefine rule numbering - All 501 tests pass (270 unit + 231 integration), tsc --noEmit clean --- .dockerignore | 6 +- .github/pull_request_template.md | 2 - .github/workflows/codeql.yml | 4 - .github/workflows/deploy.yml | 131 +- .github/workflows/pr.yml | 86 +- .gitignore | 10 +- CHANGELOG.md | 4 +- CONTRIBUTING.md | 8 +- README.md | 11 +- apps/api/.env.ci | 6 +- apps/api/.env.example | 16 +- apps/api/Dockerfile | 2 +- apps/api/README.md | 4 +- .../api/docs/runbooks/cert-renewal-failure.md | 314 - apps/api/docs/runbooks/container-crash.md | 170 - apps/api/docs/runbooks/deploy-failure.md | 102 - apps/api/docs/runbooks/monitoring-failure.md | 262 - apps/api/errors.txt | 52 - apps/api/healthcheck.js | 2 +- apps/api/package.json | 2 +- apps/api/scripts/deploy-bluegreen.sh | 54 +- apps/api/scripts/rollback.sh | 10 +- apps/api/scripts/validate-env.sh | 12 +- apps/api/scripts/verify-stabilization.sh | 8 +- apps/api/scripts/vps-setup.sh | 30 +- apps/api/src/config/env.ts | 69 +- apps/api/src/tracing.ts | 2 +- apps/api/src/utils/dedup.ts | 39 - .../tests/integration/profile/profile.test.ts | 1 + .../security/auth-validation.test.ts | 1 + .../security/tenant-isolation.test.ts | 1 + .../tests/unit/utils/config-hardening.test.ts | 107 + apps/web/.env.example | 47 - apps/web/.eslintrc.json | 35 - apps/web/README.md | 123 - apps/web/STRUCTURE.md | 205 - apps/web/components.json | 20 - apps/web/next.config.mjs | 115 - apps/web/package.json | 67 - apps/web/postcss.config.mjs | 9 - apps/web/public/logo/logo.png | Bin 439376 -> 0 bytes apps/web/src/app/(auth)/layout.tsx | 7 - apps/web/src/app/(auth)/login/page.tsx | 133 - .../app/(protected)/admin/analytics/page.tsx | 425 - .../admin/employees/[id]/profile/page.tsx | 54 - .../app/(protected)/admin/employees/page.tsx | 276 - .../app/(protected)/admin/expenses/page.tsx | 373 - .../admin/monitoring/map/EmployeeMap.tsx | 225 - .../(protected)/admin/monitoring/map/page.tsx | 268 - .../app/(protected)/admin/monitoring/page.tsx | 198 - .../src/app/(protected)/admin/queues/page.tsx | 82 - .../admin/sessions/[id]/locations/page.tsx | 107 - .../app/(protected)/admin/sessions/page.tsx | 416 - .../app/(protected)/admin/webhooks/page.tsx | 819 - .../src/app/(protected)/dashboard/page.tsx | 879 - apps/web/src/app/(protected)/error.tsx | 49 - .../web/src/app/(protected)/expenses/page.tsx | 122 - apps/web/src/app/(protected)/layout.tsx | 34 - .../src/app/(protected)/leaderboard/page.tsx | 146 - apps/web/src/app/(protected)/profile/page.tsx | 68 - .../app/(protected)/sessions/[id]/page.tsx | 122 - .../web/src/app/(protected)/sessions/page.tsx | 74 - apps/web/src/app/error.tsx | 37 - apps/web/src/app/globals.css | 234 - apps/web/src/app/layout.tsx | 31 - apps/web/src/app/page.tsx | 27 - apps/web/src/app/providers.tsx | 66 - apps/web/src/app/webhook-test/page.tsx | 96 - apps/web/src/components/ActivityBadge.tsx | 59 - apps/web/src/components/EmployeeIdentity.tsx | 267 - apps/web/src/components/EmptyState.tsx | 45 - apps/web/src/components/ErrorBanner.tsx | 48 - apps/web/src/components/LoadingSkeleton.tsx | 42 - apps/web/src/components/MetricCard.tsx | 114 - apps/web/src/components/ProfileView.tsx | 134 - .../components/charts/LeaderboardTable.tsx | 170 - .../components/charts/SessionTrendChart.tsx | 116 - .../src/components/charts/SummaryCards.tsx | 72 - .../components/charts/TopPerformersChart.tsx | 79 - apps/web/src/components/layout/AppLayout.tsx | 30 - apps/web/src/components/layout/Header.tsx | 279 - apps/web/src/components/layout/Sidebar.tsx | 343 - apps/web/src/components/maps/RouteMap.tsx | 110 - apps/web/src/components/motion.tsx | 137 - .../components/providers/theme-provider.tsx | 78 - apps/web/src/components/tables/DataTable.tsx | 192 - .../src/components/tables/ExpensesTable.tsx | 142 - apps/web/src/components/tables/Pagination.tsx | 36 - .../src/components/tables/SessionsTable.tsx | 113 - apps/web/src/components/ui/alert-dialog.tsx | 127 - apps/web/src/components/ui/avatar.tsx | 46 - apps/web/src/components/ui/badge.tsx | 72 - apps/web/src/components/ui/button.tsx | 96 - apps/web/src/components/ui/card.tsx | 75 - apps/web/src/components/ui/dialog.tsx | 100 - apps/web/src/components/ui/dropdown-menu.tsx | 193 - apps/web/src/components/ui/input.tsx | 39 - apps/web/src/components/ui/label.tsx | 21 - apps/web/src/components/ui/select.tsx | 149 - apps/web/src/components/ui/separator.tsx | 25 - apps/web/src/components/ui/sheet.tsx | 124 - apps/web/src/components/ui/skeleton.tsx | 43 - apps/web/src/components/ui/table.tsx | 113 - apps/web/src/components/ui/tabs.tsx | 54 - apps/web/src/components/ui/theme-toggle.tsx | 38 - apps/web/src/components/ui/toast.tsx | 128 - apps/web/src/components/ui/toaster.tsx | 33 - apps/web/src/components/ui/use-toast.ts | 158 - .../src/components/webhook-test/AuthCard.tsx | 154 - .../components/webhook-test/DeliveryPanel.tsx | 303 - .../webhook-test/TriggerEventCard.tsx | 184 - .../webhook-test/WebhookSetupCard.tsx | 295 - apps/web/src/contexts/AuthContext.tsx | 89 - apps/web/src/hooks/queries/useAnalytics.ts | 75 - apps/web/src/hooks/queries/useDashboard.ts | 39 - apps/web/src/hooks/queries/useEmployees.ts | 83 - apps/web/src/hooks/queries/useExpenses.ts | 140 - apps/web/src/hooks/queries/useMonitoring.ts | 37 - apps/web/src/hooks/queries/useProfile.ts | 22 - apps/web/src/hooks/queries/useQueues.ts | 29 - apps/web/src/hooks/queries/useRoutes.ts | 14 - .../src/hooks/queries/useSessionLocations.ts | 27 - apps/web/src/hooks/queries/useSessions.ts | 120 - apps/web/src/hooks/queries/useWebhooks.ts | 135 - apps/web/src/hooks/useAnimatedNumber.ts | 46 - apps/web/src/hooks/useAuth.ts | 31 - apps/web/src/hooks/useServerSentEvents.ts | 78 - apps/web/src/lib/api.ts | 3 - apps/web/src/lib/api/client.ts | 318 - apps/web/src/lib/api/endpoints.ts | 73 - apps/web/src/lib/api/index.ts | 3 - apps/web/src/lib/auth/role.ts | 54 - apps/web/src/lib/dateRange.ts | 164 - apps/web/src/lib/design-system/tokens.ts | 186 - apps/web/src/lib/design-system/variants.ts | 107 - apps/web/src/lib/env.ts | 99 - apps/web/src/lib/permissions.ts | 25 - apps/web/src/lib/query-client.ts | 46 - apps/web/src/lib/supabase.ts | 20 - apps/web/src/lib/utils.ts | 53 - apps/web/src/lib/webhook-api.ts | 126 - apps/web/src/middleware.ts | 105 - apps/web/src/test/dateRange.test.ts | 80 - apps/web/src/test/permissions.test.ts | 20 - apps/web/src/test/setup.ts | 39 - apps/web/src/types/index.ts | 51 - apps/web/tailwind.config.ts | 121 - apps/web/tsconfig.json | 27 - apps/web/vitest.config.ts | 24 - docs/ARCHITECTURE.md | 2 +- docs/DEPLOYMENT.md | 25 +- docs/OBSERVABILITY_ARCHITECTURE.md | 35 +- docs/ROLLBACK_QUICKREF.md | 8 +- docs/ROLLBACK_SYSTEM.md | 16 +- docs/SLO.md | 4 +- docs/env-contract.md | 73 +- docs/walkthrough.md | 40 +- infra/.env.monitoring.ci | 39 - infra/.env.monitoring.example | 8 +- infra/alertmanager/alertmanager.yml | 70 +- infra/docker-compose.monitoring.yml | 41 +- infra/grafana/dashboards/fieldtrack.json | 26 +- infra/nginx/{fieldtrack.conf => api.conf} | 32 +- infra/prometheus/alerts.yml | 26 +- infra/prometheus/prometheus.yml | 16 +- infra/scripts/verify-alertmanager.sh | 13 +- package-lock.json | 15085 +++------------- package.json | 11 +- packages/config/src/index.ts | 61 +- packages/types/package.json | 2 +- supabase/config.toml | 2 +- test-trigger.txt | 1 - 172 files changed, 2674 insertions(+), 28364 deletions(-) delete mode 100644 apps/api/docs/runbooks/cert-renewal-failure.md delete mode 100644 apps/api/docs/runbooks/container-crash.md delete mode 100644 apps/api/docs/runbooks/deploy-failure.md delete mode 100644 apps/api/docs/runbooks/monitoring-failure.md delete mode 100644 apps/api/errors.txt delete mode 100644 apps/api/src/utils/dedup.ts delete mode 100644 apps/web/.env.example delete mode 100644 apps/web/.eslintrc.json delete mode 100644 apps/web/README.md delete mode 100644 apps/web/STRUCTURE.md delete mode 100644 apps/web/components.json delete mode 100644 apps/web/next.config.mjs delete mode 100644 apps/web/package.json delete mode 100644 apps/web/postcss.config.mjs delete mode 100644 apps/web/public/logo/logo.png delete mode 100644 apps/web/src/app/(auth)/layout.tsx delete mode 100644 apps/web/src/app/(auth)/login/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/analytics/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/employees/[id]/profile/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/employees/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/expenses/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/monitoring/map/EmployeeMap.tsx delete mode 100644 apps/web/src/app/(protected)/admin/monitoring/map/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/monitoring/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/queues/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/sessions/[id]/locations/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/sessions/page.tsx delete mode 100644 apps/web/src/app/(protected)/admin/webhooks/page.tsx delete mode 100644 apps/web/src/app/(protected)/dashboard/page.tsx delete mode 100644 apps/web/src/app/(protected)/error.tsx delete mode 100644 apps/web/src/app/(protected)/expenses/page.tsx delete mode 100644 apps/web/src/app/(protected)/layout.tsx delete mode 100644 apps/web/src/app/(protected)/leaderboard/page.tsx delete mode 100644 apps/web/src/app/(protected)/profile/page.tsx delete mode 100644 apps/web/src/app/(protected)/sessions/[id]/page.tsx delete mode 100644 apps/web/src/app/(protected)/sessions/page.tsx delete mode 100644 apps/web/src/app/error.tsx delete mode 100644 apps/web/src/app/globals.css delete mode 100644 apps/web/src/app/layout.tsx delete mode 100644 apps/web/src/app/page.tsx delete mode 100644 apps/web/src/app/providers.tsx delete mode 100644 apps/web/src/app/webhook-test/page.tsx delete mode 100644 apps/web/src/components/ActivityBadge.tsx delete mode 100644 apps/web/src/components/EmployeeIdentity.tsx delete mode 100644 apps/web/src/components/EmptyState.tsx delete mode 100644 apps/web/src/components/ErrorBanner.tsx delete mode 100644 apps/web/src/components/LoadingSkeleton.tsx delete mode 100644 apps/web/src/components/MetricCard.tsx delete mode 100644 apps/web/src/components/ProfileView.tsx delete mode 100644 apps/web/src/components/charts/LeaderboardTable.tsx delete mode 100644 apps/web/src/components/charts/SessionTrendChart.tsx delete mode 100644 apps/web/src/components/charts/SummaryCards.tsx delete mode 100644 apps/web/src/components/charts/TopPerformersChart.tsx delete mode 100644 apps/web/src/components/layout/AppLayout.tsx delete mode 100644 apps/web/src/components/layout/Header.tsx delete mode 100644 apps/web/src/components/layout/Sidebar.tsx delete mode 100644 apps/web/src/components/maps/RouteMap.tsx delete mode 100644 apps/web/src/components/motion.tsx delete mode 100644 apps/web/src/components/providers/theme-provider.tsx delete mode 100644 apps/web/src/components/tables/DataTable.tsx delete mode 100644 apps/web/src/components/tables/ExpensesTable.tsx delete mode 100644 apps/web/src/components/tables/Pagination.tsx delete mode 100644 apps/web/src/components/tables/SessionsTable.tsx delete mode 100644 apps/web/src/components/ui/alert-dialog.tsx delete mode 100644 apps/web/src/components/ui/avatar.tsx delete mode 100644 apps/web/src/components/ui/badge.tsx delete mode 100644 apps/web/src/components/ui/button.tsx delete mode 100644 apps/web/src/components/ui/card.tsx delete mode 100644 apps/web/src/components/ui/dialog.tsx delete mode 100644 apps/web/src/components/ui/dropdown-menu.tsx delete mode 100644 apps/web/src/components/ui/input.tsx delete mode 100644 apps/web/src/components/ui/label.tsx delete mode 100644 apps/web/src/components/ui/select.tsx delete mode 100644 apps/web/src/components/ui/separator.tsx delete mode 100644 apps/web/src/components/ui/sheet.tsx delete mode 100644 apps/web/src/components/ui/skeleton.tsx delete mode 100644 apps/web/src/components/ui/table.tsx delete mode 100644 apps/web/src/components/ui/tabs.tsx delete mode 100644 apps/web/src/components/ui/theme-toggle.tsx delete mode 100644 apps/web/src/components/ui/toast.tsx delete mode 100644 apps/web/src/components/ui/toaster.tsx delete mode 100644 apps/web/src/components/ui/use-toast.ts delete mode 100644 apps/web/src/components/webhook-test/AuthCard.tsx delete mode 100644 apps/web/src/components/webhook-test/DeliveryPanel.tsx delete mode 100644 apps/web/src/components/webhook-test/TriggerEventCard.tsx delete mode 100644 apps/web/src/components/webhook-test/WebhookSetupCard.tsx delete mode 100644 apps/web/src/contexts/AuthContext.tsx delete mode 100644 apps/web/src/hooks/queries/useAnalytics.ts delete mode 100644 apps/web/src/hooks/queries/useDashboard.ts delete mode 100644 apps/web/src/hooks/queries/useEmployees.ts delete mode 100644 apps/web/src/hooks/queries/useExpenses.ts delete mode 100644 apps/web/src/hooks/queries/useMonitoring.ts delete mode 100644 apps/web/src/hooks/queries/useProfile.ts delete mode 100644 apps/web/src/hooks/queries/useQueues.ts delete mode 100644 apps/web/src/hooks/queries/useRoutes.ts delete mode 100644 apps/web/src/hooks/queries/useSessionLocations.ts delete mode 100644 apps/web/src/hooks/queries/useSessions.ts delete mode 100644 apps/web/src/hooks/queries/useWebhooks.ts delete mode 100644 apps/web/src/hooks/useAnimatedNumber.ts delete mode 100644 apps/web/src/hooks/useAuth.ts delete mode 100644 apps/web/src/hooks/useServerSentEvents.ts delete mode 100644 apps/web/src/lib/api.ts delete mode 100644 apps/web/src/lib/api/client.ts delete mode 100644 apps/web/src/lib/api/endpoints.ts delete mode 100644 apps/web/src/lib/api/index.ts delete mode 100644 apps/web/src/lib/auth/role.ts delete mode 100644 apps/web/src/lib/dateRange.ts delete mode 100644 apps/web/src/lib/design-system/tokens.ts delete mode 100644 apps/web/src/lib/design-system/variants.ts delete mode 100644 apps/web/src/lib/env.ts delete mode 100644 apps/web/src/lib/permissions.ts delete mode 100644 apps/web/src/lib/query-client.ts delete mode 100644 apps/web/src/lib/supabase.ts delete mode 100644 apps/web/src/lib/utils.ts delete mode 100644 apps/web/src/lib/webhook-api.ts delete mode 100644 apps/web/src/middleware.ts delete mode 100644 apps/web/src/test/dateRange.test.ts delete mode 100644 apps/web/src/test/permissions.test.ts delete mode 100644 apps/web/src/test/setup.ts delete mode 100644 apps/web/src/types/index.ts delete mode 100644 apps/web/tailwind.config.ts delete mode 100644 apps/web/tsconfig.json delete mode 100644 apps/web/vitest.config.ts delete mode 100644 infra/.env.monitoring.ci rename infra/nginx/{fieldtrack.conf => api.conf} (89%) delete mode 100644 test-trigger.txt diff --git a/.dockerignore b/.dockerignore index e48721f..29d0598 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -# Root .dockerignore — used when Docker build context is the monorepo root +# Root .dockerignore — used when Docker build context is the repo root # Version control .git @@ -9,10 +9,6 @@ node_modules apps/*/node_modules packages/*/node_modules -# Frontend — exclude all web source, but keep package.json for workspace resolution -apps/web/** -!apps/web/package.json - # Build artefacts (Docker regenerates these) apps/api/dist packages/*/dist diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 36bd78e..4676972 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -40,8 +40,6 @@ Closes # - [ ] `npm run typecheck -w apps/api` passes - [ ] `npm run test -w apps/api` passes -- [ ] `npm run type-check -w apps/web` passes (if frontend changed) -- [ ] `npm run build -w apps/web` passes (if frontend changed) - [ ] Integration tests pass locally - [ ] Manually tested the changed flows end-to-end - [ ] No special deployment steps required diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c6e68c..8f5ec86 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -52,10 +52,6 @@ jobs: - name: Build API run: npm run build -w apps/api || true - # (optional but useful) - - name: Build Web - run: npm run build -w apps/web || true - # ✅ Initialize CodeQL AFTER dependencies - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 99ebb3c..953c8ff 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,7 @@ # Parallel stages: # validate ─┐ # test-api ├─► build-scan-push ─► deploy ─► sync-infra ─► health-and-smoke -# build-web ┘ │ +# ┘ │ # rollback ◄────────────┘ (on failure) name: Deploy to Production @@ -40,7 +40,7 @@ jobs: # JOB: validate # # Fast pre-flight: TypeScript check + dependency audit. - # Runs in parallel with test-api and build-web to maximise pipeline speed. + # Runs in parallel with test-api to maximise pipeline speed. # --------------------------------------------------------------------------- validate: name: Validate (typecheck + audit) @@ -87,11 +87,21 @@ jobs: working-directory: apps/api run: npx tsc --noEmit + - name: Env contract guard (no direct process.env outside env.ts) + run: | + if grep -r --include="*.ts" "process\.env" apps/api/src/ \ + | grep -v "apps/api/src/config/env\.ts"; then + echo "❌ Direct process.env access detected outside env.ts" + echo " Use: import { env } from './config/env.js' instead" + exit 1 + fi + echo "✅ Env contract clean — no direct process.env access outside env.ts" + # --------------------------------------------------------------------------- # JOB: test-api # # Full backend test suite — unit tests then integration tests. - # Runs in parallel with validate and build-web. + # Runs in parallel with validate. # --------------------------------------------------------------------------- test-api: name: API Tests (unit + integration) @@ -134,63 +144,6 @@ jobs: working-directory: apps/api run: npx vitest run tests/integration/ - # --------------------------------------------------------------------------- - # JOB: build-web - # - # Full frontend validation and production build. - # Runs in parallel with validate and test-api. - # --------------------------------------------------------------------------- - build-web: - name: Frontend Build (typecheck + lint + build) - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - NEXT_PUBLIC_API_BASE_URL: /api/proxy - NEXT_PUBLIC_SUPABASE_URL: https://ci-placeholder.supabase.co - NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-build-placeholder-anon-key - NEXT_PUBLIC_MAPBOX_TOKEN: pk.ci-build-placeholder - steps: - - name: Verify NEXT_PUBLIC_API_BASE_URL is set - run: | - if [ -z "$NEXT_PUBLIC_API_BASE_URL" ]; then - echo "::error::NEXT_PUBLIC_API_BASE_URL is not set. Add it to the job env block." - exit 1 - fi - echo "NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}" - - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Node.js 24 - uses: actions/setup-node@v5 - with: - node-version: '24' - cache: npm - cache-dependency-path: '**/package-lock.json' - - - name: Install workspace dependencies (with retry) - run: | - echo "::group::npm ci" - for attempt in 1 2 3; do - npm ci && break - [ $attempt -eq 3 ] && { echo "::error::npm ci failed after 3 attempts"; exit 1; } - echo "Attempt $attempt failed — retrying in 15s..." - sleep 15 - done - echo "::endgroup::" - - - name: Build shared types - run: npm run build -w packages/types - - - name: TypeScript check (web) - run: npm run typecheck -w apps/web - - - name: ESLint (web) - run: npm run lint -w apps/web - - - name: Next.js production build - run: npm run build -w apps/web - # --------------------------------------------------------------------------- # JOB: build-scan-push # @@ -211,7 +164,7 @@ jobs: build-scan-push: name: Build, Scan & Push Docker Image runs-on: ubuntu-latest - needs: [validate, test-api, build-web] + needs: [validate, test-api] timeout-minutes: 25 permissions: contents: read @@ -254,7 +207,7 @@ jobs: load: true pull: true tags: | - fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + fieldtrack-api:${{ steps.meta.outputs.sha_short }} cache-from: type=gha,scope=production cache-to: type=gha,mode=max,scope=production @@ -262,7 +215,7 @@ jobs: # tls.createSecureContext() fails if libssl linkage is broken, proving runtime health. - name: Verify Node.js runtime (TLS operational check) run: | - IMAGE_NAME="fieldtrack-backend:${{ steps.meta.outputs.sha_short }}" + IMAGE_NAME="fieldtrack-api:${{ steps.meta.outputs.sha_short }}" echo "Testing image: $IMAGE_NAME" docker run --rm \ --entrypoint /nodejs/bin/node \ @@ -282,7 +235,7 @@ jobs: - name: Capture image digest id: digest run: | - IMAGE_NAME="fieldtrack-backend:${{ steps.meta.outputs.sha_short }}" + IMAGE_NAME="fieldtrack-api:${{ steps.meta.outputs.sha_short }}" DIGEST=$(docker inspect "$IMAGE_NAME" --format='{{.Id}}') echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" echo "=== Build traceability ===" @@ -376,7 +329,7 @@ jobs: - name: Scan image with Trivy (HIGH/CRITICAL, ignore-unfixed) env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} run: | SCAN_PASSED=false for i in 1 2 3; do @@ -407,7 +360,7 @@ jobs: - name: Scan for unfixed CRITICAL vulnerabilities (informational) continue-on-error: true env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} run: | UNFIXED_COUNT=$(docker run --rm \ --network none \ @@ -436,7 +389,7 @@ jobs: - name: Generate Trivy scan results (SARIF for GitHub Security) env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} run: | docker run --rm \ --network none \ @@ -461,7 +414,7 @@ jobs: # is exactly what lands in the registry. - name: Verify image digest unchanged before push env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} IMAGE_DIGEST: ${{ steps.digest.outputs.digest }} run: | # docker inspect .Id returns the config digest (sha256:...) which is @@ -486,16 +439,16 @@ jobs: run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') docker tag \ - fieldtrack-backend:${{ steps.meta.outputs.sha_short }} \ - ghcr.io/${OWNER}/fieldtrack-backend:${{ steps.meta.outputs.sha_short }} - docker push ghcr.io/${OWNER}/fieldtrack-backend:${{ steps.meta.outputs.sha_short }} - echo "✓ Pushed ghcr.io/${OWNER}/fieldtrack-backend:${{ steps.meta.outputs.sha_short }}" + fieldtrack-api:${{ steps.meta.outputs.sha_short }} \ + ghcr.io/${OWNER}/api:${{ steps.meta.outputs.sha_short }} + docker push ghcr.io/${OWNER}/api:${{ steps.meta.outputs.sha_short }} + echo "✓ Pushed ghcr.io/${OWNER}/api:${{ steps.meta.outputs.sha_short }}" # Use the same pinned Trivy image to generate the SBOM — no additional # tool dependency, no unpinned action, same supply-chain guarantees. - name: Generate SBOM (CycloneDX) env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} run: | docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ @@ -513,7 +466,7 @@ jobs: - name: Save build provenance env: - IMAGE_NAME: fieldtrack-backend:${{ steps.meta.outputs.sha_short }} + IMAGE_NAME: fieldtrack-api:${{ steps.meta.outputs.sha_short }} IMAGE_DIGEST: ${{ steps.digest.outputs.digest }} run: | echo "commit=${{ github.sha }}" > provenance.txt @@ -541,11 +494,11 @@ jobs: echo "| Field | Value |" echo "|---|---|" echo "| Commit SHA | \`${{ github.sha }}\` |" - echo "| Image tag | \`fieldtrack-backend:${{ steps.meta.outputs.sha_short }}\` |" + echo "| Image tag | \`fieldtrack-api:${{ steps.meta.outputs.sha_short }}\` |" echo "| Image digest | \`${IMAGE_DIGEST}\` |" echo "| SBOM components | ${SBOM_COUNT} |" echo "| Trivy gate | HIGH,CRITICAL / exit-code 1 / ignore-unfixed |" - echo "| Registry | ghcr.io/${{ github.repository_owner }}/fieldtrack-backend |" + echo "| Registry | ghcr.io/${{ github.repository_owner }}/api |" } >> "$GITHUB_STEP_SUMMARY" # --------------------------------------------------------------------------- @@ -584,7 +537,7 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" cd "$DEPLOY_ROOT" git fetch origin git reset --hard origin/master @@ -602,7 +555,7 @@ jobs: script: | set -euo pipefail T0=$(date +%s) - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" cd "$DEPLOY_ROOT" chmod +x apps/api/scripts/*.sh # Environment already validated in previous step @@ -617,7 +570,7 @@ jobs: username: ${{ secrets.DO_USER }} key: ${{ secrets.DO_SSH_KEY }} script: | - ACTIVE_SLOT=$(cat /var/run/fieldtrack/active-slot 2>/dev/null || echo "unknown") + ACTIVE_SLOT=$(cat /var/run/api/active-slot 2>/dev/null || echo "unknown") DEPLOY_STATUS="UNKNOWN" # Check if health endpoint is responding (good sign of successful deploy) @@ -648,10 +601,10 @@ jobs: script: | set -euo pipefail T0=$(date +%s) - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" INFRA_DIR="$DEPLOY_ROOT/infra" - NGINX_LIVE="/etc/nginx/sites-enabled/fieldtrack.conf" - ACTIVE_SLOT_FILE="/var/run/fieldtrack/active-slot" + NGINX_LIVE="/etc/nginx/sites-enabled/api.conf" + ACTIVE_SLOT_FILE="/var/run/api/active-slot" ACTIVE_SLOT=$(cat "$ACTIVE_SLOT_FILE" 2>/dev/null || echo "blue") if [ "$ACTIVE_SLOT" = "green" ]; then BACKEND_PORT=3002; else BACKEND_PORT=3001; fi @@ -663,18 +616,18 @@ jobs: echo "✓ API_HOSTNAME: $API_HOSTNAME" echo "=== Syncing Nginx (slot: $ACTIVE_SLOT, port: $BACKEND_PORT) ===" - sudo cp "$NGINX_LIVE" /tmp/fieldtrack.conf.bak 2>/dev/null || true + sudo cp "$NGINX_LIVE" /tmp/api.conf.bak 2>/dev/null || true NGINX_TMP=$(mktemp /tmp/fieldtrack-nginx.XXXXXX.conf) sed \ -e "s|__BACKEND_PORT__|$BACKEND_PORT|g" \ -e "s|__API_HOSTNAME__|$API_HOSTNAME|g" \ - "$INFRA_DIR/nginx/fieldtrack.conf" > "$NGINX_TMP" + "$INFRA_DIR/nginx/api.conf" > "$NGINX_TMP" sudo cp "$NGINX_TMP" "$NGINX_LIVE" rm -f "$NGINX_TMP" if ! sudo nginx -t 2>&1; then echo "Nginx test failed — restoring backup..." - sudo cp /tmp/fieldtrack.conf.bak "$NGINX_LIVE" + sudo cp /tmp/api.conf.bak "$NGINX_LIVE" exit 1 fi sudo systemctl reload nginx @@ -706,7 +659,7 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" cd "$DEPLOY_ROOT" source apps/api/scripts/load-env.sh echo "=== Checking /health via VPS (API_HOSTNAME=$API_HOSTNAME) ===" @@ -738,7 +691,7 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" cd "$DEPLOY_ROOT" source apps/api/scripts/load-env.sh echo "=== Final health check via public endpoint (API_HOSTNAME=$API_HOSTNAME) ===" @@ -830,11 +783,11 @@ jobs: key: ${{ secrets.DO_SSH_KEY }} script: | set -euo pipefail - export DEPLOY_ROOT="/home/ashish/FieldTrack-2.0" + export DEPLOY_ROOT="/api" cd "$DEPLOY_ROOT" chmod +x apps/api/scripts/*.sh ./apps/api/scripts/rollback.sh --auto # Log final state - ACTIVE_SLOT=$(cat /var/run/fieldtrack/active-slot 2>/dev/null || echo "unknown") + ACTIVE_SLOT=$(cat /var/run/api/active-slot 2>/dev/null || echo "unknown") echo "ROLLBACK_COMPLETE | ACTIVE_SLOT=$ACTIVE_SLOT | SHA=${{ github.sha }}" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7c60a53..a03b4cb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,7 +20,6 @@ jobs: timeout-minutes: 5 outputs: backend: ${{ steps.filter.outputs.backend }} - frontend: ${{ steps.filter.outputs.frontend }} infra: ${{ steps.filter.outputs.infra }} steps: - uses: actions/checkout@v5 @@ -32,9 +31,6 @@ jobs: backend: - 'apps/api/**' - 'packages/types/**' - frontend: - - 'apps/web/**' - - 'packages/types/**' infra: - 'infra/**' - '.github/workflows/**' @@ -79,6 +75,16 @@ jobs: if: needs.detect-changes.outputs.backend == 'true' - run: npx tsc --noEmit -p apps/api if: needs.detect-changes.outputs.backend == 'true' + - name: Env contract guard (no direct process.env outside env.ts) + if: needs.detect-changes.outputs.backend == 'true' + run: | + if grep -r --include="*.ts" "process\.env" apps/api/src/ \ + | grep -v "apps/api/src/config/env\.ts"; then + echo "❌ Direct process.env access detected outside env.ts" + echo " Use: import { env } from './config/env.js' instead" + exit 1 + fi + echo "✅ Env contract clean — no direct process.env access outside env.ts" - name: Dependency vulnerability scan (production deps) if: needs.detect-changes.outputs.backend == 'true' run: npm audit --omit=dev --audit-level=high @@ -106,7 +112,7 @@ jobs: --build-arg CACHE_BUSTER=${{ hashFiles('**/package-lock.json') }} \ --cache-from=type=gha,scope=pr \ --cache-to=type=gha,mode=max,scope=pr \ - -t fieldtrack-backend:ci-validation \ + -t fieldtrack-api:ci-validation \ -f apps/api/Dockerfile \ . @@ -114,13 +120,13 @@ jobs: if: needs.detect-changes.outputs.backend == 'true' run: | docker run -d \ - --name fieldtrack-ci-test \ + --name api-ci-test \ -p 127.0.0.1:3001:3000 \ --env-file apps/api/.env.ci \ -e SUPABASE_URL=${{ secrets.SUPABASE_URL_TEST }} \ -e SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY_TEST }} \ -e SUPABASE_SERVICE_ROLE_KEY=${{ secrets.SUPABASE_SERVICE_ROLE_KEY_TEST }} \ - fieldtrack-backend:ci-validation + fieldtrack-api:ci-validation STATUS="000" for i in $(seq 1 12); do @@ -132,7 +138,7 @@ jobs: if [ "$STATUS" != "200" ]; then echo "❌ /health returned HTTP $STATUS after 24 s (expected 200)" - docker logs fieldtrack-ci-test --tail 50 + docker logs api-ci-test --tail 50 exit 1 fi @@ -141,68 +147,14 @@ jobs: ECODE=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:3001${ENDPOINT}" || echo "000") if [ "$ECODE" != "401" ]; then echo "❌ ${ENDPOINT} expected 401 (unauthenticated), got ${ECODE}" - docker logs fieldtrack-ci-test --tail 50 + docker logs api-ci-test --tail 50 exit 1 fi echo "✓ ${ENDPOINT} → 401 (auth guard verified)" done - docker rm -f fieldtrack-ci-test - docker rmi fieldtrack-backend:ci-validation - - frontend-ci: - name: Frontend CI - runs-on: ubuntu-latest - needs: detect-changes - timeout-minutes: 15 - if: always() - env: - NEXT_PUBLIC_API_BASE_URL: /api/proxy - NEXT_PUBLIC_SUPABASE_URL: https://ci-placeholder.supabase.co - NEXT_PUBLIC_SUPABASE_ANON_KEY: ci-build-placeholder-anon-key - NEXT_PUBLIC_MAPBOX_TOKEN: pk.ci-build-placeholder - steps: - - name: Abort if change detection failed - if: needs.detect-changes.result != 'success' - run: | - echo "❌ Change detection did not succeed (result: ${{ needs.detect-changes.result }}) — cannot safely skip checks" - exit 1 - - - name: Verify NEXT_PUBLIC_API_BASE_URL is set - run: | - if [ -z "$NEXT_PUBLIC_API_BASE_URL" ]; then - echo "::error::NEXT_PUBLIC_API_BASE_URL is not set. Add it to the job env block." - exit 1 - fi - echo "NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}" - - - name: Skip if no frontend changes - if: needs.detect-changes.outputs.frontend != 'true' - run: | - echo "No frontend changes — skipping all frontend validation" - echo "✓ Frontend CI (skipped)" - exit 0 - - - uses: actions/checkout@v5 - if: needs.detect-changes.outputs.frontend == 'true' - - - uses: actions/setup-node@v5 - if: needs.detect-changes.outputs.frontend == 'true' - with: - node-version: '24' - cache: npm - cache-dependency-path: '**/package-lock.json' - - - run: npm ci - if: needs.detect-changes.outputs.frontend == 'true' - - run: npm run build -w packages/types - if: needs.detect-changes.outputs.frontend == 'true' - - run: npm run typecheck -w apps/web - if: needs.detect-changes.outputs.frontend == 'true' - - run: npm run lint -w apps/web - if: needs.detect-changes.outputs.frontend == 'true' - - run: npm run build -w apps/web - if: needs.detect-changes.outputs.frontend == 'true' + docker rm -f api-ci-test + docker rmi fieldtrack-api:ci-validation infra-ci: name: Infra CI @@ -233,7 +185,7 @@ jobs: sed \ -e 's/__BACKEND_PORT__/3001/g' \ -e 's/__API_HOSTNAME__/api.test.local/g' \ - infra/nginx/fieldtrack.conf > /tmp/nginx.conf + infra/nginx/api.conf > /tmp/nginx.conf if grep -q '__[A-Z_]*__' /tmp/nginx.conf; then echo "❌ Unreplaced placeholders" @@ -249,5 +201,5 @@ jobs: docker run --rm \ -v /tmp/nginx.conf:/etc/nginx/conf.d/default.conf:ro \ - -v /tmp/ssl:/etc/ssl/fieldtrack:ro \ + -v /tmp/ssl:/etc/ssl/api:ro \ nginx:1.27-alpine nginx -t \ No newline at end of file diff --git a/.gitignore b/.gitignore index f8e1077..7791c73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # ============================================ -# Centralized .gitignore for Full Stack App +# .gitignore for FieldTrack API # ============================================ # ---------------- @@ -44,20 +44,14 @@ packages/*/node_modules/ !.env.example !.env.monitoring.example !.env.ci -!.env.monitoring.ci # ---------------- # Build Output # ---------------- -# Backend dist/ build/ - -# Frontend (Next.js) .next/ out/ -/.next/ -/out/ # ---------------- # Testing @@ -72,7 +66,6 @@ coverage/ # ---------------- *.tsbuildinfo tsconfig.tsbuildinfo -next-env.d.ts # ---------------- # Logs @@ -83,7 +76,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* -lerna-debug.log* # ---------------- # OS Files diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8fb3f..f5399bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,8 +145,8 @@ All significant changes to FieldTrack 2.0 are documented here by development pha ## [Phase 13] — Production Infrastructure: VPS, Nginx & Monitoring Stack — 2026 - Added `backend/scripts/vps-setup.sh` — idempotent VPS provisioning (Docker, Nginx, systemd, certbot, ufw) -- Added `infra/nginx/fieldtrack.conf` — TLS termination, HTTP→HTTPS redirect, proxy headers, WebSocket upgrade, gzip -- Added `infra/docker-compose.monitoring.yml` — Prometheus, Grafana, Loki, Promtail, Tempo on `fieldtrack_network` +- Added `infra/nginx/api.conf` — TLS termination, HTTP→HTTPS redirect, proxy headers, WebSocket upgrade, gzip +- Added `infra/docker-compose.monitoring.yml` — Prometheus, Grafana, Loki, Promtail, Tempo on `api_network` - Added `infra/grafana/dashboards/fieldtrack.json` — pre-built dashboard (HTTP rate, latency, queue depth, heap, Redis) - Added `infra/grafana/provisioning/` — auto-provisioned dashboard and Prometheus datasource - Added `infra/prometheus/alerts.yml` — alert rules for API latency, queue depth, Redis connectivity, host metrics diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ceee90..79c9c91 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to FieldTrack 2.0 +# Contributing to FieldTrack API --- @@ -7,8 +7,8 @@ **Prerequisites:** Node.js ≥ 24, npm, Redis ```bash -git clone https://github.com/fieldtrack-tech/fieldtrack-2.0.git -cd fieldtrack-2.0 +git clone https://github.com/fieldtrack-tech/api.git +cd api npm install cp apps/api/.env.example apps/api/.env @@ -83,8 +83,6 @@ chore(deps): bump @fastify/jwt to 9.1.0 ```bash npm run typecheck -w apps/api npm run test -w apps/api - npm run type-check -w apps/web # if frontend changed - npm run build -w apps/web # if frontend changed ``` 4. Commit with a conventional commit message (see format above). diff --git a/README.md b/README.md index 4acec21..9440b2a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Production-grade multi-tenant backend for real-time field workforce tracking — attendance, GPS, expense management, and admin analytics. -[![CI](https://github.com/fieldtrack-tech/fieldtrack-2.0/actions/workflows/deploy.yml/badge.svg)](https://github.com/fieldtrack-tech/fieldtrack-2.0/actions/workflows/deploy.yml) +[![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) @@ -102,12 +102,11 @@ FieldTrack 2.0 is a production-ready REST API backend for managing field workfor ```bash # 1. Install dependencies -cd backend npm install # 2. Configure environment -cp .env.example .env -# Edit .env — fill in Supabase URL, keys, Redis URL, and ALLOWED_ORIGINS +cp apps/api/.env.example apps/api/.env +# Edit apps/api/.env — fill in Supabase URL, keys, Redis URL, and ALLOWED_ORIGINS # 3. Run in development mode npm run dev @@ -142,8 +141,10 @@ See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for full setup instructions includi ## Project Structure +> **Note:** The web frontend is maintained in a separate repository: [fieldtrack-tech/web](https://github.com/fieldtrack-tech/web) + ``` -FieldTrack-2.0/ +api/ ├── apps/api/ # Fastify + TypeScript backend │ ├── src/ # Application source │ │ ├── modules/ # Domain modules (attendance · locations · expenses · analytics) diff --git a/apps/api/.env.ci b/apps/api/.env.ci index c483a67..448d628 100644 --- a/apps/api/.env.ci +++ b/apps/api/.env.ci @@ -34,7 +34,7 @@ REDIS_URL=redis://invalid-ci-host:6379 WORKERS_ENABLED=false METRICS_SCRAPE_TOKEN=dummy -SERVICE_NAME=fieldtrack-backend-ci +SERVICE_NAME=fieldtrack-api-ci BODY_LIMIT_BYTES=1000000 REQUEST_TIMEOUT_MS=30000 @@ -44,3 +44,7 @@ MAX_POINTS_PER_SESSION=50000 MAX_SESSION_DURATION_HOURS=168 WORKER_CONCURRENCY=1 ANALYTICS_WORKER_CONCURRENCY=5 +WEBHOOK_WORKER_CONCURRENCY=5 +WEBHOOK_DLQ_MAX_SIZE=10000 +WEBHOOK_DLQ_RETENTION_DAYS=30 +WEBHOOK_MAX_PAYLOAD_BYTES=262144 diff --git a/apps/api/.env.example b/apps/api/.env.example index 6133452..ba45bc6 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -1,5 +1,5 @@ # ============================================================================= -# FieldTrack 2.0 — API Environment +# FieldTrack API — Environment # Copy to .env and fill values. Do NOT commit real secrets. # ============================================================================= @@ -21,6 +21,8 @@ CORS_ORIGIN= SUPABASE_URL=https://your-project-ref.supabase.co SUPABASE_ANON_KEY= SUPABASE_SERVICE_ROLE_KEY= +# JWT signing secret — for local test server only (tests/setup/test-server.ts). +# NOT validated by env.ts. NOT required in production (production uses JWKS via Supabase auth). SUPABASE_JWT_SECRET= # --- Redis --- @@ -31,7 +33,7 @@ METRICS_SCRAPE_TOKEN= # --- Observability --- TEMPO_ENDPOINT=http://tempo:4318 -SERVICE_NAME=fieldtrack-backend +SERVICE_NAME=fieldtrack-api # GITHUB_SHA= (auto-injected) # --- HTTP --- @@ -39,8 +41,16 @@ BODY_LIMIT_BYTES=1000000 REQUEST_TIMEOUT_MS=30000 # --- Workers --- +# Set to true in production (Redis must be provisioned). false in dev/CI. +WORKERS_ENABLED=false MAX_QUEUE_DEPTH=1000 MAX_POINTS_PER_SESSION=50000 MAX_SESSION_DURATION_HOURS=168 WORKER_CONCURRENCY=1 -ANALYTICS_WORKER_CONCURRENCY=5 \ No newline at end of file +ANALYTICS_WORKER_CONCURRENCY=5 +WEBHOOK_WORKER_CONCURRENCY=5 + +# --- Webhook DLQ --- +WEBHOOK_DLQ_MAX_SIZE=10000 +WEBHOOK_DLQ_RETENTION_DAYS=30 +WEBHOOK_MAX_PAYLOAD_BYTES=262144 \ No newline at end of file diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index e1c72cd..c8a9a88 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,4 +1,4 @@ -# Build context: monorepo root (docker build -f apps/api/Dockerfile .) +# Build context: repo root (docker build -f apps/api/Dockerfile .) # # Three-stage build: # 1. builder — compiles TypeScript (full devDependencies available) diff --git a/apps/api/README.md b/apps/api/README.md index 192e12a..e5bfd9d 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -83,10 +83,10 @@ src/ ```bash # Build image -docker build -t fieldtrack-backend . +docker build -t fieldtrack-api . # Run container -docker run -p 3000:3000 --env-file .env fieldtrack-backend +docker run -p 3000:3000 --env-file .env fieldtrack-api ``` ## API Endpoints diff --git a/apps/api/docs/runbooks/cert-renewal-failure.md b/apps/api/docs/runbooks/cert-renewal-failure.md deleted file mode 100644 index c4f00de..0000000 --- a/apps/api/docs/runbooks/cert-renewal-failure.md +++ /dev/null @@ -1,314 +0,0 @@ -# Runbook: TLS Certificate Renewal Failure - -## Symptoms -- Certificate expiry alert firing -- Browser shows "Your connection is not private" -- Certbot renewal fails -- HTTPS connections rejected - -## Immediate Actions - -### 1. Check Certificate Status -```bash -# Check certificate expiry -sudo certbot certificates - -# Check specific domain -echo | openssl s_client -servername api.fieldtrack.meowsician.tech \ - -connect api.fieldtrack.meowsician.tech:443 2>/dev/null | \ - openssl x509 -noout -dates - -# Check days until expiry -echo | openssl s_client -servername api.fieldtrack.meowsician.tech \ - -connect api.fieldtrack.meowsician.tech:443 2>/dev/null | \ - openssl x509 -noout -enddate -``` - -### 2. Check Certbot Logs -```bash -# View recent renewal attempts -sudo tail -100 /var/log/letsencrypt/letsencrypt.log - -# Check for errors -sudo grep -i error /var/log/letsencrypt/letsencrypt.log | tail -20 -``` - -### 3. Verify ACME Challenge Access -```bash -# Test .well-known/acme-challenge is accessible -curl -I http://api.fieldtrack.meowsician.tech/.well-known/acme-challenge/test - -# Should return 404 (not 403 or 502) -# 403 = blocked by nginx -# 502 = nginx misconfigured -``` - -## Manual Renewal - -### Standard Renewal -```bash -# Dry run first (test without actually renewing) -sudo certbot renew --dry-run - -# If dry run succeeds, do actual renewal -sudo certbot renew - -# Reload nginx to use new certificate -sudo systemctl reload nginx -``` - -### Force Renewal (if cert not yet expired) -```bash -# Force renewal even if not due -sudo certbot renew --force-renewal - -# Reload nginx -sudo systemctl reload nginx -``` - -### Interactive Renewal (if automated fails) -```bash -# Stop nginx temporarily -sudo systemctl stop nginx - -# Run certbot standalone -sudo certbot certonly --standalone \ - -d api.fieldtrack.meowsician.tech \ - --email your-email@example.com \ - --agree-tos - -# Start nginx -sudo systemctl start nginx -``` - -## Root Cause Analysis - -### Common Failure Modes - -1. **ACME Challenge Blocked** - ```bash - # Check nginx config allows .well-known - sudo nginx -T | grep -A 5 "well-known" - - # Should have: - # location /.well-known/acme-challenge/ { - # root /var/www/certbot; - # } - - # Verify directory exists and is writable - ls -la /var/www/certbot/.well-known/acme-challenge/ - ``` - -2. **DNS Issues** - ```bash - # Verify DNS resolves correctly - dig api.fieldtrack.meowsician.tech +short - - # Should return your VPS IP - # If not, update DNS records - ``` - -3. **Rate Limiting** - ```bash - # Let's Encrypt has rate limits: - # - 5 failed validations per hour - # - 50 certificates per domain per week - - # Check if rate limited - sudo grep "rate limit" /var/log/letsencrypt/letsencrypt.log - - # If rate limited, wait and try again later - ``` - -4. **Firewall Blocking Port 80** - ```bash - # Check port 80 is open - sudo ufw status | grep 80 - - # Should show: - # 80/tcp ALLOW Anywhere - - # If blocked, allow it - sudo ufw allow 80/tcp - ``` - -5. **Certbot Service Not Running** - ```bash - # Check certbot timer - sudo systemctl status certbot.timer - - # If inactive, enable it - sudo systemctl enable certbot.timer - sudo systemctl start certbot.timer - ``` - -## Certificate Validation - -### Verify New Certificate -```bash -# Check certificate details -sudo openssl x509 -in /etc/letsencrypt/live/api.fieldtrack.meowsician.tech/fullchain.pem \ - -noout -text | grep -A 2 "Validity" - -# Verify certificate chain -sudo openssl verify -CAfile /etc/letsencrypt/live/api.fieldtrack.meowsician.tech/chain.pem \ - /etc/letsencrypt/live/api.fieldtrack.meowsician.tech/cert.pem - -# Test HTTPS connection -curl -vI https://api.fieldtrack.meowsician.tech 2>&1 | grep -i "SSL certificate" -``` - -### Browser Test -```bash -# Test from external location -curl -I https://api.fieldtrack.meowsician.tech - -# Check SSL Labs rating (optional) -# https://www.ssllabs.com/ssltest/analyze.html?d=api.fieldtrack.meowsician.tech -``` - -## Prevention - -### Automated Renewal -```bash -# Certbot should auto-renew via systemd timer -# Verify timer is active -sudo systemctl list-timers | grep certbot - -# Should show next run time -# Certbot renews certs 30 days before expiry -``` - -### Monitoring -```bash -# Add Prometheus alert for certificate expiry -# Already configured in infra/prometheus/alerts.yml -# Alert fires when cert expires in < 7 days -``` - -### Pre-Renewal Hook -```bash -# Create pre-renewal hook to stop nginx if needed -sudo nano /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh - -#!/bin/bash -# Only stop nginx if using standalone mode -# Not needed for webroot mode (default) - -# Make executable -sudo chmod +x /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh -``` - -### Post-Renewal Hook -```bash -# Create post-renewal hook to reload nginx -sudo nano /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh - -#!/bin/bash -systemctl reload nginx - -# Make executable -sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh -``` - -## Emergency Procedures - -### Certificate Expired (Downtime Acceptable) -```bash -# Stop nginx -sudo systemctl stop nginx - -# Remove old certificate -sudo certbot delete --cert-name api.fieldtrack.meowsician.tech - -# Get new certificate (standalone) -sudo certbot certonly --standalone \ - -d api.fieldtrack.meowsician.tech \ - --email your-email@example.com \ - --agree-tos - -# Start nginx -sudo systemctl start nginx -``` - -### Certificate Expired (Zero Downtime Required) -```bash -# Use webroot mode (nginx stays running) -sudo certbot certonly --webroot \ - -w /var/www/certbot \ - -d api.fieldtrack.meowsician.tech \ - --email your-email@example.com \ - --agree-tos - -# Reload nginx -sudo systemctl reload nginx -``` - -## Backup Certificates - -### Export Current Certificates -```bash -# Create backup directory -mkdir -p ~/cert-backup-$(date +%Y%m%d) - -# Copy certificates -sudo cp -r /etc/letsencrypt/live/api.fieldtrack.meowsician.tech/ \ - ~/cert-backup-$(date +%Y%m%d)/ - -sudo cp -r /etc/letsencrypt/archive/api.fieldtrack.meowsician.tech/ \ - ~/cert-backup-$(date +%Y%m%d)/archive/ - -# Copy renewal config -sudo cp /etc/letsencrypt/renewal/api.fieldtrack.meowsician.tech.conf \ - ~/cert-backup-$(date +%Y%m%d)/ -``` - -### Restore Certificates -```bash -# Stop nginx -sudo systemctl stop nginx - -# Restore from backup -sudo cp -r ~/cert-backup-YYYYMMDD/api.fieldtrack.meowsician.tech/ \ - /etc/letsencrypt/live/ - -sudo cp -r ~/cert-backup-YYYYMMDD/archive/ \ - /etc/letsencrypt/archive/api.fieldtrack.meowsician.tech/ - -sudo cp ~/cert-backup-YYYYMMDD/api.fieldtrack.meowsician.tech.conf \ - /etc/letsencrypt/renewal/ - -# Start nginx -sudo systemctl start nginx -``` - -## Escalation - -If certificate renewal continues to fail: -1. Check Let's Encrypt status page: https://letsencrypt.status.io/ -2. Verify domain ownership and DNS records -3. Consider using alternative ACME client (acme.sh) -4. Contact Let's Encrypt support if rate limited -5. Consider purchasing commercial certificate as temporary solution - -## Post-Incident - -1. **Document Root Cause** - - What caused the failure? - - How was it detected? - - How long was the outage? - -2. **Update Monitoring** - - Ensure certificate expiry alert is working - - Set alert threshold to 14 days (not 7) - - Add alert for renewal failures - -3. **Improve Automation** - - Verify certbot timer is enabled - - Test renewal process in staging - - Document manual renewal procedure - -4. **Communication** - - Notify users if there was downtime - - Update status page - - Create incident report diff --git a/apps/api/docs/runbooks/container-crash.md b/apps/api/docs/runbooks/container-crash.md deleted file mode 100644 index f9fd1a8..0000000 --- a/apps/api/docs/runbooks/container-crash.md +++ /dev/null @@ -1,170 +0,0 @@ -# Runbook: Container Crash - -## Symptoms -- Backend container stops unexpectedly -- 502 Bad Gateway errors from Nginx -- Prometheus shows container down -- Health checks failing - -## Immediate Actions - -### 1. Identify Crashed Container -```bash -# Check container status -docker ps -a | grep backend - -# Get exit code and status -docker inspect backend-blue --format='{{.State.ExitCode}} {{.State.Status}}' -docker inspect backend-green --format='{{.State.ExitCode}} {{.State.Status}}' -``` - -### 2. Retrieve Crash Logs -```bash -# Get last 200 lines before crash -docker logs backend-blue --tail 200 > /tmp/crash-blue.log -docker logs backend-green --tail 200 > /tmp/crash-green.log - -# Check for OOM kills -dmesg | grep -i "out of memory" -dmesg | grep -i "killed process" -``` - -### 3. Check System Resources -```bash -# Memory usage -free -h -docker stats --no-stream - -# Disk space -df -h - -# CPU load -uptime -top -bn1 | head -20 -``` - -## Recovery Procedure - -### Quick Recovery (Restart Container) -```bash -# Restart the crashed container -docker restart backend-blue # or backend-green - -# Wait for health check -for i in {1..30}; do - curl -f http://localhost:3001/ready && break || sleep 2 -done -``` - -### Full Recovery (Redeploy) -```bash -cd /home/ashish/FieldTrack-2.0 -export DEPLOY_ROOT=/home/ashish/FieldTrack-2.0 - -# Get current deployment SHA -CURRENT_SHA=$(head -1 apps/api/.deploy_history) - -# Redeploy current version -./apps/api/scripts/deploy-bluegreen.sh "$CURRENT_SHA" -``` - -## Root Cause Analysis - -### Exit Code Meanings -- **0**: Clean shutdown (rare for crash) -- **1**: Application error (check logs) -- **137**: OOM killed (out of memory) -- **139**: Segmentation fault -- **143**: SIGTERM (manual stop) - -### Common Crash Causes - -1. **Out of Memory (Exit 137)** - ```bash - # Check memory limits - docker inspect backend-blue | grep -i memory - - # Increase memory limit if needed - # Edit docker run command in deploy script - ``` - -2. **Unhandled Exception** - - Check application logs for stack traces - - Look for "Unhandled error" messages - - Verify database connectivity - -3. **Database Connection Loss** - ```bash - # Test Supabase connectivity - curl -H "apikey: $SUPABASE_ANON_KEY" $SUPABASE_URL/rest/v1/ - - # Check connection pool exhaustion in logs - grep "connection pool" /tmp/crash-*.log - ``` - -4. **Redis Connection Loss** - ```bash - # Check Redis status - docker ps | grep redis - redis-cli -u $REDIS_URL ping - - # Check BullMQ connection errors - grep "Redis connection" /tmp/crash-*.log - ``` - -5. **Worker Queue Saturation** - ```bash - # Check queue depths - redis-cli -u $REDIS_URL llen bull:distance:wait - redis-cli -u $REDIS_URL llen bull:analytics:wait - - # If > MAX_QUEUE_DEPTH, drain queue - ``` - -## Prevention - -### Monitoring -- Set up Prometheus alerts for container down -- Monitor memory usage trends -- Track error rates before crashes - -### Configuration -- Set appropriate memory limits -- Configure restart policy: `--restart unless-stopped` -- Enable Docker healthcheck -- Set METRICS_SCRAPE_TOKEN in production - -### Code Quality -- Add error boundaries for async operations -- Implement graceful shutdown handlers -- Add circuit breakers for external services -- Log all unhandled rejections - -## Post-Incident - -1. **Preserve Evidence** - ```bash - # Save logs - cp /tmp/crash-*.log ~/incident-$(date +%Y%m%d-%H%M%S)/ - - # Save container state - docker inspect backend-blue > ~/incident-*/inspect.json - ``` - -2. **Update Monitoring** - - Add alert for specific error pattern - - Adjust thresholds if needed - - Document in incident log - -3. **Code Fix** - - Create GitHub issue with logs - - Add test case to prevent recurrence - - Deploy fix through normal CI/CD - -## Escalation - -If crashes persist after recovery: -1. Check Grafana dashboard for patterns -2. Review recent code changes -3. Consider rolling back to last stable version -4. Engage development team for debugging diff --git a/apps/api/docs/runbooks/deploy-failure.md b/apps/api/docs/runbooks/deploy-failure.md deleted file mode 100644 index 1547189..0000000 --- a/apps/api/docs/runbooks/deploy-failure.md +++ /dev/null @@ -1,102 +0,0 @@ -# Runbook: Deployment Failure - -## Symptoms -- GitHub Actions deploy job fails -- Container fails health/readiness checks -- Nginx configuration validation fails -- Blue-green switch incomplete - -## Immediate Actions - -### 1. Check Deployment Logs -```bash -# SSH to VPS -ssh $DO_USER@$DO_HOST - -# Check recent deployment logs -cd /home/ashish/FieldTrack-2.0 -tail -100 apps/api/.deploy_history - -# Check container logs -docker logs backend-blue --tail 100 -docker logs backend-green --tail 100 -``` - -### 2. Verify Container Status -```bash -# Check running containers -docker ps -a | grep backend - -# Check container health -docker inspect backend-blue | grep -A 10 Health -docker inspect backend-green | grep -A 10 Health -``` - -### 3. Check Readiness Dependencies -```bash -# Test Redis connectivity -redis-cli -u $REDIS_URL ping - -# Test database connectivity (from container) -docker exec backend-blue curl -f http://localhost:3000/ready - -# Check Supabase connectivity -curl -H "apikey: $SUPABASE_ANON_KEY" $SUPABASE_URL/rest/v1/ -``` - -## Rollback Procedure - -### Automatic Rollback -GitHub Actions automatically triggers rollback on deployment failure. - -### Manual Rollback -```bash -cd /home/ashish/FieldTrack-2.0 -chmod +x apps/api/scripts/rollback.sh -./apps/api/scripts/rollback.sh -``` - -## Root Cause Analysis - -### Common Failure Modes - -1. **Environment Variable Missing** - - Check `.env` file completeness - - Verify METRICS_SCRAPE_TOKEN in production - - Validate DEPLOY_ROOT is set - -2. **Database Migration Failure** - - Check migration logs - - Verify Supabase connection - - Rollback migration if needed - -3. **Redis Connection Failure** - - Verify Redis container running - - Check REDIS_URL format - - Test connectivity from app container - -4. **Image Pull Failure** - - Verify GitHub Container Registry access - - Check image tag exists - - Validate network connectivity - -5. **Nginx Configuration Error** - - Check template substitution - - Verify API_HOSTNAME set correctly (derived from API_BASE_URL in apps/api/.env) - - Test nginx config: `sudo nginx -t` - -## Prevention - -- Always test in staging first -- Run smoke tests before production deploy -- Monitor deployment metrics -- Keep rollback history (last 5 deployments) -- Set DEPLOY_ROOT environment variable - -## Escalation - -If rollback fails or issue persists: -1. Check monitoring dashboard: https://api.fieldtrack.meowsician.tech/monitor/ -2. Review Loki logs for errors -3. Contact DevOps team -4. Consider manual container restart as last resort diff --git a/apps/api/docs/runbooks/monitoring-failure.md b/apps/api/docs/runbooks/monitoring-failure.md deleted file mode 100644 index fc459cd..0000000 --- a/apps/api/docs/runbooks/monitoring-failure.md +++ /dev/null @@ -1,262 +0,0 @@ -# Runbook: Monitoring Stack Failure - -## Symptoms -- Grafana dashboard unreachable -- Prometheus not scraping metrics -- Loki logs not appearing -- Alerts not firing - -## Immediate Actions - -### 1. Check Monitoring Containers -```bash -# Check all monitoring containers -docker ps | grep -E "prometheus|grafana|loki|promtail" - -# Check container logs -docker logs prometheus --tail 50 -docker logs grafana --tail 50 -docker logs loki --tail 50 -docker logs promtail --tail 50 -``` - -### 2. Verify Network Connectivity -```bash -# Check fieldtrack_network exists -docker network ls | grep fieldtrack - -# Verify containers on network -docker network inspect fieldtrack_network | grep Name -``` - -### 3. Test Individual Components -```bash -# Test Prometheus -curl http://localhost:9090/-/healthy - -# Test Grafana -curl http://localhost:3333/api/health - -# Test Loki -curl http://localhost:3100/ready - -# Test backend metrics endpoint -curl -H "Authorization: Bearer $METRICS_SCRAPE_TOKEN" http://localhost:3001/metrics -``` - -## Recovery Procedures - -### Restart Monitoring Stack -```bash -cd /home/ashish/FieldTrack-2.0/infra - -# Stop all monitoring containers -docker compose --env-file .env.monitoring -f docker-compose.monitoring.yml down - -# Start fresh -docker compose --env-file .env.monitoring -f docker-compose.monitoring.yml up -d - -# Verify startup -docker compose --env-file .env.monitoring -f docker-compose.monitoring.yml ps -``` - -### Restart Individual Service - -#### Prometheus -```bash -docker restart prometheus - -# Verify scrape targets -curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job, health}' -``` - -#### Grafana -```bash -docker restart grafana - -# Check datasources -curl -u admin:$GRAFANA_PASSWORD http://localhost:3333/api/datasources -``` - -#### Loki -```bash -docker restart loki - -# Test log ingestion -curl http://localhost:3100/loki/api/v1/labels -``` - -#### Promtail -```bash -docker restart promtail - -# Check targets -curl http://localhost:9080/targets -``` - -## Root Cause Analysis - -### Common Issues - -1. **Prometheus Not Scraping** - ```bash - # Check scrape config - docker exec prometheus cat /etc/prometheus/prometheus.yml - - # Verify METRICS_SCRAPE_TOKEN matches - grep METRICS_SCRAPE_TOKEN infra/.env.monitoring - - # Check backend metrics endpoint - curl -H "Authorization: Bearer $METRICS_SCRAPE_TOKEN" http://backend-blue:3000/metrics - ``` - -2. **Grafana Datasource Failure** - ```bash - # Check Grafana logs for datasource errors - docker logs grafana | grep -i "datasource" - - # Verify Prometheus URL in Grafana - # Should be: http://prometheus:9090 - - # Verify Loki URL in Grafana - # Should be: http://loki:3100 - ``` - -3. **Loki Not Receiving Logs** - ```bash - # Check Promtail is running - docker ps | grep promtail - - # Verify Promtail config - docker exec promtail cat /etc/promtail/promtail.yml - - # Check Promtail can reach Loki - docker exec promtail wget -O- http://loki:3100/ready - ``` - -4. **Disk Space Full** - ```bash - # Check disk usage - df -h - - # Check Prometheus data size - du -sh infra/prometheus/data/ - - # Check Loki data size - du -sh infra/loki/ - - # Clean old data if needed (Loki has 30d retention) - ``` - -5. **Configuration Drift** - ```bash - # Verify config hash - cat ~/.fieldtrack-monitoring-hash - - # Force config reload - rm ~/.fieldtrack-monitoring-hash - cd /home/ashish/FieldTrack-2.0 - export DEPLOY_ROOT=/home/ashish/FieldTrack-2.0 - ./apps/api/scripts/deploy-bluegreen.sh $(head -1 apps/api/.deploy_history) - ``` - -## Configuration Validation - -### Prometheus -```bash -# Validate prometheus.yml -docker run --rm -v $(pwd)/infra/prometheus:/prometheus prom/prometheus:latest \ - promtool check config /prometheus/prometheus.yml - -# Validate alerts.yml -docker run --rm -v $(pwd)/infra/prometheus:/prometheus prom/prometheus:latest \ - promtool check rules /prometheus/alerts.yml -``` - -### Loki -```bash -# Validate loki-config.yaml -docker run --rm -v $(pwd)/infra/loki:/loki grafana/loki:2.9.6 \ - -config.file=/loki/loki-config.yaml -verify-config -``` - -### Promtail -```bash -# Validate promtail.yml -docker run --rm -v $(pwd)/infra/promtail:/promtail grafana/promtail:2.9.6 \ - -config.file=/promtail/promtail.yml -dry-run -``` - -## Data Recovery - -### Prometheus Data Loss -```bash -# Prometheus stores 15 days by default -# Data is in infra/prometheus/data/ -# If corrupted, stop Prometheus and delete data dir -docker stop prometheus -rm -rf infra/prometheus/data/* -docker start prometheus -# Metrics will rebuild from current scrapes -``` - -### Loki Data Loss -```bash -# Loki stores 30 days (configured in loki-config.yaml) -# Data is in infra/loki/chunks/ -# If corrupted, stop Loki and delete chunks -docker stop loki -rm -rf infra/loki/chunks/* -docker start loki -# Logs will rebuild from Promtail -``` - -### Grafana Dashboard Loss -```bash -# Dashboards are in infra/grafana/dashboards/ -# If lost, restore from git -cd /home/ashish/FieldTrack-2.0 -git checkout infra/grafana/dashboards/ -docker restart grafana -``` - -## Prevention - -### Regular Maintenance -```bash -# Weekly: Check disk space -df -h - -# Weekly: Verify all targets healthy -curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.health != "up")' - -# Monthly: Review retention policies -# Prometheus: 15d default -# Loki: 30d configured -``` - -### Monitoring the Monitors -- Set up external uptime monitoring for Grafana -- Configure Prometheus to alert on its own health -- Document baseline resource usage - -### Backup Strategy -```bash -# Backup Grafana dashboards -cp -r infra/grafana/dashboards/ ~/backups/grafana-$(date +%Y%m%d)/ - -# Backup Prometheus config -cp infra/prometheus/*.yml ~/backups/prometheus-$(date +%Y%m%d)/ - -# Backup Loki config -cp infra/loki/*.yaml ~/backups/loki-$(date +%Y%m%d)/ -``` - -## Escalation - -If monitoring cannot be restored: -1. Application continues running (monitoring is non-critical) -2. Check GitHub Actions logs for deployment issues -3. Review recent infrastructure changes -4. Consider fresh monitoring stack deployment -5. Engage DevOps team if data corruption suspected diff --git a/apps/api/errors.txt b/apps/api/errors.txt deleted file mode 100644 index bd53885..0000000 --- a/apps/api/errors.txt +++ /dev/null @@ -1,52 +0,0 @@ -node_modules/zod/v4/locales/index.d.cts(1,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ar"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(2,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/az"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(3,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/be"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(4,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/bg"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(5,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ca"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(6,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/cs"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(7,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/da"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(8,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/de"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(9,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/en"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(10,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/eo"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(11,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/es"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(12,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/fa"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(13,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/fi"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(14,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/fr"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(15,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/fr-CA"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(16,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/he"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(17,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/hu"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(18,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/hy"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(19,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/id"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(20,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/is"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(21,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/it"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(22,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ja"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(23,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ka"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(24,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/kh"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(25,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/km"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(26,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ko"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(27,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/lt"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(28,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/mk"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(29,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ms"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(30,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/nl"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(31,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/no"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(32,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ota"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(33,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ps"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(34,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/pl"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(35,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/pt"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(36,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ru"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(37,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/sl"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(38,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/sv"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(39,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ta"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(40,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/th"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(41,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/tr"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(42,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ua"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(43,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/uk"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(44,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/ur"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(45,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/uz"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(46,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/vi"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(47,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/zh-CN"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(48,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/zh-TW"' can only be default-imported using the 'esModuleInterop' flag -node_modules/zod/v4/locales/index.d.cts(49,21): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/zod/v4/locales/yo"' can only be default-imported using the 'esModuleInterop' flag -src/auth/jwtVerifier.ts(1,8): error TS1259: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/jwks-rsa/index"' can only be default-imported using the 'esModuleInterop' flag -src/auth/jwtVerifier.ts(2,8): error TS1192: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/@types/jsonwebtoken/index"' has no default export. -src/config/env.ts(28,8): error TS1192: Module '"D:/Codebase/FieldTrack-2.0/apps/api/node_modules/dotenv/lib/main"' has no default export. diff --git a/apps/api/healthcheck.js b/apps/api/healthcheck.js index 71a7ef1..9408ff7 100644 --- a/apps/api/healthcheck.js +++ b/apps/api/healthcheck.js @@ -3,7 +3,7 @@ // Exits 0 when /health returns HTTP 200, exits 1 on any error or non-200 response. // // CommonJS (not ESM) — this file is copied to /app/healthcheck.js in the -// container where the monorepo root package.json (no "type":"module") applies. +// container where the repo root package.json (no "type":"module") applies. 'use strict'; const http = require('http'); diff --git a/apps/api/package.json b/apps/api/package.json index 4cc1cd5..4e92a84 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,5 +1,5 @@ { - "name": "fieldtrack-backend", + "name": "fieldtrack-api", "version": "1.0.0", "description": "FieldTrack 2.0 Backend API", "main": "dist/server.js", diff --git a/apps/api/scripts/deploy-bluegreen.sh b/apps/api/scripts/deploy-bluegreen.sh index 91c1efd..b963e6e 100644 --- a/apps/api/scripts/deploy-bluegreen.sh +++ b/apps/api/scripts/deploy-bluegreen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ============================================================================= -# deploy-bluegreen.sh — FieldTrack 2.0 Blue-Green Deployment +# deploy-bluegreen.sh — API Blue-Green Deployment # # State machine: # INIT @@ -28,7 +28,7 @@ # -> rollback succeeded -> DEPLOY_FAILED_ROLLBACK exit 1 # -> rollback failed -> DEPLOY_FAILED_FATAL exit 2 # -# Slot state file: /var/run/fieldtrack/active-slot +# Slot state file: /var/run/api/active-slot # /var/run is a tmpfs (cleared on reboot). The _ft_resolve_slot() recovery # function handles a missing file by inspecting running containers and the # live nginx config, then re-writing the file. No manual step needed after @@ -44,8 +44,8 @@ # Observability features: # DEPLOY_ID -- unique deploy identifier for log correlation (YYYYMMDD_HHMMSS_PID) # deploy_id label -- container labeled with deploy ID for instant traceability -# fieldtrack.sha -- container labeled with image SHA for quick version lookup -# fieldtrack.slot -- container labeled with slot name (blue/green) +# api.sha -- container labeled with image SHA for quick version lookup +# api.slot -- container labeled with slot name (blue/green) # duration_sec -- all exits logged with deploy duration for performance tracking # PREFLIGHT_STRICT -- optional strict mode: enforces preflight checks, fails if missing # @@ -61,7 +61,7 @@ trap '_ft_trap_err "$LINENO"' ERR # { set +x; } 2>/dev/null suppresses xtrace noise inside helpers. # --------------------------------------------------------------------------- _FT_STATE="INIT" -DEPLOY_LOG_FILE="${DEPLOY_LOG_FILE:-/var/log/fieldtrack/deploy.log}" +DEPLOY_LOG_FILE="${DEPLOY_LOG_FILE:-/var/log/api/deploy.log}" _ft_log() { { set +x; } 2>/dev/null @@ -91,8 +91,8 @@ _ft_trap_err() { _ft_snapshot() { { set +x; } 2>/dev/null printf '[DEPLOY] -- SYSTEM SNAPSHOT ----------------------------------------\n' >&2 - printf '[DEPLOY] slot_file = %s\n' "$(cat "${ACTIVE_SLOT_FILE:-/var/run/fieldtrack/active-slot}" 2>/dev/null || echo 'MISSING')" >&2 - printf '[DEPLOY] nginx_port = %s\n' "$(grep -oP 'server 127\.0\.0\.1:\K[0-9]+' "${NGINX_CONF:-/etc/nginx/sites-enabled/fieldtrack.conf}" 2>/dev/null | head -1 || echo 'unreadable')" >&2 + printf '[DEPLOY] slot_file = %s\n' "$(cat "${ACTIVE_SLOT_FILE:-/var/run/api/active-slot}" 2>/dev/null || echo 'MISSING')" >&2 + printf '[DEPLOY] nginx_port = %s\n' "$(grep -oP 'server 127\.0\.0\.1:\K[0-9]+' "${NGINX_CONF:-/etc/nginx/sites-enabled/api.conf}" 2>/dev/null | head -1 || echo 'unreadable')" >&2 printf '[DEPLOY] containers =\n' >&2 docker ps --format '[DEPLOY] {{.Names}} -> {{.Status}} ({{.Ports}})' 1>&2 2>/dev/null \ || printf '[DEPLOY] (docker ps unavailable)\n' >&2 @@ -142,28 +142,28 @@ fi # --------------------------------------------------------------------------- # CONSTANTS # --------------------------------------------------------------------------- -IMAGE="ghcr.io/fieldtrack-tech/fieldtrack-backend:${1:-latest}" +IMAGE="ghcr.io/fieldtrack-tech/api:${1:-latest}" IMAGE_SHA="${1:-latest}" -BLUE_NAME="backend-blue" -GREEN_NAME="backend-green" +BLUE_NAME="api-blue" +GREEN_NAME="api-green" BLUE_PORT=3001 GREEN_PORT=3002 APP_PORT=3000 -NETWORK="fieldtrack_network" +NETWORK="api_network" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" # Slot state directory and file. -# /var/run/fieldtrack/ is chosen over /tmp (world-writable, cleaned by tmpwatch) +# /var/run/api/ is chosen over /tmp (world-writable, cleaned by tmpwatch) # and $HOME (variable path, not auditable as runtime state). # /var/run IS a tmpfs -- the _ft_resolve_slot() recovery handles missing files. -SLOT_DIR="/var/run/fieldtrack" +SLOT_DIR="/var/run/api" ACTIVE_SLOT_FILE="$SLOT_DIR/active-slot" -NGINX_CONF="/etc/nginx/sites-enabled/fieldtrack.conf" -NGINX_TEMPLATE="$REPO_DIR/infra/nginx/fieldtrack.conf" +NGINX_CONF="/etc/nginx/sites-enabled/api.conf" +NGINX_TEMPLATE="$REPO_DIR/infra/nginx/api.conf" MAX_HISTORY=5 MAX_HEALTH_ATTEMPTS=40 HEALTH_INTERVAL=3 @@ -388,6 +388,8 @@ _ft_log "msg='environment loaded' api_hostname=$API_HOSTNAME" set +x "$SCRIPT_DIR/validate-env.sh" --check-monitoring set -x +# Harden monitoring env file permissions on every deploy (defense-in-depth). +chmod 600 "$DEPLOY_ROOT/infra/.env.monitoring" 2>/dev/null || true _ft_log "msg='env contract validated'" @@ -486,9 +488,9 @@ timeout 60 docker run -d \ --network "$NETWORK" \ -p "127.0.0.1:$INACTIVE_PORT:$APP_PORT" \ --restart unless-stopped \ - --label "fieldtrack.sha=$IMAGE_SHA" \ - --label "fieldtrack.slot=$INACTIVE" \ - --label "fieldtrack.deploy_id=$DEPLOY_ID" \ + --label "api.sha=$IMAGE_SHA" \ + --label "api.slot=$INACTIVE" \ + --label "api.deploy_id=$DEPLOY_ID" \ --env-file "$ENV_FILE" \ "$IMAGE" @@ -581,8 +583,8 @@ _ft_state "SWITCH_NGINX" "msg='switching nginx upstream' port=$INACTIVE_PORT" # Backup goes to /etc/nginx/ (NOT sites-enabled/) so nginx does not parse it # during validation and trigger a duplicate-upstream error. -NGINX_BACKUP="/etc/nginx/fieldtrack.conf.bak.$(date +%s)" -NGINX_TMP="$(mktemp /tmp/fieldtrack-nginx.XXXXXX.conf)" +NGINX_BACKUP="/etc/nginx/api.conf.bak.$(date +%s)" +NGINX_TMP="$(mktemp /tmp/api-nginx.XXXXXX.conf)" sed \ -e "s|__BACKEND_PORT__|$INACTIVE_PORT|g" \ @@ -593,7 +595,7 @@ sudo cp "$NGINX_CONF" "$NGINX_BACKUP" sudo cp "$NGINX_TMP" "$NGINX_CONF" rm -f "$NGINX_TMP" # Remove stale backups accidentally left in sites-enabled/ by old deploy runs. -sudo rm -f /etc/nginx/sites-enabled/fieldtrack.conf.bak.* +sudo rm -f /etc/nginx/sites-enabled/api.conf.bak.* if ! sudo nginx -t 2>&1; then _ft_log "level=ERROR msg='nginx config test failed -- restoring backup'" @@ -699,9 +701,9 @@ if [ "$_PUB_PASSED" != "true" ]; then fi _ft_log "msg='system degraded -- triggering rollback' container=$ACTIVE_NAME" - if [ "${FIELDTRACK_ROLLBACK_IN_PROGRESS:-0}" != "1" ]; then + if [ "${API_ROLLBACK_IN_PROGRESS:-0}" != "1" ]; then _ft_log "msg='triggering image rollback to previous stable SHA'" - export FIELDTRACK_ROLLBACK_IN_PROGRESS=1 + export API_ROLLBACK_IN_PROGRESS=1 _ft_release_lock if ! "$SCRIPT_DIR/rollback.sh" --auto; then _ft_snapshot @@ -759,8 +761,8 @@ if [ "$_STABLE" = "false" ]; then fi _ft_log "msg='triggering rollback after stability failure'" - if [ "${FIELDTRACK_ROLLBACK_IN_PROGRESS:-0}" != "1" ]; then - export FIELDTRACK_ROLLBACK_IN_PROGRESS=1 + if [ "${API_ROLLBACK_IN_PROGRESS:-0}" != "1" ]; then + export API_ROLLBACK_IN_PROGRESS=1 _ft_release_lock if ! "$SCRIPT_DIR/rollback.sh" --auto; then _ft_snapshot @@ -920,7 +922,7 @@ MONITORING_HASH=$(find "$REPO_DIR/infra" -readable \ -not -path "$REPO_DIR/infra/nginx/*" \ \( -name '*.yml' -o -name '*.yaml' -o -name '*.conf' -o -name '*.toml' -o -name '*.json' \) \ | sort | xargs -r sha256sum 2>/dev/null | sha256sum | cut -d' ' -f1 || echo "changed") -MONITORING_HASH_FILE="$HOME/.fieldtrack-monitoring-hash" +MONITORING_HASH_FILE="$HOME/.api-monitoring-hash" if [ -f "$MONITORING_HASH_FILE" ] && [ "$(cat "$MONITORING_HASH_FILE")" = "$MONITORING_HASH" ]; then _ft_log "msg='monitoring config unchanged -- skipping restart'" diff --git a/apps/api/scripts/rollback.sh b/apps/api/scripts/rollback.sh index 4c90b0b..1533a63 100644 --- a/apps/api/scripts/rollback.sh +++ b/apps/api/scripts/rollback.sh @@ -54,9 +54,9 @@ echo "" # Validate that the rollback image exists in the registry echo "Validating rollback image exists..." -if ! docker manifest inspect "ghcr.io/fieldtrack-tech/fieldtrack-backend:$PREVIOUS_SHA" >/dev/null 2>&1; then +if ! docker manifest inspect "ghcr.io/fieldtrack-tech/api:$PREVIOUS_SHA" >/dev/null 2>&1; then echo "ERROR: Rollback image not found in registry." - echo "Image: ghcr.io/fieldtrack-tech/fieldtrack-backend:$PREVIOUS_SHA" + echo "Image: ghcr.io/fieldtrack-tech/api:$PREVIOUS_SHA" echo "Cannot proceed with rollback to non-existent image." exit 1 fi @@ -80,7 +80,7 @@ echo "Starting rollback to: $PREVIOUS_SHA" echo "" # Set guard to prevent infinite rollback loops -export FIELDTRACK_ROLLBACK_IN_PROGRESS=1 +export API_ROLLBACK_IN_PROGRESS=1 # Attempt rollback deploy if ! "$SCRIPT_DIR/deploy-bluegreen.sh" "$PREVIOUS_SHA"; then @@ -93,7 +93,7 @@ if ! "$SCRIPT_DIR/deploy-bluegreen.sh" "$PREVIOUS_SHA"; then echo "SYSTEM STATE SNAPSHOT:" echo " Active containers:" docker ps --format ' {{.Names}} → {{.Status}} ({{.Ports}})' 2>/dev/null || echo " (docker ps failed)" - echo " Active slot file: $(cat "/var/run/fieldtrack/active-slot" 2>/dev/null || echo 'MISSING')" + echo " Active slot file: $(cat "/var/run/api/active-slot" 2>/dev/null || echo 'MISSING')" echo " Nginx config test: $(sudo nginx -t 2>&1)" echo "" echo "Target SHA: $PREVIOUS_SHA" @@ -101,7 +101,7 @@ if ! "$SCRIPT_DIR/deploy-bluegreen.sh" "$PREVIOUS_SHA"; then echo "Action required:" echo " 1. Check container status: docker ps -a" echo " 2. Check nginx config: sudo nginx -t" - echo " 3. Review logs: docker logs backend-blue backend-green" + echo " 3. Review logs: docker logs api-blue api-green" echo " 4. Manually restore last known good state" echo "=========================================" exit 2 diff --git a/apps/api/scripts/validate-env.sh b/apps/api/scripts/validate-env.sh index d9f17c4..04be75c 100644 --- a/apps/api/scripts/validate-env.sh +++ b/apps/api/scripts/validate-env.sh @@ -207,8 +207,8 @@ else REQUIRED_MON_VARS=( API_HOSTNAME METRICS_SCRAPE_TOKEN - FRONTEND_DOMAIN GRAFANA_ADMIN_PASSWORD + ALERTMANAGER_SLACK_WEBHOOK ) for var in "${REQUIRED_MON_VARS[@]}"; do val="$(get_val "$var" "$MONITORING_ENV_FILE")" @@ -219,6 +219,16 @@ else fi done + # Optional hardening: Slack webhook must be a valid Slack incoming webhook URL. + SLACK_WEBHOOK="$(get_val "ALERTMANAGER_SLACK_WEBHOOK" "$MONITORING_ENV_FILE")" + if [[ -z "$SLACK_WEBHOOK" ]]; then + fail "ALERTMANAGER_SLACK_WEBHOOK not set in infra/.env.monitoring" + elif [[ ! "$SLACK_WEBHOOK" =~ ^https://hooks.slack.com/ ]]; then + fail "ALERTMANAGER_SLACK_WEBHOOK is not a valid Slack webhook URL" + else + pass "ALERTMANAGER_SLACK_WEBHOOK is valid" + fi + # Cross-check 1: API_HOSTNAME must match the hostname derived from API_BASE_URL MON_HOSTNAME="$(get_val "API_HOSTNAME" "$MONITORING_ENV_FILE")" if [[ -n "$DERIVED_HOSTNAME" && -n "$MON_HOSTNAME" ]]; then diff --git a/apps/api/scripts/verify-stabilization.sh b/apps/api/scripts/verify-stabilization.sh index 1cc3d00..c594c95 100644 --- a/apps/api/scripts/verify-stabilization.sh +++ b/apps/api/scripts/verify-stabilization.sh @@ -190,14 +190,14 @@ echo "" echo "TEST 5: Rollback guard" echo "---------------------" -if grep -q "FIELDTRACK_ROLLBACK_IN_PROGRESS" "$SCRIPT_DIR/rollback.sh"; then - pass "rollback.sh sets FIELDTRACK_ROLLBACK_IN_PROGRESS guard" +if grep -q "API_ROLLBACK_IN_PROGRESS" "$SCRIPT_DIR/rollback.sh"; then + pass "rollback.sh sets API_ROLLBACK_IN_PROGRESS guard" else fail "rollback.sh missing rollback guard" fi -if grep -q "FIELDTRACK_ROLLBACK_IN_PROGRESS" "$SCRIPT_DIR/deploy-bluegreen.sh"; then - pass "deploy-bluegreen.sh checks FIELDTRACK_ROLLBACK_IN_PROGRESS" +if grep -q "API_ROLLBACK_IN_PROGRESS" "$SCRIPT_DIR/deploy-bluegreen.sh"; then + pass "deploy-bluegreen.sh checks API_ROLLBACK_IN_PROGRESS" else fail "deploy-bluegreen.sh missing rollback guard check" fi diff --git a/apps/api/scripts/vps-setup.sh b/apps/api/scripts/vps-setup.sh index 520fada..63b5f6b 100644 --- a/apps/api/scripts/vps-setup.sh +++ b/apps/api/scripts/vps-setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================ -# FieldTrack 2.0 — VPS Setup Script +# FieldTrack API — VPS Setup Script # ============================================================================ # # Deterministic first-time setup for a fresh Ubuntu 22.04/24.04 VPS. @@ -21,15 +21,14 @@ set -euo pipefail # ── Configuration (EDIT THESE) ───────────────────────────────────────────────── DOMAIN="api.getfieldtrack.app" # Production API domain -FRONTEND_DOMAIN="app.getfieldtrack.app" # Production frontend domain GH_USER="fieldtrack-tech" # GitHub org name GH_PAT="" # GitHub Personal Access Token (packages:read) DEPLOY_USER="ashish" # Non-root user for deployment DEPLOY_USER_SSH_PUBLIC_KEY="" # Required public key for deploy user (ssh-ed25519 ...) -REPO_URL="https://github.com/fieldtrack-tech/fieldtrack-2.0.git" -REPO_DIR="/home/${DEPLOY_USER}/FieldTrack-2.0" -NETWORK="fieldtrack_network" -NGINX_SITE_LINK="/etc/nginx/conf.d/fieldtrack.conf" +REPO_URL="https://github.com/fieldtrack-tech/api.git" +REPO_DIR="/api" +NETWORK="api_network" +NGINX_SITE_LINK="/etc/nginx/conf.d/api.conf" # ── Colour output ───────────────────────────────────────────────────────────── GREEN='\033[0;32m' @@ -43,14 +42,13 @@ err() { echo -e "${RED}[✗]${NC} $1"; exit 1; } render_nginx_ssl_config() { local target_file="$1" - cp "$REPO_DIR/infra/nginx/fieldtrack.conf" "$target_file" + cp "$REPO_DIR/infra/nginx/api.conf" "$target_file" sed -i "s|__API_HOSTNAME__|$DOMAIN|g" "$target_file" - sed -i "s|__FRONTEND_DOMAIN__|$FRONTEND_DOMAIN|g" "$target_file" } echo "" echo "=============================================" -echo " FieldTrack 2.0 — VPS Setup" +echo " FieldTrack API — VPS Setup" echo "=============================================" echo "" @@ -274,7 +272,7 @@ rm -f /etc/nginx/conf.d/default mkdir -p /var/www/certbot -BOOTSTRAP_NGINX_CONF="/tmp/fieldtrack-bootstrap-http.conf" +BOOTSTRAP_NGINX_CONF="/tmp/api-bootstrap-http.conf" cat > "$BOOTSTRAP_NGINX_CONF" << EOF server { listen 80; @@ -284,7 +282,7 @@ server { root /var/www/certbot; } location / { - return 200 'FieldTrack bootstrap HTTP mode'; + return 200 'API bootstrap HTTP mode'; add_header Content-Type text/plain; } } @@ -362,7 +360,7 @@ log "Monitoring stack started (Prometheus, Grafana, Node Exporter)" log "Phase 15: Pulling and starting initial backend container..." # Pull the latest image -sudo -u "$DEPLOY_USER" docker pull ghcr.io/fieldtrack-tech/fieldtrack-backend:latest +sudo -u "$DEPLOY_USER" docker pull ghcr.io/fieldtrack-tech/api:latest # Start the blue container as initial deployment if [ -f "$ENV_FILE" ] && grep -q "SUPABASE_URL=your-" "$ENV_FILE"; then @@ -371,14 +369,14 @@ if [ -f "$ENV_FILE" ] && grep -q "SUPABASE_URL=your-" "$ENV_FILE"; then warn " cd $REPO_DIR/apps/api && ./scripts/deploy-bluegreen.sh latest" else sudo -u "$DEPLOY_USER" docker run -d \ - --name backend-blue \ + --name api-blue \ --network "$NETWORK" \ -p "127.0.0.1:3001:3000" \ --restart unless-stopped \ --env-file "$ENV_FILE" \ - ghcr.io/fieldtrack-tech/fieldtrack-backend:latest + ghcr.io/fieldtrack-tech/api:latest - log "Backend container (backend-blue) started on 127.0.0.1:3001." + log "Backend container (api-blue) started on 127.0.0.1:3001." fi # ============================================================================ @@ -400,7 +398,7 @@ log "SSH hardened (root login disabled, password auth disabled)." # ============================================================================ echo "" echo "=============================================" -echo " FieldTrack 2.0 — VPS Setup Complete" +echo " FieldTrack API — VPS Setup Complete" echo "=============================================" echo "" echo " Next steps:" diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts index 18c64bf..95f438f 100644 --- a/apps/api/src/config/env.ts +++ b/apps/api/src/config/env.ts @@ -1,3 +1,12 @@ +// ⚠️ DO NOT add env variables without updating ALL of the following: +// - apps/api/.env.example (developer template — every variable must appear here) +// - apps/api/.env.ci (CI non-secret defaults, if the variable is CI-relevant) +// - docs/env-contract.md (source-of-truth documentation table) +// - apps/api/scripts/validate-env.sh (if monitoring-layer or cross-file validation needed) +// +// Failure to keep these in sync causes config drift, silent CI failures, and +// environment contract violations caught only at production deploy time. + /** * env.ts — Centralized, Zod-validated environment configuration for FieldTrack 2.0. * @@ -52,6 +61,46 @@ const optionalBaseUrl = z.preprocess( z.string().url().optional(), ); +/** + * Zod schema for FRONTEND_BASE_URL — the canonical URL of the web frontend + * (fieldtrack-tech/web). + * + * Validation rules (enforced at process startup): + * 1. Must be a valid absolute URL (http:// or https://). + * 2. Must NOT end with a trailing slash — normalizeUrl strips one if present, + * and the explicit refine below asserts the contract is visible in code. + * 3. Optional outside production — required in production via superRefine. + * + * Used ONLY for: + * - Password-reset email links: `${FRONTEND_BASE_URL}/reset-password?token=…` + * - Invitation email links: `${FRONTEND_BASE_URL}/accept-invite?token=…` + * - User-facing redirects that land the user in the UI + * + * NOT used for: + * - API routing or CORS (use CORS_ORIGIN for that) + * - Internal service-to-service calls + * - Server-side proxy targets + */ +const frontendBaseUrl = z.preprocess( + (val) => + typeof val === "string" && val.trim().length > 0 + ? normalizeUrl(val.trim()) + : undefined, + z + .string() + .url({ message: "FRONTEND_BASE_URL must be a valid absolute URL (e.g. https://app.getfieldtrack.app)" }) + .refine( + (url) => !url.endsWith("/"), + { + message: + "FRONTEND_BASE_URL must not end with a trailing slash. " + + "Correct: https://app.getfieldtrack.app — " + + "Incorrect: https://app.getfieldtrack.app/", + }, + ) + .optional(), +); + // ─── Schema ─────────────────────────────────────────────────────────────────── const envSchema = z @@ -83,7 +132,7 @@ const envSchema = z * Falls back to NODE_ENV for backward compatibility with existing deployments * that only set NODE_ENV (e.g. the Dockerfile bakes NODE_ENV=production). * - * Valid values: development | staging | production | test + * Valid values: development | staging | production | test | ci */ APP_ENV: z.preprocess( (val) => @@ -156,9 +205,9 @@ const envSchema = z * Required in production — startup fails if absent when APP_ENV=production. * * Format: full URL including protocol, no trailing slash. - * Example: https://app.fieldtrack.com + * Example: https://app.getfieldtrack.app */ - FRONTEND_BASE_URL: optionalBaseUrl, + FRONTEND_BASE_URL: frontendBaseUrl, // ── CORS ────────────────────────────────────────────────────────────────── @@ -237,7 +286,7 @@ const envSchema = z /** * OTLP HTTP endpoint for exporting traces to Grafana Tempo. * - * Default resolves via Docker service name on fieldtrack_network. + * Default resolves via Docker service name on api_network. * Override for non-Docker deployments, external Tempo, or cloud OTLP ingest. * * Note: bare Docker service hostnames (e.g. "tempo") are intentionally @@ -263,7 +312,7 @@ const envSchema = z * Appears as the service label in Grafana Tempo / service graph. * Change per deployment if running multiple environments in one Tempo instance. */ - SERVICE_NAME: z.string().default("fieldtrack-backend"), + SERVICE_NAME: z.string().default("fieldtrack-api"), /** * Git commit SHA injected by GitHub Actions. @@ -446,7 +495,7 @@ const envSchema = z }); } - // 4. Open CORS in production leaks credentials to any origin. + // 3. Open CORS in production leaks credentials to any origin. if (isProd && !data.CORS_ORIGIN.trim()) { ctx.addIssue({ code: "custom", @@ -458,7 +507,7 @@ const envSchema = z }); } - // 5. APP_BASE_URL is the canonical root for all absolute link generation. + // 4. APP_BASE_URL is the canonical root for all absolute link generation. if (isProd && !data.APP_BASE_URL) { ctx.addIssue({ code: "custom", @@ -470,7 +519,7 @@ const envSchema = z }); } - // 6. API_BASE_URL is needed for accurate OpenAPI documentation and any + // 5. API_BASE_URL is needed for accurate OpenAPI documentation and any // server-generated absolute links that reference the API itself. if (isProd && !data.API_BASE_URL) { ctx.addIssue({ @@ -483,7 +532,7 @@ const envSchema = z }); } - // 7. FRONTEND_BASE_URL is required in production for email link generation. + // 6. FRONTEND_BASE_URL is required in production for email link generation. // Reset-password and invitation emails become broken without it. if (isProd && !data.FRONTEND_BASE_URL) { ctx.addIssue({ @@ -496,7 +545,7 @@ const envSchema = z }); } - // 8. WORKERS_ENABLED must be explicitly true in production. + // 7. WORKERS_ENABLED must be explicitly true in production. // Production always has Redis provisioned; a mis-configured production // container that skips workers silently would be a serious oversight. if (isProd && !data.WORKERS_ENABLED) { diff --git a/apps/api/src/tracing.ts b/apps/api/src/tracing.ts index 1c7b424..1104b88 100644 --- a/apps/api/src/tracing.ts +++ b/apps/api/src/tracing.ts @@ -5,7 +5,7 @@ // // Traces are shipped via OTLP HTTP to Grafana Tempo on the shared Docker // network. View them in: Grafana → Explore → Tempo → Search traces -// Filter by service.name = env.SERVICE_NAME (default: "fieldtrack-backend") +// Filter by service.name = env.SERVICE_NAME (default: "fieldtrack-api") import { NodeSDK } from "@opentelemetry/sdk-node"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; diff --git a/apps/api/src/utils/dedup.ts b/apps/api/src/utils/dedup.ts deleted file mode 100644 index 577a5e7..0000000 --- a/apps/api/src/utils/dedup.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Phase 22: In-flight request deduplication. - * - * Prevents cache-miss stampedes on expensive endpoints (e.g. /admin/dashboard) - * when multiple identical requests arrive concurrently before the first one - * has written its result to Redis. - * - * Usage: - * const data = await deduped(`dashboard:${orgId}`, () => expensiveQuery()); - * - * Guarantees: - * - Only ONE underlying call executes for a given key at any instant. - * - All concurrent callers resolve to the SAME promise (same data, same timing). - * - The key is removed from the map as soon as the promise settles (resolve OR reject), - * so the next caller after the first finishes starts a fresh execution. - * - Errors propagate to all waiters; they are not swallowed. - * - * Thread-safety note: Node.js is single-threaded, so the map read-check-set - * sequence is atomic and race-free within a single process. - */ - -// Module-level singleton — shared across all callers in this process. -const inflight = new Map>(); - -/** - * Return the in-flight promise for `key` if one exists; otherwise call `fn`, - * store the resulting promise under `key`, and return it. - */ -export function deduped(key: string, fn: () => Promise): Promise { - const existing = inflight.get(key); - if (existing !== undefined) return existing as Promise; - - const promise = fn().finally(() => { - inflight.delete(key); - }); - - inflight.set(key, promise); - return promise; -} diff --git a/apps/api/tests/integration/profile/profile.test.ts b/apps/api/tests/integration/profile/profile.test.ts index 303f058..2c15eef 100644 --- a/apps/api/tests/integration/profile/profile.test.ts +++ b/apps/api/tests/integration/profile/profile.test.ts @@ -20,6 +20,7 @@ vi.mock("../../../src/modules/profile/profile.repository.js", () => ({ getEmployeeById: vi.fn(), getEmployeeStats: vi.fn(), getEmployeeExpenseStats: vi.fn(), + getMetricsSnapshot: vi.fn().mockResolvedValue(null), updateLastActivity: vi.fn().mockResolvedValue(undefined), }, computeActivityStatusFromTimestamp: vi.fn().mockReturnValue("ACTIVE"), diff --git a/apps/api/tests/integration/security/auth-validation.test.ts b/apps/api/tests/integration/security/auth-validation.test.ts index 3598bc9..9d3fd83 100644 --- a/apps/api/tests/integration/security/auth-validation.test.ts +++ b/apps/api/tests/integration/security/auth-validation.test.ts @@ -32,6 +32,7 @@ vi.mock("../../../src/modules/expenses/expenses.repository.js", () => ({ findExpenseById: vi.fn(), findExpensesByUser: vi.fn(), findExpensesByOrg: vi.fn(), + findPendingFromSnapshot: vi.fn().mockResolvedValue({ data: [], total: 0, source: "fallback" }), updateExpenseStatus: vi.fn(), findExpenseSummaryByEmployee: vi.fn(), }, diff --git a/apps/api/tests/integration/security/tenant-isolation.test.ts b/apps/api/tests/integration/security/tenant-isolation.test.ts index eeb117c..1311d1b 100644 --- a/apps/api/tests/integration/security/tenant-isolation.test.ts +++ b/apps/api/tests/integration/security/tenant-isolation.test.ts @@ -33,6 +33,7 @@ vi.mock("../../../src/modules/expenses/expenses.repository.js", () => ({ findExpenseById: vi.fn(), findExpensesByUser: vi.fn(), findExpensesByOrg: vi.fn(), + findPendingFromSnapshot: vi.fn().mockResolvedValue({ data: [], total: 0, source: "fallback" }), updateExpenseStatus: vi.fn(), findExpenseSummaryByEmployee: vi.fn(), }, diff --git a/apps/api/tests/unit/utils/config-hardening.test.ts b/apps/api/tests/unit/utils/config-hardening.test.ts index 344a0de..d67a551 100644 --- a/apps/api/tests/unit/utils/config-hardening.test.ts +++ b/apps/api/tests/unit/utils/config-hardening.test.ts @@ -13,6 +13,8 @@ * 8. privateEnv — contains secret fields; publicEnv does NOT * 9. logStartupConfig — calls logger.info exactly once with safe payload * 10. APP_BASE_URL — optional field present in schema, required in production + * 11. General env object invariants + * 12. FRONTEND_BASE_URL — trailing-slash contract and usage rules * * These tests are intentionally isolated from the database, Redis, and any * external services. They operate purely on plain functions and Zod schemas. @@ -693,3 +695,108 @@ describe("env object — general invariants", () => { expect(env.WORKER_CONCURRENCY).toBeGreaterThanOrEqual(1); }); }); + +// ───────────────────────────────────────────────────────────────────────────── +// 12. FRONTEND_BASE_URL — trailing-slash contract and usage rules +// ───────────────────────────────────────────────────────────────────────────── +// +// FRONTEND_BASE_URL is the single canonical variable for the web frontend URL +// (fieldtrack-tech/web). Used exclusively to build email links and +// user-facing redirects — NOT for CORS, API routing, or service-to-service. +// +// Validation rules (defined in env.ts via the frontendBaseUrl schema): +// 1. Must be a valid absolute URL (http:// or https://) +// 2. Must NOT end with a trailing slash +// 3. Trailing slashes are stripped by normalizeUrl in the preprocessor +// 4. Optional in non-production; required in production via superRefine + +describe("FRONTEND_BASE_URL — trailing-slash contract", () => { + // Mirror the frontendBaseUrl schema from env.ts to test it in isolation. + const frontendBaseUrl = z.preprocess( + (val) => + typeof val === "string" && val.trim().length > 0 + ? normalizeUrl(val.trim()) + : undefined, + z + .string() + .url() + .refine( + (url) => !url.endsWith("/"), + { message: "FRONTEND_BASE_URL must not end with a trailing slash" }, + ) + .optional(), + ); + + it("accepts a clean production URL", () => { + expect(frontendBaseUrl.parse("https://app.getfieldtrack.app")).toBe( + "https://app.getfieldtrack.app", + ); + }); + + it("strips a trailing slash and the refine passes", () => { + expect(frontendBaseUrl.parse("https://app.getfieldtrack.app/")).toBe( + "https://app.getfieldtrack.app", + ); + }); + + it("strips multiple trailing slashes and the refine passes", () => { + expect(frontendBaseUrl.parse("https://app.getfieldtrack.app///")).toBe( + "https://app.getfieldtrack.app", + ); + }); + + it("accepts a localhost URL in dev/test", () => { + expect(frontendBaseUrl.parse("http://localhost:3000")).toBe( + "http://localhost:3000", + ); + }); + + it("strips trailing slash from localhost URL", () => { + expect(frontendBaseUrl.parse("http://localhost:3000/")).toBe( + "http://localhost:3000", + ); + }); + + it("yields undefined for an empty string (unset in dev/CI)", () => { + expect(frontendBaseUrl.parse("")).toBeUndefined(); + }); + + it("yields undefined for a whitespace-only string", () => { + expect(frontendBaseUrl.parse(" ")).toBeUndefined(); + }); + + it("yields undefined for undefined (unset in dev/CI)", () => { + expect(frontendBaseUrl.parse(undefined)).toBeUndefined(); + }); + + it("rejects a non-URL string", () => { + expect(() => frontendBaseUrl.parse("not-a-url")).toThrow(); + }); + + it("path concatenation is safe after normalisation — no double slash", () => { + const base = frontendBaseUrl.parse("https://app.getfieldtrack.app/") as string; + const resetLink = `${base}/reset-password?token=abc`; + expect(resetLink).toBe("https://app.getfieldtrack.app/reset-password?token=abc"); + expect(resetLink).not.toContain("//reset"); + }); + + it("path concatenation is safe for invite links", () => { + const base = frontendBaseUrl.parse("https://app.getfieldtrack.app") as string; + const inviteLink = `${base}/accept-invite?token=xyz`; + expect(inviteLink).toBe("https://app.getfieldtrack.app/accept-invite?token=xyz"); + expect(inviteLink).not.toContain("//accept"); + }); + + it("live env.FRONTEND_BASE_URL does not end with a trailing slash (if set)", () => { + if (env.FRONTEND_BASE_URL !== undefined) { + expect(env.FRONTEND_BASE_URL).not.toMatch(/\/$/); + } + }); + + it("live env.FRONTEND_BASE_URL is accessible and is string | undefined", () => { + expect( + env.FRONTEND_BASE_URL === undefined || typeof env.FRONTEND_BASE_URL === "string", + ).toBe(true); + }); +}); + diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index fe47189..0000000 --- a/apps/web/.env.example +++ /dev/null @@ -1,47 +0,0 @@ -# ============================================ -# Frontend Environment Variables -# ============================================ -# Copy this file to .env.local and fill in the values -# .env.local is gitignored and will not be committed - -# ---------------- -# Backend API -# ---------------- -# Controls how the browser reaches the backend API. -# -# Mode A — Direct (recommended for Vercel / production): -# Set to the full API URL so the browser calls it directly. -NEXT_PUBLIC_API_BASE_URL=http://localhost:3001 -# -# Mode B — Server-side proxy (avoids CORS, hides API origin from browser): -# Set to /api/proxy so all browser traffic routes through Next.js. -# You MUST also set API_DESTINATION_URL (below) — Next.js will forward -# /api/proxy/:path* → /:path* on the server. -# NEXT_PUBLIC_API_BASE_URL=/api/proxy -# -# IMPORTANT: Variable was renamed from NEXT_PUBLIC_API_URL to -# NEXT_PUBLIC_API_BASE_URL. If your Vercel project still uses the old name, -# update it in Vercel project settings or it will be picked up automatically -# as a fallback for one release cycle. - -# Server-side proxy destination (only needed when Mode B is active above). -# This is NOT baked into the client JS — it is read only by the Next.js server. -# API_DESTINATION_URL=https://api.getfieldtrack.app - -# ---------------- -# Supabase -# ---------------- -# Supabase project URL -# Find this in your Supabase project settings -NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co - -# Supabase anonymous/public key -# Find this in your Supabase project settings under API -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key - -# ---------------- -# Mapbox -# ---------------- -# Mapbox access token for map rendering -# Get this from https://account.mapbox.com/access-tokens/ -NEXT_PUBLIC_MAPBOX_TOKEN=your-mapbox-token diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json deleted file mode 100644 index b896beb..0000000 --- a/apps/web/.eslintrc.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "rules": { - "no-restricted-imports": [ - "error", - { - "paths": [ - { - "name": "axios", - "message": "Do not use axios directly. Use the API client (apiGet/apiPost/apiPatch) from @/lib/api/client instead." - }, - { - "name": "node-fetch", - "message": "Do not use node-fetch directly. Use the API client from @/lib/api/client instead." - }, - { - "name": "ky", - "message": "Do not use ky directly. Use the API client from @/lib/api/client instead." - } - ] - } - ], - "no-restricted-syntax": [ - "error", - { - "selector": "CallExpression[callee.name='fetch'] > Literal[value=/^\\/(?!api\\/proxy)/]", - "message": "Do not call fetch() with a relative path. Use the API client (apiGet/apiPost/apiPatch) from @/lib/api/client instead." - }, - { - "selector": "CallExpression[callee.name='fetch'] > TemplateLiteral > TemplateElement[value.cooked=/^\\/(?!api\\/proxy)/]", - "message": "Do not call fetch() with a relative path. Use the API client (apiGet/apiPost/apiPatch) from @/lib/api/client instead." - } - ] - } -} diff --git a/apps/web/README.md b/apps/web/README.md deleted file mode 100644 index 7b4adcc..0000000 --- a/apps/web/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# FieldTrack 2.0 — Frontend - -Modern Next.js 15 frontend for the FieldTrack 2.0 workforce management platform. - -## Tech Stack - -- **Framework**: Next.js 15 (App Router) -- **Language**: TypeScript 5.9 -- **Styling**: TailwindCSS 4 -- **UI Components**: shadcn/ui -- **Maps**: Mapbox GL JS -- **Auth**: Supabase Auth -- **State Management**: React Query (TanStack Query) -- **Forms**: React Hook Form + Zod validation - -## Quick Start - -```bash -# Install dependencies -npm install - -# Copy environment template and fill in values -cp .env.example .env.local - -# Start development server -npm run dev -``` - -The app will be available at `http://localhost:3000`. - -## Environment Variables - -Required environment variables (see `.env.example`): - -- `NEXT_PUBLIC_API_BASE_URL`: Backend API URL - - Development: `http://localhost:3001` - - Production: `https://api.fieldtrack.meowsician.tech` -- `NEXT_PUBLIC_SUPABASE_URL`: Supabase project URL -- `NEXT_PUBLIC_SUPABASE_ANON_KEY`: Supabase anonymous key -- `NEXT_PUBLIC_MAPBOX_TOKEN`: Mapbox access token - -## Scripts - -| Command | Description | -|---------|-------------| -| `npm run dev` | Start development server with hot reload | -| `npm run build` | Build production bundle | -| `npm start` | Run production server | -| `npm run lint` | Run ESLint | -| `npm run type-check` | Run TypeScript compiler check | - -## Project Structure - -``` -src/ -├── app/ # Next.js App Router pages -│ ├── (auth)/ # Authentication pages (login, signup) -│ ├── (dashboard)/ # Protected dashboard pages -│ └── layout.tsx # Root layout -├── components/ # React components -│ ├── ui/ # shadcn/ui components -│ └── ... # Feature components -├── lib/ # Utilities and configurations -│ ├── api/ # API client and hooks -│ ├── env.ts # Environment variable validation -│ └── utils.ts # Shared utilities -└── types/ # TypeScript type definitions -``` - -## Architecture - -### Domain Architecture - -- **Frontend**: `https://fieldtrack.meowsician.tech` -- **API**: `https://api.fieldtrack.meowsician.tech` - -### API Communication - -The frontend communicates with the backend API using environment-based configuration: - -- **Local Development**: Direct connection to `http://localhost:3001` -- **Production**: Uses Next.js API proxy (`/api/proxy/*`) to avoid CORS issues - -The API client (`src/lib/api/client.ts`) automatically uses the `NEXT_PUBLIC_API_BASE_URL` environment variable. - -## Deployment - -The frontend is deployed to Vercel with automatic deployments on push to `master`. - -### Environment Variables (Vercel) - -Set these in your Vercel project settings: - -```bash -NEXT_PUBLIC_API_BASE_URL=/api/proxy -NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key -NEXT_PUBLIC_MAPBOX_TOKEN=your-mapbox-token -``` - -Note: In production, `NEXT_PUBLIC_API_BASE_URL` should be set to `/api/proxy` to use the Next.js server-side proxy, which avoids CORS preflight requests. - -## Features - -- **Authentication**: Secure login/signup with Supabase Auth -- **Dashboard**: Real-time workforce overview and analytics -- **Attendance Tracking**: Check-in/check-out with GPS location -- **Expense Management**: Submit and track expense claims -- **Location Tracking**: Real-time GPS tracking on interactive maps -- **Admin Analytics**: Organization-wide insights and reports -- **Responsive Design**: Mobile-first, works on all devices - -## Documentation - -For more information, see: - -- [Project Structure](STRUCTURE.md) - Detailed frontend architecture -- [API Reference](../docs/API_REFERENCE.md) - Backend API documentation -- [Architecture](../docs/ARCHITECTURE.md) - System design and data flows - -## License - -[MIT](../../LICENSE) © 2026 Ashish Raj diff --git a/apps/web/STRUCTURE.md b/apps/web/STRUCTURE.md deleted file mode 100644 index e95ca49..0000000 --- a/apps/web/STRUCTURE.md +++ /dev/null @@ -1,205 +0,0 @@ -# Frontend Structure Chart - -## Overview -Next.js 14+ application with App Router, TypeScript, TailwindCSS, and React Query for state management. - -## Directory Structure - -``` -frontend/ -├── .next/ # Next.js build output (auto-generated) -├── node_modules/ # Dependencies (auto-generated) -├── public/ # Static assets -├── src/ # Source code -│ ├── app/ # Next.js App Router pages -│ │ ├── (protected)/ # Protected routes (requires auth) -│ │ │ ├── analytics/ # Analytics dashboard -│ │ │ ├── attendance/ # Attendance management -│ │ │ ├── expenses/ # Expense tracking -│ │ │ ├── locations/ # Location management -│ │ │ └── layout.tsx # Protected layout wrapper -│ │ ├── login/ # Login page -│ │ │ └── page.tsx -│ │ ├── globals.css # Global styles -│ │ ├── layout.tsx # Root layout -│ │ ├── page.tsx # Home/landing page -│ │ └── providers.tsx # App-level providers -│ │ -│ ├── components/ # Reusable components -│ │ ├── charts/ # Chart components (Recharts) -│ │ │ ├── AttendanceChart.tsx -│ │ │ ├── ExpenseChart.tsx -│ │ │ └── ... -│ │ ├── layout/ # Layout components -│ │ │ ├── Header.tsx -│ │ │ ├── Sidebar.tsx -│ │ │ └── ... -│ │ ├── maps/ # Map components (Mapbox) -│ │ │ ├── AttendanceMap.tsx -│ │ │ ├── LocationMap.tsx -│ │ │ └── ... -│ │ ├── tables/ # Table components -│ │ │ ├── AttendanceTable.tsx -│ │ │ ├── ExpenseTable.tsx -│ │ │ └── ... -│ │ ├── ui/ # shadcn/ui components -│ │ │ ├── button.tsx -│ │ │ ├── card.tsx -│ │ │ ├── dialog.tsx -│ │ │ ├── dropdown-menu.tsx -│ │ │ ├── input.tsx -│ │ │ ├── label.tsx -│ │ │ ├── select.tsx -│ │ │ ├── separator.tsx -│ │ │ ├── tabs.tsx -│ │ │ ├── toast.tsx -│ │ │ ├── toaster.tsx -│ │ │ └── use-toast.ts -│ │ ├── EmptyState.tsx # Empty state component -│ │ ├── ErrorBanner.tsx # Error display component -│ │ └── LoadingSkeleton.tsx # Loading state component -│ │ -│ ├── contexts/ # React contexts -│ │ └── AuthContext.tsx # Authentication context -│ │ -│ ├── hooks/ # Custom React hooks -│ │ ├── queries/ # React Query hooks -│ │ │ ├── useAnalytics.ts -│ │ │ ├── useAttendance.ts -│ │ │ ├── useExpenses.ts -│ │ │ └── useLocations.ts -│ │ └── useAuth.ts # Authentication hook -│ │ -│ ├── lib/ # Utility libraries -│ │ ├── api.ts # API client configuration -│ │ ├── env.ts # Environment variables -│ │ ├── permissions.ts # Permission utilities -│ │ ├── query-client.ts # React Query client -│ │ ├── supabase.ts # Supabase client -│ │ └── utils.ts # General utilities -│ │ -│ └── types/ # TypeScript type definitions -│ └── index.ts # Shared types -│ -├── .eslintrc.json # ESLint configuration -├── .gitignore # Git ignore rules -├── components.json # shadcn/ui configuration -├── next.config.mjs # Next.js configuration -├── next-env.d.ts # Next.js TypeScript declarations -├── package.json # Dependencies and scripts -├── package-lock.json # Dependency lock file -├── postcss.config.mjs # PostCSS configuration -├── README.md # Frontend documentation -├── STRUCTURE.md # This file -├── tailwind.config.ts # TailwindCSS configuration -└── tsconfig.json # TypeScript configuration - -``` - -## Key Technologies - -### Core Framework -- **Next.js 14+**: React framework with App Router -- **React 18**: UI library -- **TypeScript**: Type safety - -### Styling -- **TailwindCSS**: Utility-first CSS framework -- **shadcn/ui**: Reusable component library -- **Radix UI**: Headless UI primitives - -### State Management -- **React Query (@tanstack/react-query)**: Server state management -- **React Context**: Client state management - -### Data Visualization -- **Recharts**: Chart library -- **Mapbox GL**: Interactive maps - -### Authentication -- **Supabase**: Authentication and database client - -### Development Tools -- **ESLint**: Code linting -- **PostCSS**: CSS processing -- **Autoprefixer**: CSS vendor prefixing - -## Routing Structure - -``` -/ # Landing page -/login # Login page -/(protected)/ # Protected routes (requires authentication) - ├── /analytics # Analytics dashboard - ├── /attendance # Attendance management - ├── /expenses # Expense tracking - └── /locations # Location management -``` - -## Component Architecture - -### UI Components (shadcn/ui) -Reusable, accessible components built on Radix UI primitives: -- Buttons, Inputs, Labels -- Cards, Dialogs, Dropdowns -- Tabs, Toasts, Separators - -### Feature Components -Domain-specific components organized by feature: -- **Charts**: Data visualization components -- **Maps**: Geographic visualization components -- **Tables**: Data table components -- **Layout**: Navigation and structure components - -### State Components -- **EmptyState**: Display when no data available -- **ErrorBanner**: Error message display -- **LoadingSkeleton**: Loading state placeholders - -## Data Flow - -1. **Authentication**: Supabase Auth → AuthContext → Protected Routes -2. **API Calls**: React Query hooks → API client → Backend -3. **State Management**: React Query cache + React Context -4. **UI Updates**: Query invalidation → Automatic refetch → UI update - -## Environment Variables - -Required environment variables (see `.env.example`): -- `NEXT_PUBLIC_API_BASE_URL`: Backend API URL -- `NEXT_PUBLIC_SUPABASE_URL`: Supabase project URL -- `NEXT_PUBLIC_SUPABASE_ANON_KEY`: Supabase anonymous key - -## Build & Development - -### Development -```bash -npm run dev # Start development server (port 3000) -npm run lint # Run ESLint -``` - -### Production -```bash -npm run build # Build for production -npm start # Start production server -``` - -## Code Organization Principles - -1. **Feature-based organization**: Components grouped by feature/domain -2. **Separation of concerns**: UI, logic, and data layers separated -3. **Reusability**: Shared components in `components/ui/` -4. **Type safety**: TypeScript throughout -5. **Server state**: Managed by React Query -6. **Client state**: Managed by React Context when needed - -## Best Practices - -- Use React Query for all server state -- Keep components small and focused -- Use TypeScript for type safety -- Follow Next.js App Router conventions -- Use shadcn/ui components for consistency -- Implement proper error boundaries -- Use loading states for better UX -- Follow accessibility guidelines (WCAG) diff --git a/apps/web/components.json b/apps/web/components.json deleted file mode 100644 index dba50c6..0000000 --- a/apps/web/components.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "src/app/globals.css", - "baseColor": "slate", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs deleted file mode 100644 index 6173a49..0000000 --- a/apps/web/next.config.mjs +++ /dev/null @@ -1,115 +0,0 @@ -/** @type {import('next').NextConfig} */ - -const isDev = process.env.NODE_ENV === 'development'; - -// NEXT_PUBLIC_API_BASE_URL controls how the browser reaches the backend. -// -// Mode A — Direct (recommended for Vercel): -// NEXT_PUBLIC_API_BASE_URL=https://api.getfieldtrack.app -// Browser calls the API directly; no server-side proxy is involved. -// -// Mode B — Server-side proxy (avoids CORS, hides API origin from browser): -// NEXT_PUBLIC_API_BASE_URL=/api/proxy -// API_DESTINATION_URL=https://api.getfieldtrack.app ← server-only, never baked into JS -// Browser calls /api/proxy/:path*, Next.js rewrites to API_DESTINATION_URL/:path*. -// API_DESTINATION_URL MUST be set when using proxy mode or the rewrite has no destination. -// -// CI placeholder builds may use NEXT_PUBLIC_API_BASE_URL=/api/proxy without -// API_DESTINATION_URL — this is fine because no real requests are made during `next build`. - -const NEXT_PUBLIC_API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? ""; -const API_DESTINATION_URL = process.env.API_DESTINATION_URL ?? ""; - -const apiIsFullUrl = /^https?:\/\//.test(NEXT_PUBLIC_API_BASE_URL); -const destinationIsFullUrl = /^https?:\/\//.test(API_DESTINATION_URL); - -// In direct mode, extract the origin from NEXT_PUBLIC_API_BASE_URL. -// In proxy mode, extract it from API_DESTINATION_URL (server-only var). -// Used for CSP and as the rewrite destination. -const apiOrigin = apiIsFullUrl - ? new URL(NEXT_PUBLIC_API_BASE_URL).origin - : destinationIsFullUrl - ? new URL(API_DESTINATION_URL).origin - : ""; - -const nextConfig = { - transpilePackages: ["mapbox-gl", "@fieldtrack/types"], - images: { - domains: [], - // Mitigate GHSA-3x4c-7xq6-9pq8 (unbounded Next.js image disk cache growth). - // Limit format variants and enforce TTL so stale image cache entries expire. - // Full fix: upgrade to next@>=16.1.7 when breaking changes are reviewed. - formats: ["image/webp"], - minimumCacheTTL: 3600, - }, - async headers() { - const connectSources = [ - "'self'", - "https://*.supabase.co", // Supabase auth, realtime, storage - "https://*.tiles.mapbox.com", // Mapbox raster / vector tiles - "https://api.mapbox.com", // Mapbox geocoding, directions, styles - "https://events.mapbox.com", // Mapbox telemetry - "https://*.tile.openstreetmap.org", // Leaflet / OpenStreetMap tiles - ]; - // Only add the API origin when it is a full URL — same-origin requests - // (/api/proxy path) are already covered by 'self' above. - // In proxy mode, apiOrigin is derived from API_DESTINATION_URL (server-only) so - // it is NOT embedded in the client bundle, but it IS the rewrite destination. - if (apiOrigin) { - connectSources.push(apiOrigin); - } - - return [ - { - source: "/:path*", - headers: [ - { key: "X-Frame-Options", value: "SAMEORIGIN" }, - { key: "X-Content-Type-Options", value: "nosniff" }, - { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, - { - key: "Content-Security-Policy", - value: [ - "default-src 'self'", - // In development, Next.js Fast Refresh (HMR) requires 'unsafe-eval'. - // Without it the React event system breaks and forms submit natively. - isDev ? "script-src 'self' 'unsafe-inline' 'unsafe-eval'" : "script-src 'self' 'unsafe-inline'", - "style-src 'self' 'unsafe-inline'", - // blob: required for Mapbox GL sprite / image atlas - "img-src 'self' data: blob: https:", - "font-src 'self' data:", - // Mapbox GL v3 spawns blob: Web Workers for tile decoding - "worker-src blob:", - "child-src blob:", - `connect-src ${connectSources.join(" ")}`, - "frame-ancestors 'self'", - ].join("; "), - }, - ], - }, - ]; - }, - async rewrites() { - // The /api/proxy rewrite forwards browser requests to the real backend. - // - // In direct mode (NEXT_PUBLIC_API_BASE_URL = full URL): - // apiOrigin is derived from that URL — rewrite is available as a convenience - // but the client calls the API directly and never hits /api/proxy. - // - // In proxy mode (NEXT_PUBLIC_API_BASE_URL = /api/proxy): - // apiOrigin is derived from API_DESTINATION_URL — the rewrite is REQUIRED - // because the browser sends every request to /api/proxy/:path*. - // Without it, Next.js returns a 404 HTML page and all JSON parsing fails. - // - // When no destination is resolvable (e.g. CI placeholder builds) the rewrite - // is skipped — this is safe because no real requests are made during `next build`. - if (!apiOrigin) return []; - return [ - { - source: "/api/proxy/:path*", - destination: `${apiOrigin}/:path*`, - }, - ]; - }, -}; - -export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json deleted file mode 100644 index 19d2cce..0000000 --- a/apps/web/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "fieldtrack-frontend", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint", - "type-check": "tsc --noEmit", - "typecheck": "tsc --noEmit", - "test": "vitest run", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage" - }, - "dependencies": { - "@fieldtrack/types": "*", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-avatar": "^1.1.2", - "@radix-ui/react-dialog": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.4", - "@radix-ui/react-label": "^2.1.1", - "@radix-ui/react-select": "^2.1.4", - "@radix-ui/react-separator": "^1.1.1", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.2", - "@radix-ui/react-toast": "^1.2.4", - "@supabase/ssr": "^0.5.2", - "@supabase/supabase-js": "^2.46.2", - "@tanstack/react-query": "^5.62.7", - "@types/leaflet": "^1.9.21", - "@types/leaflet.markercluster": "^1.5.6", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "framer-motion": "^12.36.0", - "leaflet": "^1.9.4", - "leaflet.markercluster": "^1.5.3", - "lucide-react": "^0.468.0", - "mapbox-gl": "^3.8.0", - "next": "^15.1.3", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-leaflet": "^5.0.0", - "recharts": "^2.13.3", - "tailwind-merge": "^2.5.5", - "zod": "^4.3.6" - }, - "devDependencies": { - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.1.0", - "@testing-library/user-event": "^14.5.2", - "@types/mapbox-gl": "^3.4.1", - "@types/node": "^22.10.2", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", - "@vitejs/plugin-react": "^4.3.4", - "@vitest/coverage-v8": "^2.1.8", - "autoprefixer": "^10.4.20", - "eslint": "^8.57.1", - "eslint-config-next": "^15.1.3", - "jsdom": "^25.0.1", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17", - "typescript": "^5.7.2", - "vitest": "^2.1.8" - } -} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs deleted file mode 100644 index 73a0f54..0000000 --- a/apps/web/postcss.config.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import('postcss').Config} */ -const config = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - -export default config; diff --git a/apps/web/public/logo/logo.png b/apps/web/public/logo/logo.png deleted file mode 100644 index 41562102b8673449f98076ac270276c9d6ec89ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 439376 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

@ZL%?xU> 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¥5)I{;pl(Ex1@w*tJ%_D?p<$ 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%0AU*j2Rg{Hg~Ru6Q4RUXWuZ#=K)$qf;B*xb-Sz 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 z&`*va^VT>M|I;X{children}; -} diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx deleted file mode 100644 index c0f7416..0000000 --- a/apps/web/src/app/(auth)/login/page.tsx +++ /dev/null @@ -1,133 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import Image from "next/image"; -import { useAuth } from "@/hooks/useAuth"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { cn } from "@/lib/utils"; - -export default function LoginPage() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { login } = useAuth(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - setError(null); - setIsLoading(true); - try { - const userRole = await login(email, password); - const next = searchParams.get("next"); - if (next && next.startsWith("/") && !next.startsWith("//")) { - router.push(next); - } else if (userRole === "ADMIN") { - router.push("/dashboard"); - } else { - router.push("/sessions"); - } - } catch (err) { - setError(err instanceof Error ? err : new Error("Login failed")); - } finally { - setIsLoading(false); - } - } - - return ( -

- {/* Ambient background glow */} -
-
-
- -
- {/* Logo + wordmark */} -
-
- FieldTrack -
-
-

FieldTrack

-

Field workforce tracking & management

-
-
- - {/* Auth card */} -
-
-

Sign in to your account

-

Enter your credentials to continue

-
- -
void handleSubmit(e)} className="space-y-4"> - {error && } - -
- - setEmail(e.target.value)} - required - autoComplete="email" - className="h-10" - /> -
- -
- - setPassword(e.target.value)} - required - autoComplete="current-password" - className="h-10" - /> -
- - - -
- -

- FieldTrack 2.0 — Secure field workforce management -

-
-
- ); -} diff --git a/apps/web/src/app/(protected)/admin/analytics/page.tsx b/apps/web/src/app/(protected)/admin/analytics/page.tsx deleted file mode 100644 index 37f628b..0000000 --- a/apps/web/src/app/(protected)/admin/analytics/page.tsx +++ /dev/null @@ -1,425 +0,0 @@ -"use client"; - -import { useState, useMemo } from "react"; -import { useRouter } from "next/navigation"; -import { motion, AnimatePresence } from "framer-motion"; -import { useAuth } from "@/hooks/useAuth"; -import { - useOrgSummary, - useTopPerformers, - useSessionTrend, - useLeaderboard, -} from "@/hooks/queries/useAnalytics"; -import { TopPerformersChart } from "@/components/charts/TopPerformersChart"; -import { SessionTrendChart } from "@/components/charts/SessionTrendChart"; -import { LeaderboardTable } from "@/components/charts/LeaderboardTable"; -import { MetricCard } from "@/components/MetricCard"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { StaggerList, StaggerItem, FadeUp } from "@/components/motion"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Input } from "@/components/ui/input"; -import { formatDistance, formatDuration, formatCurrency } from "@/lib/utils"; -import { Activity, MapPin, Clock, Receipt, Users, TrendingUp, Trophy, Calendar } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { - type DateRange, - type PresetKey, - PRESET_LABELS, - rangeForPreset, - loadPersistedPreset, - loadPersistedCustomRange, - persistPreset, - persistCustomRange, - toInputDate, - formatRangeLabel, -} from "@/lib/dateRange"; -import type { OrgSummaryData } from "@/types"; - -// --- Date Range Filter Bar --------------------------------------------------- - -const PRESETS: PresetKey[] = [ - "today", - "yesterday", - "7d", - "30d", - "thisMonth", - "lastMonth", - "custom", -]; - -interface DateRangeFilterProps { - preset: PresetKey; - customRange: DateRange | null; - activeRange: DateRange; - onChange: (preset: PresetKey, customRange?: DateRange) => void; -} - -function DateRangeFilter({ - preset, - customRange, - activeRange, - onChange, -}: DateRangeFilterProps) { - const [showCustom, setShowCustom] = useState(preset === "custom"); - const [localFrom, setLocalFrom] = useState( - customRange ? customRange.from.slice(0, 10) : toInputDate(new Date()) - ); - const [localTo, setLocalTo] = useState( - customRange ? customRange.to.slice(0, 10) : toInputDate(new Date()) - ); - - function handlePreset(p: PresetKey) { - if (p === "custom") { - setShowCustom(true); - onChange("custom", customRange ?? undefined); - } else { - setShowCustom(false); - onChange(p); - } - } - - function handleApply() { - if (!localFrom || !localTo) return; - const from = new Date(localFrom); - from.setHours(0, 0, 0, 0); - const to = new Date(localTo); - to.setHours(23, 59, 59, 999); - if (from > to) return; - const range: DateRange = { from: from.toISOString(), to: to.toISOString() }; - onChange("custom", range); - } - - return ( -
-
- {PRESETS.map((p) => ( - - ))} -
- - - {showCustom && ( - -
-
- Start Date - setLocalFrom(e.target.value)} - className="h-8 text-sm" - /> -
-
- End Date - setLocalTo(e.target.value)} - className="h-8 text-sm" - /> -
- -
-
- )} -
- -

- - {formatRangeLabel(activeRange)} -

-
- ); -} - -// --- Analytics Metric Cards -------------------------------------------------- - -function AnalyticsMetrics({ - summary, - isLoading, -}: { - summary?: OrgSummaryData; - isLoading: boolean; -}) { - const cards = [ - { - title: "Sessions", - value: summary?.totalSessions.toLocaleString() ?? "—", - numericValue: summary?.totalSessions, - icon: , - }, - { - title: "Distance", - value: summary ? formatDistance(summary.totalDistanceKm) : "—", - icon: , - }, - { - title: "Duration", - value: summary ? formatDuration(summary.totalDurationSeconds) : "—", - icon: , - }, - { - title: "Active Employees", - value: summary?.activeEmployeesCount.toLocaleString() ?? "—", - numericValue: summary?.activeEmployeesCount, - icon: , - highlighted: true, - }, - { - title: "Approved Expenses", - value: summary ? formatCurrency(summary.approvedExpenseAmount) : "—", - icon: , - }, - { - title: "Expense Requests", - value: summary?.totalExpenses.toLocaleString() ?? "—", - numericValue: summary?.totalExpenses, - icon: , - }, - ]; - - return ( - - {cards.map((card) => ( - - - - ))} - - ); -} - -// --- Leaderboard section ----------------------------------------------------- - -type LbMetric = "distance" | "sessions" | "duration" | "expenses"; - -function AnalyticsLeaderboard({ from, to }: { from?: string; to?: string }) { - const [metric, setMetric] = useState("distance"); - const { data, isLoading, error } = useLeaderboard(metric, 10, from, to); - - return ( - - - - - Employee Leaderboard - - setMetric(v as LbMetric)}> - - Distance - Sessions - Duration - Expenses - - - - - {error && } - {isLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( - - ))} -
- ) : ( - - )} -
-
- ); -} - -// --- Animated range transition wrapper --------------------------------------- - -function RangeContent({ - rangeKey, - children, -}: { - rangeKey: string; - children: React.ReactNode; -}) { - return ( - - - {children} - - - ); -} - -// --- Page -------------------------------------------------------------------- - -export default function AnalyticsPage() { - const { permissions } = useAuth(); - const router = useRouter(); - - // Lazy-init from localStorage — no flicker, no useEffect needed - const [preset, setPreset] = useState(() => loadPersistedPreset()); - const [customRange, setCustomRange] = useState( - () => loadPersistedCustomRange() - ); - - const activeRange = useMemo(() => { - if (preset === "custom" && customRange) return customRange; - if (preset === "custom") return rangeForPreset("7d"); - return rangeForPreset(preset); - }, [preset, customRange]); - - const { from, to } = activeRange; - const rangeKey = `${from}::${to}`; - - const summary = useOrgSummary(from, to); - const sessionTrend = useSessionTrend(from, to); - const topByDistance = useTopPerformers("distance", 10, from, to); - const topBySessions = useTopPerformers("sessions", 10, from, to); - - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - return null; - } - - function handleFilterChange(newPreset: PresetKey, newCustom?: DateRange) { - setPreset(newPreset); - persistPreset(newPreset); - if (newCustom) { - setCustomRange(newCustom); - persistCustomRange(newCustom); - } - } - - return ( -
- {/* Header */} -
-

Analytics

-

- Historical performance insights — select a date range to explore trends. -

-
- - {/* Date range filter */} -
- -
- - {/* Animated content — remounts with fade on range change */} - -
- {summary.error && } - - {/* Summary cards */} - - - {/* Session trend chart */} - - - - - - Session Trend - - - - {sessionTrend.isLoading ? ( - - ) : sessionTrend.error ? ( - - ) : ( - - )} - - - - - {/* Top performers */} - -
- - - Top by Distance - - - {topByDistance.isLoading ? ( - - ) : topByDistance.error ? ( - - ) : ( - - )} - - - - - - Top by Sessions - - - {topBySessions.isLoading ? ( - - ) : topBySessions.error ? ( - - ) : ( - - )} - - -
-
- - {/* Leaderboard */} - - - -
-
-
- ); -} diff --git a/apps/web/src/app/(protected)/admin/employees/[id]/profile/page.tsx b/apps/web/src/app/(protected)/admin/employees/[id]/profile/page.tsx deleted file mode 100644 index 7ae4f0a..0000000 --- a/apps/web/src/app/(protected)/admin/employees/[id]/profile/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { useEmployeeProfile } from "@/hooks/queries/useProfile"; -import { useLeaderboard } from "@/hooks/queries/useAnalytics"; -import { useAuth } from "@/hooks/useAuth"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Skeleton } from "@/components/ui/skeleton"; -import { ProfileView } from "@/components/ProfileView"; -import { redirect } from "next/navigation"; - -export default function AdminEmployeeProfilePage() { - const { permissions } = useAuth(); - const params = useParams(); - const employeeId = params.id as string; - - if (!permissions.viewAnalytics) { - redirect("/profile"); - } - - const { data: profile, isLoading: profileLoading, error } = useEmployeeProfile(employeeId); - const { data: leaderboard } = useLeaderboard("distance", 50); - - const employeeRank = profile && leaderboard - ? leaderboard.find((e) => e.employeeId === profile.id)?.rank - : undefined; - - return ( -
-
-

Employee Profile

-

- Employee identity, performance, and activity status. -

-
- - {profileLoading ? ( -
- -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
-
- ) : error ? ( - - ) : profile ? ( - - ) : null} -
- ); -} - diff --git a/apps/web/src/app/(protected)/admin/employees/page.tsx b/apps/web/src/app/(protected)/admin/employees/page.tsx deleted file mode 100644 index 4ad0c88..0000000 --- a/apps/web/src/app/(protected)/admin/employees/page.tsx +++ /dev/null @@ -1,276 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { - useEmployeeList, - useCreateEmployee, - useSetEmployeeStatus, - type EmployeeRecord, -} from "@/hooks/queries/useEmployees"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { useToast } from "@/components/ui/use-toast"; -import { UserPlus, Search, UserCheck, UserX } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Skeleton } from "@/components/ui/skeleton"; -import { EmptyState } from "@/components/EmptyState"; -import { Users } from "lucide-react"; - -const PAGE_SIZE = 50; - -function EmployeeRow({ employee }: { employee: EmployeeRecord }) { - const { toast } = useToast(); - const setStatus = useSetEmployeeStatus(employee.id); - - function handleToggle() { - setStatus.mutate(!employee.is_active, { - onSuccess: () => - toast({ title: employee.is_active ? "Employee deactivated" : "Employee activated" }), - onError: (err) => - toast({ variant: "destructive", title: "Update failed", description: err.message }), - }); - } - - return ( - - {employee.employee_code} - {employee.name} - {employee.phone ?? "—"} - - - {employee.is_active ? "Active" : "Inactive"} - - - - - - - ); -} - -export default function AdminEmployeesPage() { - const { permissions } = useAuth(); - const router = useRouter(); - const { toast } = useToast(); - - useEffect(() => { - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - } - }, [permissions, router]); - - const [page, setPage] = useState(1); - const [search, setSearch] = useState(""); - const [filterActive, setFilterActive] = useState(undefined); - const [showCreateForm, setShowCreateForm] = useState(false); - const [newName, setNewName] = useState(""); - const [newPhone, setNewPhone] = useState(""); - - const { data, isLoading, error } = useEmployeeList(page, PAGE_SIZE, { - active: filterActive, - search: search || undefined, - }); - const createEmployee = useCreateEmployee(); - - const employees = data?.data ?? []; - const total = data?.pagination.total ?? 0; - const hasMore = page * PAGE_SIZE < total; - - function handleCreate() { - if (!newName.trim()) return; - createEmployee.mutate( - { name: newName.trim(), phone: newPhone.trim() || undefined }, - { - onSuccess: (emp) => { - toast({ - title: "Employee created", - description: `${emp.name} — code: ${emp.employee_code}`, - }); - setNewName(""); - setNewPhone(""); - setShowCreateForm(false); - }, - onError: (err) => { - toast({ variant: "destructive", title: "Creation failed", description: err.message }); - }, - }, - ); - } - - if (!permissions.viewAnalytics) return null; - - return ( -
-
-
-

Employees

-

{total} employees registered

-
- -
- - {showCreateForm && ( - - - New Employee - - -
- - setNewName(e.target.value)} - /> -
-
- - setNewPhone(e.target.value)} - /> -
-
- - -
-
-
- )} - - - -
- - { - setSearch(e.target.value); - setPage(1); - }} - /> -
-
- {(["all", "active", "inactive"] as const).map((f) => ( - - ))} -
-
-
- - {error && } - - - - {isLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( -
- - - - -
- ))} -
- ) : employees.length === 0 ? ( - - ) : ( -
- - - - - - - - - - - - {employees.map((emp) => ( - - ))} - -
CodeNamePhoneStatusActions
-
- )} -
-
- - {(page > 1 || hasMore) && ( -
- - - Page {page} · {total} total - - -
- )} -
- ); -} diff --git a/apps/web/src/app/(protected)/admin/expenses/page.tsx b/apps/web/src/app/(protected)/admin/expenses/page.tsx deleted file mode 100644 index 59bb8b3..0000000 --- a/apps/web/src/app/(protected)/admin/expenses/page.tsx +++ /dev/null @@ -1,373 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { useExpenseSummaryByEmployee, useEmployeeOrgExpenses, useUpdateExpenseStatus } from "@/hooks/queries/useExpenses"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { EmployeeIdentity } from "@/components/EmployeeIdentity"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { useToast } from "@/components/ui/use-toast"; -import { Expense, ExpenseStatus, EmployeeExpenseSummary } from "@/types"; -import { formatCurrency, formatDate, cn } from "@/lib/utils"; -import { Receipt, ChevronRight, CheckCircle2, XCircle, ExternalLink } from "lucide-react"; -import { motion, AnimatePresence } from "framer-motion"; - -// ─── Constants ──────────────────────────────────────────────────────────────── - -const PAGE_SIZE = 50; - -// ─── Sub-components ─────────────────────────────────────────────────────────── - -function ExpenseStatusBadge({ status }: { status: ExpenseStatus }) { - if (status === "APPROVED") - return Approved; - if (status === "REJECTED") - return Rejected; - return Pending; -} - -function ExpenseReviewSheet({ - summary, - onClose, - onAction, - isPending, -}: { - summary: EmployeeExpenseSummary | null; - onClose: () => void; - onAction: (expense: Expense, status: ExpenseStatus) => void; - isPending: boolean; -}) { - // Load individual expenses for the selected employee on-demand. - // This avoids the bulk-fetch-all-expenses pattern — only this employee's - // expenses are loaded when the review sheet opens. - const { data: expensesPage, isLoading: expensesLoading } = useEmployeeOrgExpenses( - summary?.employeeId ?? null, - 1, - 100, - ); - const expenses = expensesPage?.data ?? []; - - return ( - !open && onClose()}> - - {summary && ( - <> - - Expense Review - -

- {summary.pendingCount > 0 - ? `${summary.pendingCount} pending · ${formatCurrency(summary.pendingAmount)}` - : "No pending expenses"} -

-
- -
- {expensesLoading ? ( - - - )} - - - ); -} - -function EmployeeExpenseRow({ - group, - onClick, -}: { - group: EmployeeExpenseSummary; - onClick: () => void; -}) { - const hasPending = group.pendingCount > 0; - return ( - -
- -
- -
- {hasPending ? ( - {group.pendingCount} Pending - ) : ( - - )} -
- -
- {group.pendingAmount > 0 ? ( - formatCurrency(group.pendingAmount) - ) : ( - - )} -
- -
- {group.latestExpenseDate ? formatDate(group.latestExpenseDate) : "—"} -
- - -
- ); -} - -// ─── Page ───────────────────────────────────────────────────────────────────── - -export default function AdminExpensesPage() { - const { permissions } = useAuth(); - const router = useRouter(); - const { toast } = useToast(); - - const [selectedSummary, setSelectedSummary] = useState(null); - const [viewPage, setViewPage] = useState(1); - - useEffect(() => { - if (!permissions.manageExpenses) router.replace("/sessions"); - }, [permissions, router]); - - // Server-aggregated: one row per employee, already sorted with pending-first. - // O(employees) vs O(all expenses) — no client-side grouping required. - const { data: summaryPage, isLoading, error, refetch } = useExpenseSummaryByEmployee(viewPage, PAGE_SIZE); - const updateStatus = useUpdateExpenseStatus(); - - const groups = summaryPage?.data ?? []; - const totalGroups = summaryPage?.pagination.total ?? 0; - const hasMore = groups.length > 0 && totalGroups > viewPage * PAGE_SIZE; - const pendingEmployees = groups.filter((g) => g.pendingCount > 0).length; - - // Keep the open sheet in sync: re-fetch on mutation success is handled by - // useUpdateExpenseStatus's onSuccess cache invalidation. - const refreshedSummary = selectedSummary - ? (groups.find((g) => g.employeeId === selectedSummary.employeeId) ?? selectedSummary) - : null; - - if (!permissions.manageExpenses) return null; - - function handleAction(expense: Expense, status: ExpenseStatus) { - updateStatus.mutate( - { id: expense.id, status }, - { - onSuccess: () => { - toast({ - title: status === "APPROVED" ? "Expense approved" : "Expense rejected", - description: `${expense.description} · ${formatCurrency(expense.amount)}`, - }); - }, - onError: (err) => { - toast({ - variant: "destructive", - title: "Failed to update", - description: err.message, - }); - }, - } - ); - } - - return ( -
- {/* Header */} -
-
-

Manage Expenses

-

- {isLoading - ? "Loading..." - : `${totalGroups} employees · ${ - pendingEmployees > 0 - ? `${pendingEmployees} require${pendingEmployees === 1 ? "s" : ""} attention` - : "all clear" - }`} -

-
- {pendingEmployees > 0 && !isLoading && ( - - {pendingEmployees} pending - - )} -
- - {error && void refetch()} />} - - {/* Table */} -
- {/* Column headers */} -
- Employee - Pending Expenses - Pending Amount - Latest Expense - -
- - {/* Skeleton */} - {isLoading && ( -
- {Array.from({ length: 6 }).map((_, i) => ( -
-
-
-
-
-
-
-
- {Array.from({ length: 3 }).map((_, j) => ( -
- ))} -
-
- ))} -
- )} - - {/* Empty */} - {!isLoading && groups.length === 0 && ( -
- -

No expenses found

-
- )} - - {/* Rows */} - {!isLoading && groups.length > 0 && ( -
- - {groups.map((group) => ( - setSelectedSummary(group)} - /> - ))} - - - {hasMore && ( -
- -
- )} -
- )} -
- - {/* Expense review slide-in panel */} - setSelectedSummary(null)} - onAction={handleAction} - isPending={updateStatus.isPending} - /> -
- ); -} diff --git a/apps/web/src/app/(protected)/admin/monitoring/map/EmployeeMap.tsx b/apps/web/src/app/(protected)/admin/monitoring/map/EmployeeMap.tsx deleted file mode 100644 index 8efbb8b..0000000 --- a/apps/web/src/app/(protected)/admin/monitoring/map/EmployeeMap.tsx +++ /dev/null @@ -1,225 +0,0 @@ -"use client"; - -/** - * EmployeeMap — Leaflet map with MarkerClusterGroup support. - * - * Imported dynamically with `ssr: false` from the parent page because Leaflet - * accesses `window` at module initialisation time and will crash Next.js SSR. - * - * Marker colour scheme: - * ACTIVE → green (checked in within the last 2 hours) - * RECENT → orange (checked out, still this calendar day) - * INACTIVE → grey (no session activity today) - * - * Selected employee → enlarged SVG + pulsing ring overlay. - * Clustering → nearby markers grouped at low zoom via MarkerClusterGroup. - */ - -import { useEffect, useRef } from "react"; -import type { Map as LeafletMap, Marker as LeafletMarker } from "leaflet"; -import L from "leaflet"; -import "leaflet.markercluster"; -import type { EmployeeMapMarker } from "@/types"; - -// ─── Marker icon colours ────────────────────────────────────────────────────── - -const STATUS_COLOURS: Record = { - ACTIVE: "#22c55e", // green-500 - RECENT: "#f97316", // orange-500 - INACTIVE: "#94a3b8", // slate-400 -}; - -function makeIcon( - status: EmployeeMapMarker["status"], - selected = false -) { - const colour = STATUS_COLOURS[status]; - const size = selected ? 32 : 24; - const inner = selected ? 8 : 5; - - const pulse = selected - ? ` - - - ` - : ""; - - const svg = ` - - ${pulse} - - - - `.trim(); - - return L.divIcon({ - html: svg, - className: "", // prevent Leaflet's default white-box class - iconSize: [size, size], - iconAnchor: [size / 2, size / 2], - popupAnchor: [0, -(size / 2 + 2)], - }); -} - -// ─── Popup HTML ─────────────────────────────────────────────────────────────── - -function buildPopupHtml(m: EmployeeMapMarker): string { - const ts = new Date(m.recordedAt).toLocaleString(); - const code = m.employeeCode ? ` (${m.employeeCode})` : ""; - const statusColour = - m.status === "ACTIVE" ? "#22c55e" : - m.status === "RECENT" ? "#f97316" : "#94a3b8"; - - return ` -
- ${m.employeeName}${code}
- ${m.status}
- Last fix: ${ts} -
- `.trim(); -} - -// ─── Component ──────────────────────────────────────────────────────────────── - -interface Props { - markers: EmployeeMapMarker[]; - isLoading: boolean; - selectedEmployeeId?: string | null; -} - -export default function EmployeeMap({ markers, isLoading, selectedEmployeeId }: Props) { - const mapContainerRef = useRef(null); - const mapRef = useRef(null); - const clusterGroupRef = useRef(null); - // Track current markers by employeeId → Leaflet marker - const markerMapRef = useRef>(new Map()); - - // ── Initialise Leaflet map once ──────────────────────────────────────────── - useEffect(() => { - if (!mapContainerRef.current || mapRef.current) return; - const container = mapContainerRef.current; - - const map = L.map(container, { - center: [20, 0], - zoom: 2, - zoomControl: true, - }); - - L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { - attribution: - '© OpenStreetMap contributors', - maxZoom: 19, - }).addTo(map); - - // Marker cluster group with custom cluster icon - const clusterGroup = L.markerClusterGroup({ - maxClusterRadius: 60, - showCoverageOnHover: false, - iconCreateFunction(cluster) { - const count = cluster.getChildCount(); - return L.divIcon({ - html: `
${count}
`, - className: "", - iconSize: [36, 36], - iconAnchor: [18, 18], - }); - }, - }); - map.addLayer(clusterGroup); - clusterGroupRef.current = clusterGroup; - mapRef.current = map; - - // ResizeObserver → invalidateSize when container dimensions change - const ro = new ResizeObserver(() => map.invalidateSize({ animate: false })); - ro.observe(container); - - const raf = requestAnimationFrame(() => map.invalidateSize({ animate: false })); - - return () => { - ro.disconnect(); - cancelAnimationFrame(raf); - map.remove(); - mapRef.current = null; - clusterGroupRef.current = null; - markerMapRef.current.clear(); - }; - }, []); - - // ── Sync markers when data or selection changes ──────────────────────────── - useEffect(() => { - const map = mapRef.current; - const clusterGroup = clusterGroupRef.current; - if (!map || !clusterGroup) return; - - const incoming = new Map(markers.map((m) => [m.employeeId, m])); - const existing = markerMapRef.current; - - // Remove markers no longer in the data set - for (const [id, leafletMarker] of existing) { - if (!incoming.has(id)) { - clusterGroup.removeLayer(leafletMarker); - existing.delete(id); - } - } - - const latLngs: [number, number][] = []; - const toAdd: LeafletMarker[] = []; - - for (const m of markers) { - const isSelected = selectedEmployeeId === m.employeeId; - const icon = makeIcon(m.status, isSelected); - latLngs.push([m.latitude, m.longitude]); - - if (existing.has(m.employeeId)) { - // Update existing marker position + icon (smooth move, no remove/re-add) - const lm = existing.get(m.employeeId)!; - lm.setLatLng([m.latitude, m.longitude]); - lm.setIcon(icon); - lm.setPopupContent(buildPopupHtml(m)); - } else { - // New marker - const lm = L.marker([m.latitude, m.longitude], { icon }).bindPopup(buildPopupHtml(m)); - existing.set(m.employeeId, lm); - toAdd.push(lm); - } - } - - if (toAdd.length > 0) { - clusterGroup.addLayers(toAdd); - } - - markerMapRef.current = existing; - - // Auto-centre: only on first data load (when no markers existed before) - if (latLngs.length > 0 && existing.size === toAdd.length) { - map.fitBounds(L.latLngBounds(latLngs), { padding: [40, 40], maxZoom: 14 }); - } - - // Pan to selected employee marker if it exists - if (selectedEmployeeId) { - const sel = existing.get(selectedEmployeeId); - if (sel) { - map.setView(sel.getLatLng(), Math.max(map.getZoom(), 13), { animate: true }); - sel.openPopup(); - } - } - }, [markers, selectedEmployeeId]); - - return ( -
- {isLoading && ( -
- Loading positions… -
- )} -
-
- ); -} diff --git a/apps/web/src/app/(protected)/admin/monitoring/map/page.tsx b/apps/web/src/app/(protected)/admin/monitoring/map/page.tsx deleted file mode 100644 index f78477b..0000000 --- a/apps/web/src/app/(protected)/admin/monitoring/map/page.tsx +++ /dev/null @@ -1,268 +0,0 @@ -"use client"; - -import { useState } from "react"; -import dynamic from "next/dynamic"; -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { useAdminMap } from "@/hooks/queries/useDashboard"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Badge } from "@/components/ui/badge"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { MapPin, RefreshCw, Users, Search } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { cn } from "@/lib/utils"; -import type { EmployeeMapMarker } from "@/types"; - -// ─── Dynamic Leaflet import (SSR disabled — Leaflet uses `window`) ──────────── - -const EmployeeMap = dynamic(() => import("./EmployeeMap"), { - ssr: false, - loading: () => ( -
- Loading map… -
- ), -}); - -// ─── Helpers ────────────────────────────────────────────────────────────────── - -const STATUS_DOT: Record = { - ACTIVE: "bg-emerald-500", - RECENT: "bg-orange-400", - INACTIVE: "bg-slate-400", -}; - -const STATUS_LABEL: Record = { - ACTIVE: "Active", - RECENT: "Recent", - INACTIVE: "Inactive", -}; - -// ─── Employee List Item ──────────────────────────────────────────────────────── - -function EmployeeListItem({ - marker, - selected, - onClick, -}: { - marker: EmployeeMapMarker; - selected: boolean; - onClick: () => void; -}) { - const initials = marker.employeeName - .split(" ") - .slice(0, 2) - .map((n) => n[0] ?? "") - .join("") - .toUpperCase(); - - return ( - - ); -} - -// ─── Page ───────────────────────────────────────────────────────────────────── - -export default function MonitoringMapPage() { - const { permissions } = useAuth(); - const router = useRouter(); - const [selectedId, setSelectedId] = useState(null); - const [search, setSearch] = useState(""); - - useEffect(() => { - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - } - }, [permissions, router]); - - const { data: markers = [], isLoading, error, dataUpdatedAt, refetch } = useAdminMap(); - - if (!permissions.viewAnalytics) return null; - - const activeCount = markers.filter((m) => m.status === "ACTIVE").length; - const recentCount = markers.filter((m) => m.status === "RECENT").length; - const inactiveCount = markers.filter((m) => m.status === "INACTIVE").length; - - const filtered = markers.filter((m) => - search - ? m.employeeName.toLowerCase().includes(search.toLowerCase()) || - (m.employeeCode ?? "").toLowerCase().includes(search.toLowerCase()) - : true - ); - - function handleSelect(id: string) { - setSelectedId((prev) => (prev === id ? null : id)); - } - - return ( -
- {/* Header */} -
-
-

Live Employee Map

-

- Showing latest GPS position per employee. Refreshes every 30 s. -

-
-
- {dataUpdatedAt ? ( - - Updated {new Date(dataUpdatedAt).toLocaleTimeString()} - - ) : null} - -
-
- - {/* Summary badges */} -
- - - {activeCount} Active - - - - {recentCount} Recent - - - - {markers.length} on map - -
- - {/* Error */} - {error ? : null} - - {/* Main content: map + employee list */} -
- {/* Map */} - - - Employee Positions - - -
- -
-
-
- - {/* Employee sidebar */} - - - - - Employees - - - - {/* Search */} -
- - setSearch(e.target.value)} - className="pl-8 h-8 text-sm" - /> -
- - {/* Status summary */} -
- {activeCount} active - {recentCount} recent - {inactiveCount} inactive -
- - {/* Scrollable list */} -
- {isLoading && ( -
- {Array.from({ length: 5 }).map((_, i) => ( -
-
-
-
- ))} -
- )} - - {!isLoading && filtered.length === 0 && ( -

No employees found

- )} - - {/* Sort: ACTIVE first, then RECENT, then INACTIVE */} - {[...filtered] - .sort((a, b) => { - const order = { ACTIVE: 0, RECENT: 1, INACTIVE: 2 }; - return order[a.status] - order[b.status]; - }) - .map((m) => ( - handleSelect(m.employeeId)} - /> - ))} -
- - -
- - {/* Empty state */} - {!isLoading && markers.length === 0 && !error && ( -
- -

No GPS data yet

-

- Markers appear after employees check in and record a location point. -

-
- )} -
- ); -} diff --git a/apps/web/src/app/(protected)/admin/monitoring/page.tsx b/apps/web/src/app/(protected)/admin/monitoring/page.tsx deleted file mode 100644 index c55c25f..0000000 --- a/apps/web/src/app/(protected)/admin/monitoring/page.tsx +++ /dev/null @@ -1,198 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { - useMonitoringHistory, - useStartMonitoring, - useStopMonitoring, -} from "@/hooks/queries/useMonitoring"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { useToast } from "@/components/ui/use-toast"; -import { formatDate, formatTime, formatDuration } from "@/lib/utils"; -import { Play, Square, Activity } from "lucide-react"; -import type { AdminSession } from "@/types"; - -const PAGE_LIMIT = 20; - -function formatSessionDuration(session: AdminSession): string { - if (!session.ended_at) return "Ongoing"; - const startMs = new Date(session.started_at).getTime(); - const endMs = new Date(session.ended_at).getTime(); - const seconds = Math.round((endMs - startMs) / 1000); - return formatDuration(seconds); -} - -export default function AdminMonitoringPage() { - const { permissions } = useAuth(); - const router = useRouter(); - const { toast } = useToast(); - - useEffect(() => { - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - } - }, [permissions, router]); - - const [page, setPage] = useState(1); - const { data, isLoading, error } = useMonitoringHistory(page, PAGE_LIMIT); - const startMonitoring = useStartMonitoring(); - const stopMonitoring = useStopMonitoring(); - - const sessions = data?.data ?? []; - const total = data?.pagination.total ?? 0; - const hasMore = page * PAGE_LIMIT < total; - - const activeSession = sessions.find((s) => !s.ended_at); - - if (!permissions.viewAnalytics) return null; - - function handleStart() { - startMonitoring.mutate(undefined, { - onSuccess: () => { - toast({ title: "Monitoring started", description: "Location monitoring is now active." }); - }, - onError: (err) => { - toast({ variant: "destructive", title: "Failed to start", description: err.message }); - }, - }); - } - - function handleStop() { - stopMonitoring.mutate(undefined, { - onSuccess: () => { - toast({ title: "Monitoring stopped", description: "Location monitoring has been stopped." }); - }, - onError: (err) => { - const isNotFound = (err as { status?: number }).status === 404; - const msg = isNotFound ? "No active session to stop." : err.message; - toast({ variant: "destructive", title: "Failed to stop", description: msg }); - }, - }); - } - - return ( -
-
-

Monitoring

-

Control and review location monitoring sessions.

-
- - {/* Controls */} - - - - - Monitoring Controls - - - - {activeSession ? ( - - Active since {formatTime(activeSession.started_at)} - - ) : ( - Inactive - )} - - - - - - {/* History */} -
-

Session History

- - {error && } - - {isLoading ? ( -

Loading...

- ) : sessions.length === 0 ? ( -

No monitoring sessions yet.

- ) : ( -
- - - - - - - - - - - {sessions.map((session) => ( - - - - - - - ))} - -
Start TimeEnd TimeDurationStatus
-
{formatDate(session.started_at)}
-
{formatTime(session.started_at)}
-
- {session.ended_at ? ( - <> -
{formatDate(session.ended_at)}
-
{formatTime(session.ended_at)}
- - ) : ( - - )} -
{formatSessionDuration(session)} - {session.ended_at ? ( - Ended - ) : ( - Active - )} -
-
- )} - - {(sessions.length > 0) && ( -
- - Page {page} - -
- )} -
-
- ); -} diff --git a/apps/web/src/app/(protected)/admin/queues/page.tsx b/apps/web/src/app/(protected)/admin/queues/page.tsx deleted file mode 100644 index b511543..0000000 --- a/apps/web/src/app/(protected)/admin/queues/page.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { useAdminQueues, type QueueStats } from "@/hooks/queries/useQueues"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Activity } from "lucide-react"; - -function StatRow({ label, value }: { label: string; value: number }) { - return ( -
- {label} - {value.toLocaleString()} -
- ); -} - -function QueueCard({ name, stats }: { name: string; stats: QueueStats }) { - const hasBacklog = stats.waiting > 100 || stats.failed > 5; - return ( - - -
- {name} Queue - - {hasBacklog ? "Attention" : "Healthy"} - -
-
- - - - - - {stats.dlq !== undefined && } - -
- ); -} - -export default function AdminQueuesPage() { - const { permissions } = useAuth(); - const router = useRouter(); - - useEffect(() => { - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - } - }, [permissions, router]); - - const { data, isLoading, error } = useAdminQueues(); - - if (!permissions.viewAnalytics) return null; - - return ( -
-
- -
-

Queue Monitor

-

- BullMQ — refreshes every 30 s -

-
-
- - {error && } - - {isLoading ? ( -

Loading queue stats…

- ) : data ? ( -
- - -
- ) : null} -
- ); -} diff --git a/apps/web/src/app/(protected)/admin/sessions/[id]/locations/page.tsx b/apps/web/src/app/(protected)/admin/sessions/[id]/locations/page.tsx deleted file mode 100644 index ce743b2..0000000 --- a/apps/web/src/app/(protected)/admin/sessions/[id]/locations/page.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter, useParams } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { useSessionLocations } from "@/hooks/queries/useSessionLocations"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { ArrowLeft, MapPin } from "lucide-react"; -import { formatDate } from "@/lib/utils"; - -function formatCoord(n: number, decimals = 6) { - return n.toFixed(decimals); -} - -export default function SessionLocationsPage() { - const { permissions } = useAuth(); - const router = useRouter(); - const params = useParams<{ id: string }>(); - const sessionId = params.id ?? null; - - useEffect(() => { - if (!permissions.viewAnalytics) { - router.replace("/sessions"); - } - }, [permissions, router]); - - const { data: locations, isLoading, error } = useSessionLocations(sessionId); - - if (!permissions.viewAnalytics) return null; - - return ( -
-
- -
-

Session Route

-

- {sessionId} -

-
-
- - {error && } - - - -
- - - GPS Points - - {!isLoading && locations && ( - {locations.length} point{locations.length !== 1 ? "s" : ""} - )} -
-
- - {isLoading ? ( -
Loading route…
- ) : !locations || locations.length === 0 ? ( -
No GPS points recorded for this session.
- ) : ( -
- - - - - - - - - - - - {locations.map((loc, idx) => ( - - - - - - - - ))} - -
#Recorded AtLatitudeLongitudeAccuracy (m)
- {loc.sequence_number ?? idx + 1} - - {formatDate(loc.recorded_at)} - - {formatCoord(loc.latitude)} - - {formatCoord(loc.longitude)} - - {loc.accuracy !== null ? loc.accuracy.toFixed(1) : "—"} -
-
- )} -
-
-
- ); -} diff --git a/apps/web/src/app/(protected)/admin/sessions/page.tsx b/apps/web/src/app/(protected)/admin/sessions/page.tsx deleted file mode 100644 index 68f4bf7..0000000 --- a/apps/web/src/app/(protected)/admin/sessions/page.tsx +++ /dev/null @@ -1,416 +0,0 @@ -"use client"; - -import { useEffect, useState, useMemo } from "react"; -import { useRouter } from "next/navigation"; -import { useAuth } from "@/hooks/useAuth"; -import { useAllOrgSessions, useEmployeeSessionHistory } from "@/hooks/queries/useSessions"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { EmployeeIdentity } from "@/components/EmployeeIdentity"; -import { Badge } from "@/components/ui/badge"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { AttendanceSession, ActivityStatus } from "@/types"; -import { formatDate, formatTime, formatDistance, formatDuration, cn } from "@/lib/utils"; -import { Clock, ChevronRight } from "lucide-react"; -import { motion, AnimatePresence } from "framer-motion"; - -// --- Constants ---------------------------------------------------------------- - -const VIEW_PAGE_SIZE = 25; - -// --- Types -------------------------------------------------------------------- - -type FilterTab = "all" | ActivityStatus; - -const TABS: { key: FilterTab; label: string }[] = [ - { key: "all", label: "All" }, - { key: "ACTIVE", label: "Active" }, - { key: "RECENT", label: "Recently Active" }, - { key: "INACTIVE", label: "Inactive" }, -]; - -interface EmployeeSessionGroup { - employeeId: string; - employeeName: string; - employeeCode: string | null; - activityStatus: ActivityStatus; - latestSession: AttendanceSession; - sessions: AttendanceSession[]; -} - -// --- Helpers ------------------------------------------------------------------ - -function deriveStatus(session: AttendanceSession): ActivityStatus { - if (!session.checkout_at) return "ACTIVE"; - const lastTs = new Date(session.checkout_at).getTime(); - return Date.now() - lastTs < 86_400_000 ? "RECENT" : "INACTIVE"; -} - -const STATUS_ORDER: Record = { ACTIVE: 0, RECENT: 1, INACTIVE: 2 }; - -function groupSessions(sessions: AttendanceSession[]): EmployeeSessionGroup[] { - const map = new Map(); - for (const s of sessions) { - const arr = map.get(s.employee_id) ?? []; - arr.push(s); - map.set(s.employee_id, arr); - } - const groups: EmployeeSessionGroup[] = []; - for (const [empId, empSessions] of map) { - const sorted = [...empSessions].sort( - (a, b) => new Date(b.checkin_at).getTime() - new Date(a.checkin_at).getTime() - ); - const latest = sorted[0]; - const status = latest.activityStatus ?? deriveStatus(latest); - groups.push({ - employeeId: empId, - employeeName: latest.employee_name ?? latest.employee_code ?? empId, - employeeCode: latest.employee_code ?? null, - activityStatus: status, - latestSession: latest, - sessions: sorted, - }); - } - groups.sort((a, b) => { - const sd = STATUS_ORDER[a.activityStatus] - STATUS_ORDER[b.activityStatus]; - if (sd !== 0) return sd; - return ( - new Date(b.latestSession.checkin_at).getTime() - - new Date(a.latestSession.checkin_at).getTime() - ); - }); - return groups; -} - -// --- Sub-components ----------------------------------------------------------- - -function StatusBadge({ status }: { status: ActivityStatus }) { - if (status === "ACTIVE") - return ( - - Active - - ); - if (status === "RECENT") - return ( - - Recent - - ); - return ( - - Inactive - - ); -} - -function SessionHistorySheet({ - group, - onClose, -}: { - group: EmployeeSessionGroup | null; - onClose: () => void; -}) { - const { data: historyPage, isLoading: historyLoading } = useEmployeeSessionHistory( - group?.employeeId ?? null, - ); - const historySessions = historyPage?.data ?? []; - - return ( - !open && onClose()}> - - {group && ( - <> - - Session History - -

- {historyLoading - ? "Loading sessions…" - : `${historySessions.length} session${historySessions.length !== 1 ? "s" : ""} total`} -

-
- -
- {historyLoading && - Array.from({ length: 4 }).map((_, i) => ( -
-
-
-
- ))} - {!historyLoading && - historySessions.map((session) => { - const status = session.activityStatus ?? deriveStatus(session); - return ( -
-
-
-
- {formatDate(session.checkin_at)} - -
-
- - - {formatTime(session.checkin_at)} - {session.checkout_at - ? ` to ${formatTime(session.checkout_at)}` - : " (checked in)"} - -
-
-
-

- {formatDistance(session.total_distance_km)} -

-

- {formatDuration(session.total_duration_seconds)} -

-
-
-
- ); - })} -
- - )} - - - ); -} - -function EmployeeSessionRow({ - group, - onClick, -}: { - group: EmployeeSessionGroup; - onClick: () => void; -}) { - const s = group.latestSession; - return ( - -
- -
- -
- {formatDate(s.checkin_at)} - {formatTime(s.checkin_at)} -
- -
- {s.checkout_at ? ( - <> - {formatDate(s.checkout_at)} - {formatTime(s.checkout_at)} - - ) : ( - - - Live - - )} -
- - {formatDistance(s.total_distance_km)} - {formatDuration(s.total_duration_seconds)} - -
- -
- - -
- ); -} - -// --- Page --------------------------------------------------------------------- - -export default function AdminSessionsPage() { - const { permissions } = useAuth(); - const router = useRouter(); - - const [activeTab, setActiveTab] = useState("all"); - const [selectedGroup, setSelectedGroup] = useState(null); - const [viewPage, setViewPage] = useState(1); - - useEffect(() => { - if (!permissions.viewOrgSessions) router.replace("/sessions"); - }, [permissions, router]); - - const { data: allSessions, isLoading, error, refetch } = useAllOrgSessions(); - const groups = useMemo(() => groupSessions(allSessions), [allSessions]); - - const tabCounts = useMemo(() => { - const counts: Record = { all: groups.length, ACTIVE: 0, RECENT: 0, INACTIVE: 0 }; - for (const g of groups) counts[g.activityStatus]++; - return counts; - }, [groups]); - - const filtered = useMemo( - () => (activeTab === "all" ? groups : groups.filter((g) => g.activityStatus === activeTab)), - [groups, activeTab] - ); - - const paged = filtered.slice(0, viewPage * VIEW_PAGE_SIZE); - const hasMore = paged.length < filtered.length; - - if (!permissions.viewOrgSessions) return null; - - return ( -
-
-

All Sessions

-

- {isLoading - ? "Loading..." - : `${groups.length} employee${groups.length !== 1 ? "s" : ""} with session activity`} -

-
- - {error && void refetch()} />} - -
- {TABS.map((tab) => ( - - ))} -
- -
-
- Employee - Latest Check-in - Latest Check-out - Distance - Duration - Status - -
- - {isLoading && ( -
- {Array.from({ length: 8 }).map((_, i) => ( -
-
-
-
-
-
-
-
- {Array.from({ length: 5 }).map((_, j) => ( -
- ))} -
-
- ))} -
- )} - - {!isLoading && filtered.length === 0 && ( -
- -

No sessions found

-

- {activeTab !== "all" - ? "Try switching to a different filter" - : "Sessions will appear here once recorded"} -

-
- )} - - {!isLoading && filtered.length > 0 && ( -
- - {paged.map((group) => ( - setSelectedGroup(group)} - /> - ))} - - - {hasMore && ( -
- -
- )} -
- )} -
- - setSelectedGroup(null)} - /> -
- ); -} \ No newline at end of file diff --git a/apps/web/src/app/(protected)/admin/webhooks/page.tsx b/apps/web/src/app/(protected)/admin/webhooks/page.tsx deleted file mode 100644 index 7e25147..0000000 --- a/apps/web/src/app/(protected)/admin/webhooks/page.tsx +++ /dev/null @@ -1,819 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import { useToast } from "@/components/ui/use-toast"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Badge } from "@/components/ui/badge"; -import { Skeleton } from "@/components/ui/skeleton"; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, - SheetFooter, -} from "@/components/ui/sheet"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { - Webhook, - Plus, - Trash2, - RefreshCw, - ChevronDown, - ChevronUp, - CheckCircle2, - XCircle, - Clock, - ToggleLeft, - ToggleRight, - Eye, - EyeOff, - Copy, - Check, -} from "lucide-react"; -import { cn } from "@/lib/utils"; -import { - useWebhooks, - useWebhookDeliveries, - useCreateWebhook, - useUpdateWebhook, - useDeleteWebhook, - useRetryDelivery, - WEBHOOK_EVENT_TYPES, - type WebhookRecord, - type WebhookDelivery, - type DeliveryStatus, - type CreateWebhookBody, -} from "@/hooks/queries/useWebhooks"; - -// ─── Constants ──────────────────────────────────────────────────────────────── - -const EVENT_LABELS: Record = { - "employee.checked_in": "Check In", - "employee.checked_out": "Check Out", - "expense.created": "Expense Created", - "expense.approved": "Expense Approved", - "expense.rejected": "Expense Rejected", - "employee.created": "Employee Created", -}; - -const STATUS_CONFIG: Record< - DeliveryStatus, - { label: string; icon: React.ElementType; className: string } -> = { - success: { label: "Success", icon: CheckCircle2, className: "text-emerald-500" }, - failed: { label: "Failed", icon: XCircle, className: "text-rose-500" }, - pending: { label: "Pending", icon: Clock, className: "text-amber-500" }, -}; - -// ─── Helpers ────────────────────────────────────────────────────────────────── - -function formatRelativeTime(iso: string): string { - const diff = Date.now() - new Date(iso).getTime(); - if (diff < 60_000) return "Just now"; - if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`; - if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`; - return `${Math.floor(diff / 86_400_000)}d ago`; -} - -// ─── Delivery Status Badge ──────────────────────────────────────────────────── - -function DeliveryStatusBadge({ status }: { status: DeliveryStatus }) { - const { label, icon: Icon, className } = STATUS_CONFIG[status]; - return ( - - - {label} - - ); -} - -// ─── Expandable Payload Row ─────────────────────────────────────────────────── - -function DeliveryRow({ delivery, onRetry, isRetrying }: { - delivery: WebhookDelivery; - onRetry: (id: string) => void; - isRetrying: boolean; -}) { - const [expanded, setExpanded] = useState(false); - - return ( -
- - )} - {expanded ? ( - - ) : ( - - )} - - - - {expanded && ( - -
-
-

- Response Body -

-
-                  {delivery.response_body ?? "(no response body)"}
-                
-
-
- Event: {delivery.event_id.slice(0, 8)}… - Delivery: {delivery.id.slice(0, 8)}… -
-
-
- )} -
-
- ); -} - -// ─── Deliveries Panel ───────────────────────────────────────────────────────── - -function DeliveriesPanel({ webhookId }: { webhookId: string | null }) { - const [page, setPage] = useState(1); - const [statusFilter, setStatusFilter] = useState(undefined); - const retryDelivery = useRetryDelivery(); - const { toast } = useToast(); - - const { data, isLoading } = useWebhookDeliveries( - page, - 20, - webhookId ?? undefined, - statusFilter, - ); - - const deliveries = data?.data ?? []; - const total = data?.pagination.total ?? 0; - const hasMore = page * 20 < total; - - function handleRetry(id: string) { - retryDelivery.mutate(id, { - onSuccess: () => toast({ title: "Delivery queued for retry" }), - onError: (e) => toast({ variant: "destructive", title: "Retry failed", description: e.message }), - }); - } - - const FILTERS: { key: DeliveryStatus | undefined; label: string }[] = [ - { key: undefined, label: "All" }, - { key: "pending", label: "Pending" }, - { key: "success", label: "Success" }, - { key: "failed", label: "Failed" }, - ]; - - return ( -
- {/* Filters */} -
- {FILTERS.map((f) => ( - - ))} -
- -
- {isLoading && ( -
- {Array.from({ length: 5 }).map((_, i) => ( -
- - - -
- ))} -
- )} - - {!isLoading && deliveries.length === 0 && ( -
- -

No deliveries yet

-

- Deliveries appear here when a webhook event is triggered. -

-
- )} - - {!isLoading && deliveries.length > 0 && ( -
- {deliveries.map((d) => ( - - ))} -
- )} -
- - {(deliveries.length > 0 || page > 1) && ( -
- {total} total deliveries -
- - -
-
- )} -
- ); -} - -// ─── Webhook Card ───────────────────────────────────────────────────────────── - -function WebhookCard({ - webhook, - onEdit, - onDelete, -}: { - webhook: WebhookRecord; - onEdit: (w: WebhookRecord) => void; - onDelete: (id: string) => void; -}) { - const [showDeliveries, setShowDeliveries] = useState(false); - const [copied, setCopied] = useState(false); - - const updateWebhook = useUpdateWebhook(webhook.id); - const { toast } = useToast(); - - function handleToggleActive() { - updateWebhook.mutate( - { is_active: !webhook.is_active }, - { - onSuccess: () => - toast({ title: `Webhook ${!webhook.is_active ? "enabled" : "disabled"}` }), - onError: (e) => - toast({ variant: "destructive", title: "Update failed", description: e.message }), - } - ); - } - - function copyUrl() { - void navigator.clipboard.writeText(webhook.url); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - - return ( - - {/* Header row */} -
- {/* Status dot */} -
- {webhook.is_active ? ( - - - - - ) : ( - - )} -
- - {/* URL + events */} -
-
- - {webhook.url} - - -
-
- {webhook.events.map((e) => ( - - {EVENT_LABELS[e] ?? e} - - ))} -
-
- - {/* Actions */} -
- - - -
-
- - {/* Deliveries toggle */} - - - - {showDeliveries && ( - -
- -
-
- )} -
-
- ); -} - -// ─── Create / Edit Sheet ────────────────────────────────────────────────────── - -interface WebhookFormState { - url: string; - secret: string; - events: Set; -} - -function WebhookSheet({ - open, - editing, - onClose, -}: { - open: boolean; - editing: WebhookRecord | null; - onClose: () => void; -}) { - const { toast } = useToast(); - const createWebhook = useCreateWebhook(); - // Always call the hook — pass editing.id when editing, empty string otherwise. - // An empty string never triggers a real request (mutations are on-demand). - const updateWebhook = useUpdateWebhook(editing?.id ?? ""); - const [showSecret, setShowSecret] = useState(false); - const [form, setForm] = useState({ - url: "", - secret: "", - events: new Set(), - }); - - // Sync form when the editing target changes - // eslint-disable-next-line react-hooks/exhaustive-deps - useState(() => { - if (editing) { - setForm({ url: editing.url, secret: "", events: new Set(editing.events) }); - } else { - setForm({ url: "", secret: "", events: new Set() }); - } - }); - - function handleOpen(isOpen: boolean) { - if (!isOpen) { - setForm({ url: "", secret: "", events: new Set() }); - onClose(); - } - } - - function toggleEvent(event: string) { - setForm((f) => { - const next = new Set(f.events); - next.has(event) ? next.delete(event) : next.add(event); - return { ...f, events: next }; - }); - } - - function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - - if (form.events.size === 0) { - toast({ variant: "destructive", title: "Select at least one event" }); - return; - } - - if (editing) { - const patch: Parameters[0] = { - url: form.url || editing.url, - events: [...form.events] as CreateWebhookBody["events"], - }; - if (form.secret) patch.secret = form.secret; - - updateWebhook.mutate(patch, { - onSuccess: () => { toast({ title: "Webhook updated" }); onClose(); }, - onError: (err) => toast({ variant: "destructive", title: "Update failed", description: err.message }), - }); - } else { - if (form.url.length < 5) { - toast({ variant: "destructive", title: "Enter a valid URL" }); - return; - } - if (form.secret.length < 16) { - toast({ variant: "destructive", title: "Secret must be ≥ 16 characters" }); - return; - } - createWebhook.mutate( - { - url: form.url, - secret: form.secret, - events: [...form.events] as CreateWebhookBody["events"], - }, - { - onSuccess: () => { toast({ title: "Webhook registered" }); onClose(); }, - onError: (err) => toast({ variant: "destructive", title: "Failed to create webhook", description: err.message }), - } - ); - } - } - - const isPending = createWebhook.isPending || updateWebhook.isPending; - - return ( - - - - - - {editing ? "Edit Webhook" : "Register Webhook"} - - - -
-
- {/* URL */} -
- - setForm((f) => ({ ...f, url: e.target.value }))} - required={!editing} - /> -

- FieldTrack will POST JSON events to this URL. -

-
- - {/* Secret */} -
- -
- setForm((f) => ({ ...f, secret: e.target.value }))} - required={!editing} - className="pr-10" - /> - -
-

- Used to sign the X-FieldTrack-Signature header. -

-
- - {/* Events */} -
- -
- {WEBHOOK_EVENT_TYPES.map((event) => { - const checked = form.events.has(event); - return ( - - ); - })} -
-
-
- - - - - -
-
-
- ); -} - -// ─── Delete Confirm Dialog ──────────────────────────────────────────────────── - -function DeleteWebhookDialog({ - webhookId, - onClose, -}: { - webhookId: string | null; - onClose: () => void; -}) { - const deleteWebhook = useDeleteWebhook(); - const { toast } = useToast(); - - function handleConfirm() { - if (!webhookId) return; - deleteWebhook.mutate(webhookId, { - onSuccess: () => { toast({ title: "Webhook deleted" }); onClose(); }, - onError: (e) => { toast({ variant: "destructive", title: "Delete failed", description: e.message }); onClose(); }, - }); - } - - return ( - !open && onClose()}> - - - Delete webhook? - - This will permanently remove the webhook endpoint and all its delivery - history. This action cannot be undone. - - - - Cancel - - {deleteWebhook.isPending ? "Deleting…" : "Delete"} - - - - - ); -} - -// ─── Page ───────────────────────────────────────────────────────────────────── - -export default function WebhooksPage() { - const { data: webhooks, isLoading, error } = useWebhooks(); - const [sheetOpen, setSheetOpen] = useState(false); - const [editingWebhook, setEditingWebhook] = useState(null); - const [deletingId, setDeletingId] = useState(null); - - function openCreate() { - setEditingWebhook(null); - setSheetOpen(true); - } - - function openEdit(w: WebhookRecord) { - setEditingWebhook(w); - setSheetOpen(true); - } - - function closeSheet() { - setSheetOpen(false); - setEditingWebhook(null); - } - - return ( -
- {/* Page header */} -
-
-

- - Webhooks -

-

- Register HTTP endpoints to receive real-time FieldTrack events. -

-
- -
- - {/* Error state */} - {error && ( -
- Failed to load webhooks: {error.message} -
- )} - - {/* Loading state */} - {isLoading && ( -
- {Array.from({ length: 3 }).map((_, i) => ( -
-
- - - -
-
- - -
-
- ))} -
- )} - - {/* Empty state */} - {!isLoading && !error && (webhooks ?? []).length === 0 && ( -
-
- -
-
-

No webhooks registered

-

- Register an HTTP endpoint to receive real-time events like check-ins, - expense submissions, and employee updates. -

-
- -
- )} - - {/* Webhook cards */} - {!isLoading && (webhooks ?? []).length > 0 && ( - <> - {/* Summary bar */} -
- - {webhooks!.length}{" "} - webhook{webhooks!.length !== 1 ? "s" : ""} - - · - - - {webhooks!.filter((w) => w.is_active).length} - {" "} - active - -
- - -
- {webhooks!.map((webhook) => ( - - ))} -
-
- - )} - - {/* Global delivery history — shows all org deliveries when no specific webhook is selected */} - {!isLoading && (webhooks ?? []).length > 0 && ( -
-

- All Deliveries -

- -
- )} - - {/* Sheets + Dialogs */} - - setDeletingId(null)} /> -
- ); -} diff --git a/apps/web/src/app/(protected)/dashboard/page.tsx b/apps/web/src/app/(protected)/dashboard/page.tsx deleted file mode 100644 index 2f1bf99..0000000 --- a/apps/web/src/app/(protected)/dashboard/page.tsx +++ /dev/null @@ -1,879 +0,0 @@ -"use client"; - -import { useState, useMemo, useEffect } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import { useQueryClient } from "@tanstack/react-query"; -import { useAuth } from "@/hooks/useAuth"; -import { useOrgSummary, useLeaderboard } from "@/hooks/queries/useAnalytics"; -import { useMyDashboard } from "@/hooks/queries/useDashboard"; -import { useMyProfile } from "@/hooks/queries/useProfile"; -import { useOrgSessions } from "@/hooks/queries/useSessions"; -import { useOrgExpenses } from "@/hooks/queries/useExpenses"; -import { MetricCard } from "@/components/MetricCard"; -import { LeaderboardTable } from "@/components/charts/LeaderboardTable"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { EmptyState } from "@/components/EmptyState"; -import { StaggerList, StaggerItem, FadeUp } from "@/components/motion"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Skeleton } from "@/components/ui/skeleton"; -import { formatDistance, formatDuration, formatCurrency, formatTime } from "@/lib/utils"; -import { Activity, MapPin, Clock, Receipt, Users, Trophy, Zap, LogIn, LogOut, CheckCircle2, XCircle } from "lucide-react"; -import Link from "next/link"; -import type { OrgSummaryData, DashboardSummary, EmployeeProfileData } from "@/types"; -import { EmployeeIdentity } from "@/components/EmployeeIdentity"; -import { cn } from "@/lib/utils"; -import { todayRange } from "@/lib/dateRange"; - -// ─── Helper ─────────────────────────────────────────────────────────────────── - -function getFirstName(name: string | undefined | null, email: string | undefined | null) { - if (name) return name.split(" ")[0]; - if (email) return email.split("@")[0]; - return "there"; -} - -// ─── Admin Hero Card ────────────────────────────────────────────────────────── - -function AdminHeroCard({ - summary, - isLoading, -}: { - summary?: OrgSummaryData; - isLoading: boolean; -}) { - const { user } = useAuth(); - const { data: profile } = useMyProfile(); - const firstName = getFirstName(profile?.name, user?.email); - - return ( - - {/* Decorative circles */} -
-
- -
- {/* Left: greeting */} -
- - ADMIN - -

- Welcome back, {firstName} 👋 -

-

- Here's what's happening with your field team. -

-
- - {/* Right: key stats */} -
- {isLoading ? ( -
- {[1, 2, 3].map((i) => ( -
- ))} -
- ) : ( - <> -
-

- {(summary?.activeEmployeesCount ?? 0)} -

-

Active now

-
-
-
-

- {(summary?.totalSessions ?? 0).toLocaleString()} -

-

Sessions today

-
-
-
-

- {summary ? formatDistance(summary.totalDistanceKm) : "—"} -

-

Distance today

-
- - )} -
-
- - ); -} - -// ─── Org metrics grid ───────────────────────────────────────────────────────── - -function OrgSummarySection({ summary, isLoading }: { summary?: OrgSummaryData; isLoading: boolean }) { - const cards = [ - { - title: "Sessions Today", - value: summary?.totalSessions.toLocaleString() ?? "—", - numericValue: summary?.totalSessions, - icon: , - }, - { - title: "Distance Today", - value: summary ? formatDistance(summary.totalDistanceKm) : "—", - icon: , - }, - { - title: "Duration Today", - value: summary ? formatDuration(summary.totalDurationSeconds) : "—", - icon: , - }, - { - title: "Active Now", - value: summary?.activeEmployeesCount.toLocaleString() ?? "—", - numericValue: summary?.activeEmployeesCount, - icon: , - highlighted: true, - }, - { - title: "Expenses Today", - value: summary ? formatCurrency(summary.approvedExpenseAmount) : "—", - icon: , - }, - ]; - - return ( - - {cards.map((card) => ( - - - - ))} - - ); -} - -// ─── Activity status card ───────────────────────────────────────────────────── - -function ActivityStatusCard({ summary }: { summary?: OrgSummaryData }) { - if (!summary) { - return ( - - -
- {[1, 2, 3].map((i) => ( - - ))} -
-
-
- ); - } - - return ( - - - - - Live Activity - - - - {/* Active count with pulse */} -
-
- - - - - Active employees -
- - {summary.activeEmployeesCount} - -
- - {/* Expense breakdown */} -
-
- Total expenses - {summary.totalExpenses.toLocaleString()} -
-
- Approved - - {formatCurrency(summary.approvedExpenseAmount)} - -
-
- Rejected - - {formatCurrency(summary.rejectedExpenseAmount)} - -
-
-
-
- ); -} - -// ─── Admin leaderboard section ──────────────────────────────────────────────── - -type LeaderboardMetric = "distance" | "sessions" | "duration" | "expenses"; - -function AdminLeaderboardSection({ from, to }: { from: string; to: string }) { - const [metric, setMetric] = useState("distance"); - const { data, isLoading, error } = useLeaderboard(metric, 10, from, to); - - return ( - - - - - Top Performers Today - - setMetric(v as LeaderboardMetric)}> - - Distance - Sessions - Duration - Expenses - - - - - {error && } - {isLoading ? ( -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
- ) : ( - - )} -
- - View full leaderboard → - -
-
-
- ); -} - -// ─── Live Activity Feed ─────────────────────────────────────────────────────── - -// Avatar gradient helpers (feed-local, mirrors EmployeeIdentity palette) -const FEED_PALETTE = [ - "from-blue-500 to-indigo-600", - "from-violet-500 to-purple-600", - "from-emerald-500 to-teal-600", - "from-rose-500 to-pink-600", - "from-cyan-500 to-blue-600", - "from-fuchsia-500 to-violet-600", - "from-amber-500 to-orange-600", - "from-teal-500 to-cyan-600", -]; -function feedGradient(name: string): string { - const s = Array.from(name).reduce((a, c) => a + c.charCodeAt(0), 0); - return FEED_PALETTE[s % FEED_PALETTE.length]; -} -function feedInitials(name: string): string { - return name.trim().split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase(); -} - -type ActivityEventType = - | "SESSION_CHECKIN" - | "SESSION_CHECKOUT" - | "EXPENSE_SUBMITTED" - | "EXPENSE_APPROVED" - | "EXPENSE_REJECTED"; - -interface ActivityFeedEntry { - id: string; - eventType: ActivityEventType; - employeeId: string; - name: string; - action: string; - detail?: string; - time: string; - ts: number; - href: string; -} - -const EVENT_CONFIG: Record< - ActivityEventType, - { bg: string; fg: string; dot: string; Icon: React.ElementType } -> = { - SESSION_CHECKIN: { - bg: "bg-emerald-100 dark:bg-emerald-950/40", - fg: "text-emerald-600 dark:text-emerald-400", - dot: "bg-emerald-500", - Icon: LogIn, - }, - SESSION_CHECKOUT: { - bg: "bg-blue-100 dark:bg-blue-950/40", - fg: "text-blue-600 dark:text-blue-400", - dot: "bg-blue-500", - Icon: LogOut, - }, - EXPENSE_SUBMITTED: { - bg: "bg-orange-100 dark:bg-orange-950/40", - fg: "text-orange-600 dark:text-orange-400", - dot: "bg-orange-500", - Icon: Receipt, - }, - EXPENSE_APPROVED: { - bg: "bg-emerald-100 dark:bg-emerald-950/40", - fg: "text-emerald-600 dark:text-emerald-400", - dot: "bg-emerald-500", - Icon: CheckCircle2, - }, - EXPENSE_REJECTED: { - bg: "bg-rose-100 dark:bg-rose-950/40", - fg: "text-rose-600 dark:text-rose-400", - dot: "bg-rose-500", - Icon: XCircle, - }, -}; - -function ActivityFeedItem({ - entry, - index, -}: { - entry: ActivityFeedEntry; - index: number; -}) { - const cfg = EVENT_CONFIG[entry.eventType]; - const { Icon } = cfg; - - return ( - - - {/* Avatar + event-type badge */} -
-
- {feedInitials(entry.name)} -
-
- -
-
- - {/* Content */} -
-

- - {entry.name} - {" "} - {entry.action} - {entry.detail && ( - · {entry.detail} - )} -

-

{entry.time}

-
- - {/* Event type dot */} -
- - - ); -} - -function TodayActivityFeed() { - const queryClient = useQueryClient(); - const sessions = useOrgSessions(1, 50); - const expenses = useOrgExpenses(1, 50); - - // Auto-refresh every 30 s so the feed stays live without a page reload - useEffect(() => { - const id = setInterval(() => { - void queryClient.invalidateQueries({ queryKey: ["orgSessions"] }); - void queryClient.invalidateQueries({ queryKey: ["orgExpenses"] }); - }, 30_000); - return () => clearInterval(id); - }, [queryClient]); - - const today = useMemo(() => { - const d = new Date(); - d.setHours(0, 0, 0, 0); - return d; - }, []); - - const feed = useMemo(() => { - const entries: ActivityFeedEntry[] = []; - - for (const s of sessions.data?.data ?? []) { - const name = s.employee_name ?? "Unknown"; - - // Session check-in - const checkinTs = new Date(s.checkin_at); - if (checkinTs >= today) { - entries.push({ - id: `checkin-${s.id}`, - eventType: "SESSION_CHECKIN", - employeeId: s.employee_id, - name, - action: "checked in", - time: formatTime(s.checkin_at), - ts: checkinTs.getTime(), - href: "/admin/sessions", - }); - } - - // Session check-out - if (s.checkout_at) { - const checkoutTs = new Date(s.checkout_at); - if (checkoutTs >= today) { - entries.push({ - id: `checkout-${s.id}`, - eventType: "SESSION_CHECKOUT", - employeeId: s.employee_id, - name, - action: "completed session", - detail: - s.total_duration_seconds != null - ? formatDuration(s.total_duration_seconds) - : undefined, - time: formatTime(s.checkout_at), - ts: checkoutTs.getTime(), - href: "/admin/sessions", - }); - } - } - } - - for (const e of expenses.data?.data ?? []) { - const name = e.employee_name ?? "Unknown"; - - // Expense submitted - const submittedTs = new Date(e.submitted_at); - if (submittedTs >= today) { - entries.push({ - id: `expense-sub-${e.id}`, - eventType: "EXPENSE_SUBMITTED", - employeeId: e.employee_id, - name, - action: "submitted expense", - detail: formatCurrency(e.amount), - time: formatTime(e.submitted_at), - ts: submittedTs.getTime(), - href: "/admin/expenses", - }); - } - - // Expense reviewed (approved / rejected) - if (e.reviewed_at && (e.status === "APPROVED" || e.status === "REJECTED")) { - const reviewedTs = new Date(e.reviewed_at); - if (reviewedTs >= today) { - entries.push({ - id: `expense-review-${e.id}`, - eventType: - e.status === "APPROVED" ? "EXPENSE_APPROVED" : "EXPENSE_REJECTED", - employeeId: e.employee_id, - name, - action: - e.status === "APPROVED" ? "expense approved" : "expense rejected", - detail: formatCurrency(e.amount), - time: formatTime(e.reviewed_at), - ts: reviewedTs.getTime(), - href: "/admin/expenses", - }); - } - } - } - - return entries.sort((a, b) => b.ts - a.ts).slice(0, 20); - }, [sessions.data, expenses.data, today]); - - const isLoading = sessions.isLoading && expenses.isLoading; - - return ( - - -
- - - Today's Activity - -
- - - - - Live -
-
-
- - - {isLoading ? ( -
- {Array.from({ length: 8 }).map((_, i) => ( -
-
-
-
-
-
-
- ))} -
- ) : feed.length === 0 ? ( -
- -

- No activity yet today -

-

- Field events appear here as employees check in. -

-
- ) : ( - -
- {feed.map((entry, idx) => ( - - ))} -
-
- )} - - - {!isLoading && feed.length > 0 && ( -
- - {feed.length} event{feed.length !== 1 ? "s" : ""} today - - - All sessions → - -
- )} - - ); -} - -// ─── Team Activity Widget ───────────────────────────────────────────────────── - -function TeamActivityWidget({ from, to }: { from: string; to: string }) { - const { data, isLoading } = useLeaderboard("sessions", 7, from, to); - - return ( - - - - - Top Performers - - - - {isLoading ? ( -
- {Array.from({ length: 5 }).map((_, i) => ( -
-
-
-
-
-
-
-
- ))} -
- ) : ( -
- {(data ?? []).map((entry, idx) => ( -
- - - {entry.sessions}s - -
- ))} -
- )} - - - ); -} - -// ─── Admin dashboard ────────────────────────────────────────────────────────── - -function AdminDashboard() { - const { from, to } = useMemo(() => todayRange(), []); - const summary = useOrgSummary(from, to); - - return ( -
- {summary.error && } - - {/* Hero banner */} - - - {/* Today's metrics row */} - - - {/* Today's activity feed + Live status */} - -
-
- -
-
- - -
-
-
- - {/* Today's leaderboard */} - - - -
- ); -} - -// ─── Employee Hero Card ─────────────────────────────────────────────────────── - -function EmployeeHeroCard({ - profile, - dashboard, - rank, - isLoading, -}: { - profile?: EmployeeProfileData; - dashboard?: DashboardSummary; - rank?: number; - isLoading: boolean; -}) { - const initials = profile?.name - ? profile.name - .split(" ") - .slice(0, 2) - .map((n) => n[0]) - .join("") - .toUpperCase() - : "?"; - - const statusLabel = - profile?.activityStatus === "ACTIVE" - ? "Active" - : profile?.activityStatus === "RECENT" - ? "Recently Active" - : "Inactive"; - - return ( - -
- -
-
- {/* Avatar */} -
- {isLoading ? "…" : initials} -
-
- - EMPLOYEE - -

- {isLoading ? ( - - ) : ( - profile?.name ?? "—" - )} -

- {/* Inline status indicator */} -
- - {profile?.activityStatus === "ACTIVE" && ( - - )} - - - {statusLabel} -
-
-
- - {/* Rank badge */} - {rank != null && ( -
- -
-

Distance Rank

-

#{rank}

-
-
- )} -
- - ); -} - -// ─── Employee dashboard ──────────────────────────────────────────────────────── - -function EmployeeDashboard() { - const { data: dashboard, isLoading: dashLoading, error: dashError } = useMyDashboard(); - const { data: profile, isLoading: profileLoading } = useMyProfile(); - const { data: leaderboard, isLoading: lbLoading } = useLeaderboard("distance", 10); - - const myRank = profile - ? leaderboard?.find((e) => e.employeeId === profile.id)?.rank - : undefined; - - const isLoading = dashLoading || profileLoading; - - if (dashError) return ; - - const stats = dashboard - ? [ - { - title: "Sessions This Week", - value: dashboard.sessionsThisWeek.toLocaleString(), - numericValue: dashboard.sessionsThisWeek, - icon: , - }, - { - title: "Distance This Week", - value: formatDistance(dashboard.distanceThisWeek), - icon: , - }, - { - title: "Hours Worked", - value: `${dashboard.hoursThisWeek.toFixed(1)} hrs`, - icon: , - }, - { - title: "Expenses Submitted", - value: dashboard.expensesSubmitted.toLocaleString(), - numericValue: dashboard.expensesSubmitted, - icon: , - }, - { - title: "Expenses Approved", - value: dashboard.expensesApproved.toLocaleString(), - numericValue: dashboard.expensesApproved, - icon: , - highlighted: true, - }, - ] - : []; - - return ( -
- {/* Hero card */} - - - {/* Weekly stats */} - - {stats.map((s) => ( - - - - ))} - - - {/* Leaderboard preview */} - - - - - - Distance Leaderboard - - - Full leaderboard → - - - - {lbLoading ? ( -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
- ) : ( - - )} -
-
-
-
- ); -} - -// ─── Page ───────────────────────────────────────────────────────────────────── - -export default function DashboardPage() { - const { permissions } = useAuth(); - return permissions.viewAnalytics ? : ; -} - diff --git a/apps/web/src/app/(protected)/error.tsx b/apps/web/src/app/(protected)/error.tsx deleted file mode 100644 index 946927a..0000000 --- a/apps/web/src/app/(protected)/error.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { AlertTriangle } from "lucide-react"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; - -interface ErrorPageProps { - error: Error & { digest?: string }; - reset: () => void; -} - -export default function ProtectedError({ error, reset }: ErrorPageProps) { - const router = useRouter(); - - useEffect(() => { - console.error("[FieldTrack] Error in protected area:", error); - }, [error]); - - return ( -
- - -
- -
- Something went wrong -
- -

- An unexpected error occurred while loading this page. -

- {error.digest && ( -

- Error ID: {error.digest} -

- )} -
- - -
-
-
-
- ); -} diff --git a/apps/web/src/app/(protected)/expenses/page.tsx b/apps/web/src/app/(protected)/expenses/page.tsx deleted file mode 100644 index e57daea..0000000 --- a/apps/web/src/app/(protected)/expenses/page.tsx +++ /dev/null @@ -1,122 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useMyExpenses, useCreateExpense } from "@/hooks/queries/useExpenses"; -import { ExpensesTable } from "@/components/tables/ExpensesTable"; -import { ErrorBanner } from "@/components/ErrorBanner"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { useToast } from "@/components/ui/use-toast"; - -const PAGE_LIMIT = 20; - -export default function ExpensesPage() { - const [page, setPage] = useState(1); - const { data, isLoading, error } = useMyExpenses(page, PAGE_LIMIT); - const createExpense = useCreateExpense(); - const { toast } = useToast(); - - const [amount, setAmount] = useState(""); - const [description, setDescription] = useState(""); - const [receiptUrl, setReceiptUrl] = useState(""); - - const expenses = data?.data ?? []; - const total = data?.pagination.total ?? 0; - const hasMore = page * PAGE_LIMIT < total; - - function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - const parsedAmount = parseFloat(amount); - if (isNaN(parsedAmount) || parsedAmount <= 0) { - toast({ variant: "destructive", title: "Invalid amount", description: "Enter a positive number." }); - return; - } - createExpense.mutate( - { amount: parsedAmount, description, receipt_url: receiptUrl || undefined }, - { - onSuccess: () => { - toast({ title: "Expense submitted", description: "Your expense claim has been submitted for review." }); - setAmount(""); - setDescription(""); - setReceiptUrl(""); - }, - onError: (err) => { - toast({ variant: "destructive", title: "Submission failed", description: err.message }); - }, - } - ); - } - - return ( -
-
-

My Expenses

-

Submit and track your expense claims.

-
- - - - Submit New Expense - - -
-
-
- - setAmount(e.target.value)} - required - /> -
-
- - setReceiptUrl(e.target.value)} - /> -
-
-
- -