System Guide & Handover

This page explains how the whole Passaic 360 system fits together so a new team can understand, run, and extend it.

System Design (Architecture View)What Passaic 360 IsHigh-Level Architecture & Request FlowRoles, Permissions & Session ModelHow Major Flows Work (End-to-End)Tech Stack & Important ModulesConfiguration & Environment VariablesHow to Run / Build / DeployCore Data Model (Summary)Security, RLS & Admin Safety ChecksHandover Notes for Future Teams

System Design (Architecture View)

Logical components: (1) Web client (Next.js app), (2) API layer (Express), (3) Data/Auth layer (Supabase), all running as separate deployable units but sharing a single Supabase project.

Frontend responsibilities: routing and UI, calling typed helpers in frontend/src/lib/api.ts, managing auth session using AuthContext, and rendering role-specific dashboards (user, employer, admin).

Backend responsibilities: validating inputs, enforcing roles with middleware (requireAuth/requireAdmin), orchestrating reads/writes to Supabase, and implementing cross-table logic (e.g. user history summary, posting lifecycle, audit logging).

Data/Auth responsibilities: Supabase Auth issues JWTs; Postgres stores normalized tables with row-level security. The backend uses a service role key for privileged operations; the frontend never sees this key.

Deployment view: typically, the frontend is deployed on a static/Node host, the backend is a Node process or container behind HTTPS, and Supabase runs as a managed service. All environments (dev/stage/prod) use the same schema migrations.

What Passaic 360 Is

Passaic 360 is a full‑stack web platform for Passaic County, New Jersey. It tracks SNAP work compliance hours, supports housing readiness, and coordinates volunteering / food rescue activities.

The system is multi‑tenant by role: residents/volunteers, employers/organizations, and county admins share a single Supabase project and database but see different UI and data shapes.

High-Level Architecture & Request Flow

Frontend (frontend/): Next.js 15 App Router, TypeScript and Tailwind. The app is internationalized with next-intl and uses a locale segment ([locale]) for all user‑facing routes.

Backend (backend/): Express + TypeScript. Single entry point at src/index.ts mounts feature routers under /auth, /profile, /timesheet, /organization, /admin, plus /health and /config.

Data & Auth: Supabase (Postgres + Auth). Auth users live in Supabase auth; domain entities live in public tables (user_profile, timesheet, volunteering_companies, volunteering_postings, volunteer_applicant, admin_audit_log, etc.).

Typical request: Browser → Next.js page → frontend/src/lib/api.ts helper → backend Express endpoint → Supabase JS client → Postgres. Auth tokens are provided by Supabase Auth and forwarded as Bearer tokens to the backend when needed.

Roles, Permissions & Session Model

Roles are stored redundantly in two places: Supabase auth.user_metadata.role and the user_profile.role column. The backend always prefers user_profile as source of truth.

User (resident/volunteer): default role created via /auth/signup. Can manage their profile, submit timesheets, and discover/apply to active volunteering postings.

Employer / Organization: role=employer, created via /auth/signup-employer or /auth/signup-organization and then /organization/onboarding. Employers are allowed to create, edit and pause/close only their own postings.

Admin: role=admin. Admins must log in through /auth/admin-login, which validates credentials and then checks user_profile.role === "admin". Non‑admins attempting admin-login are automatically logged out and get HTTP 403.

Session model: The backend returns Supabase Auth sessions (access_token, refresh_token, user, expires_at) which the frontend stores and attaches to subsequent API calls via helpers in frontend/src/lib/api.ts.

How Major Flows Work (End-to-End)

Account & Role Assignment: On signup, the backend creates a Supabase auth user, then upserts into user_profile with auth_id and role. If profile creation fails, the newly created auth user is rolled back for consistency.

Profile Management: The /profile router reads/writes user_profile rows, parsing and re‑serializing Address JSON. PUT /profile always sets name/phone/address fields so that clearing the form also clears DB values.

Timesheet Logging: Timesheet entries are always tied to the current user’s profile.id via email lookup. Validation includes strict YYYY‑MM‑DD date parsing and non‑negative integer minutes_worked. Images are stored as string paths only (no binary).

Timesheet Approval: Admins work against /admin/timesheets and /admin/timesheets/:id. Changing admin_approve to true/false/null also writes to admin_audit_log with the action type (approve/reject/reset) and optional org_note payload.

Volunteering Postings: Employers must have a volunteering_companies row (organization onboarding) before posting. New postings start in draft or pending; admins use /admin/postings/:id to approve to active or reject, and the admin action is logged.

Applications: When a user applies via /organization/postings/:id/apply, the backend re‑checks that the posting exists and is status=active before inserting into volunteer_applicant. Legacy compatibility is handled if posting_id is missing from schema.

Org-side Views: /organization/postings/:id/applicants ensures the posting belongs to the caller’s company_id before returning applicants; there is no way to see other organizations’ applicants.

Admin User History: /admin/users/:userId aggregates user_profile, all timesheets, and for employers their volunteering_companies and volunteering_postings. For admins it also fetches admin_audit_log entries and builds summary counts.

Tech Stack & Important Modules

Frontend: Next.js 15 (App Router) + React 19 + TypeScript. Styling is Tailwind CSS. Routing is locale‑aware via next-intl, configured in src/i18n.

Key frontend modules: src/lib/api.ts (typed API client and AuthSession types), src/contexts/AuthContext.tsx (session provider), and src/components/* for all dashboard/admin/org flows.

Backend: Node.js + Express 4 + TypeScript. src/index.ts wires routers; each router file (auth.ts, profile.ts, timesheet.ts, organization.ts, admin.ts) encapsulates business logic for a domain.

Database/Auth: Supabase JS client (backend/src/lib/supabase.ts) talks to Postgres and Auth. Supabase row‑level security policies live in the SQL migrations under supabase/migrations.

Configuration & Environment Variables

backend/.env (see backend/.env.example): SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY (server‑side admin key), SUPABASE_ANON_KEY (for /config passthrough), PORT, and CORS_ORIGIN (frontend origin).

frontend/.env.local (see frontend/.env.example): NEXT_PUBLIC_API_URL, which should point to the backend base URL (e.g. https://api.passaic360.com). No secret keys should live in the frontend env.

Supabase dashboard: Project URL and keys are configured once; migrations in supabase/migrations are applied to keep schema in sync across environments.

How to Run / Build / Deploy

Local backend dev: cd backend && npm install && npm run dev (tsx watch src/index.ts, default http://localhost:4000).

Local frontend dev: cd frontend && npm install && npm run dev (Next.js dev server; default http://localhost:3000 but Next will choose another free port if needed).

Production build: Backend uses npm run build && npm start (compiled to dist/). Frontend uses npm run build && npm start served by Next.js.

Deploy pattern (typical): 1) Provision Supabase project and apply SQL migrations, 2) Deploy backend behind HTTPS (Node process or container), 3) Deploy frontend on a static/Node host with NEXT_PUBLIC_API_URL pointing to the backend.

Core Data Model (Summary)

user_profile: One row per person; stores contact info, role and auth_id. Email is used as natural key for lookups; auth_id links back to Supabase Auth.

timesheet: Time entries with employer_name, work_date, location_text, minutes_worked, notes, up to 4 image paths (imgpath, imgpath_2, imgpath_3, imgpath_4), admin_approve and status (pending | approved | rejected).

volunteering_companies: Organization profile per employer. userid is a foreign key to user_profile.id, and there is a unique constraint so each employer account has at most one organization row.

volunteering_postings: Volunteering opportunities linked to volunteering_companies via company_id. Contains metadata (title, description, category, schedule, age/skill requirements, spots, and status lifecycle).

volunteer_applicant: Application rows with applicants_Firstname, applicants_lastname, contact fields and a posting_id foreign key (with backwards compatibility when posting_id is temporarily missing).

admin_audit_log: Append‑only table that records admin_profile_id, action_type, target_type, target_id, optional details JSON, and timestamps. Used to build per‑admin activity summaries.

Security, RLS & Admin Safety Checks

Supabase row-level security policies (see supabase/migrations/*.sql) restrict direct table access. For example, volunteering_companies policies ensure only the owner (by auth.uid → user_profile.id) can read or write their org row.

Backend middleware requireAuth extracts the user email from the Supabase JWT and attaches it to the Express request (AuthenticatedRequest). requireAdmin then verifies user_profile.role === "admin" before entering adminRouter.

Admin endpoints validate identifiers (UUID regex for posting and timesheet IDs) and always re‑fetch records with ownership constraints (e.g. company_id matching the employer) before performing updates.

Handover Notes for Future Teams

Use /workflow for sequence diagrams, /documentation for precise REST contracts, and this /system page for mental model and deployment notes.

Most backend domain logic is in backend/src/routes/*.ts; if you add a new feature, follow the same pattern: validate input, look up user_profile, enforce role, then call Supabase.

Supabase migrations in supabase/migrations are the authoritative source for schema; always update migrations (not just the live DB) when adding/changing tables or columns.

When modifying roles/permissions, make sure user_profile, Supabase Auth metadata, backend checks (requireAdmin, etc.) and RLS policies all agree to avoid privilege escalation or lockouts.

For endpoint-level details, see API Docs and for visual flows, see Workflows.

Passaic 360 | Passaic360