Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Taykl12/Classify/llms.txt

Use this file to discover all available pages before exploring further.

Classify is organized as a pnpm monorepo containing a React 19 frontend, an Express 5 TypeScript backend, and firmware for an ESP32-C3 hardware device — all sharing a single repository root. The Supabase platform provides authentication, a PostgreSQL database, and Row-Level Security policies that enforce data access rules at the database layer, independent of application logic.

Repository Layout

The directory tree below shows the top-level structure of the monorepo. The React source lives in src/, the Express server in server/src/, Supabase migration SQL files in supabase/migrations/, and the ESP32 firmware in esp32/.
classify/
├── pnpm-workspace.yaml          # Declares pnpm workspaces
├── package.json                 # Root scripts (dev, build, preview)
├── vite.config.ts               # Vite config — proxy /api → :3001
├── tsconfig.json                # Root TypeScript config

├── src/                         # React 19 frontend
│   ├── main.tsx                 # ReactDOM.createRoot entrypoint
│   ├── App.tsx                  # Router + AuthProvider + ThemeProvider
│   ├── routes.ts                # ROUTES constant (all path strings)
│   ├── lib/
│   │   └── api.ts               # apiFetch, apiFetchWithRetry
│   ├── contexts/
│   │   ├── AuthContext.tsx
│   │   └── ThemeContext.tsx
│   ├── components/
│   │   ├── auth/
│   │   ├── layout/
│   │   ├── dashboard/
│   │   └── projects/
│   └── pages/
│       ├── DashboardPage.tsx
│       ├── ProjectsPage.tsx
│       ├── ProjectConfigPage.tsx
│       ├── LoginPage.tsx
│       ├── RegisterPage.tsx
│       ├── admin/               # AdminCursosPage, AdminUsersPage, …
│       └── professor/           # ProfessorCoursesPage, ProfessorAttendancePage, …

├── server/                      # Express 5 backend (classify-server package)
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts             # listen() on PORT (default 3001)
│       ├── app.ts               # Express app — CORS, JSON, route mounts
│       ├── config.ts            # Reads .env: SUPABASE_URL, keys, PORT, …
│       ├── middleware/
│       │   ├── auth.ts          # Validates Bearer JWT
│       │   ├── admin.ts         # Requires role === 1
│       │   └── professor.ts     # Requires role <= 2
│       ├── routes/
│       │   ├── auth.ts
│       │   ├── projects.ts
│       │   ├── dashboard.ts
│       │   ├── tasks.ts
│       │   ├── calendar.ts
│       │   ├── professor.ts
│       │   ├── admin.ts
│       │   ├── adminProjects.ts
│       │   ├── users.ts
│       │   ├── profile.ts
│       │   └── esp32Device.ts
│       └── lib/
│           ├── supabase.ts      # createAnonClient / createUserClient
│           ├── authUser.ts
│           ├── mappers.ts
│           ├── projectAccess.ts
│           ├── projectMembers.ts
│           ├── projectOwner.ts
│           ├── roles.ts
│           └── esp32State.ts

├── supabase/
│   └── migrations/              # 15 ordered SQL migrations (001–015)

├── esp32/
│   └── src/main.cpp             # ESP32-C3 SuperMini firmware (Arduino/C++)

├── start-api.bat                # Windows: launch Express in new window
├── start-vite.bat               # Windows: launch Vite in new window
└── start-dev.bat                # Windows: launch both servers at once

Frontend — React + Vite

The frontend is a single-page application bootstrapped with Vite 6 and written in React 19 + TypeScript. Routing is handled by react-router-dom; the full set of route path strings is centralised in src/routes.ts as the ROUTES constant, preventing hard-coded strings from spreading across the codebase. Styling uses vanilla CSS with a variables.css design-token file. There is no CSS framework or utility-class library — all component styles reference custom properties (e.g. --color-primary, --spacing-md) defined in that token file. Global providers are composed in App.tsx in the following order:
<AuthProvider>
  <ThemeProvider>
    <RouterProvider router={router} />
  </ThemeProvider>
</AuthProvider>
AuthContext exposes the current user, the raw JWT, and the logout helper. ThemeContext manages the light/dark theme preference persisted to localStorage.

API Client and the Vite Proxy

All HTTP calls from the frontend go through src/lib/api.ts, which exposes two functions:
  • apiFetch(path, options?) — attaches the Authorization: Bearer <token> header from localStorage.classify_access_token, calls fetch, and triggers an automatic logout + redirect to /login on a 401 response.
  • apiFetchWithRetry(path, options?, retries = 3) — wraps apiFetch and re-attempts the request up to three times when the server returns 502, 503, or 504 (gateway/upstream errors that are transient in nature).
In development, vite.config.ts declares a proxy rule that rewrites any request starting with /api to http://localhost:3001. This means the React app always calls relative URLs like /api/auth/login, and Vite silently forwards them to Express — no CORS headers are involved in the local dev flow.
// vite.config.ts (simplified)
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
      },
    },
  },
})

Backend — Express 5

The Express application is defined in server/src/app.ts and started in server/src/index.ts. All configuration is loaded from environment variables via server/src/config.ts, which exports typed constants (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, PORT, APP_ORIGIN, ESP32_DEVICE_TOKEN).

Middleware Chain

Most incoming requests pass through the following layers in order:
  1. CORSAPP_ORIGIN is the only allowed origin; credentials are permitted.
  2. /api/device/esp32 — the ESP32 device router is mounted here, before express.json(), so it handles raw device payloads independently.
  3. express.json() — parses JSON request bodies for all other routes.
  4. Route-specific middleware — applied per-router, not globally:
    • auth.ts — verifies the Authorization: Bearer JWT with Supabase and attaches the decoded user to req.user.
    • admin.ts — rejects requests where req.user.role !== 1.
    • professor.ts — rejects requests where req.user.role > 2.

Route Modules

ModuleMount pathProtection
auth.ts/api/authPublic
dashboard.ts/api/dashboardAuth required
projects.ts/api/projectsAuth required
tasks.ts/api/tasksAuth required
calendar.ts/api/calendarAuth required
professor.ts/api/professorProfessor + Admin
admin.ts/api/adminAdmin only
esp32Device.ts/api/device/esp32Device token
users.ts/api/usersAuth required
profile.ts/api/profileAuth required
adminProjects.ts/api/admin/proyectosAdmin only

ESP32 Device Layer

The esp32Device.ts route and esp32State.ts library handle communication with the ESP32-C3 SuperMini hardware. The device authenticates using the ESP32_DEVICE_TOKEN shared secret (passed as a header), polls for pending attendance commands, and posts back scan results. esp32State.ts maintains in-memory state on the server representing the device’s last-known status and pending operations.

Database — Supabase

Classify uses Supabase as both its authentication provider and its PostgreSQL database host. The project reference is jgrtmokyqdvdxsldmkou.
The supabase/migrations/ directory contains 15 SQL migration files (numbered 001 through 015). They must be applied in order to create the full schema — including tables for users, courses, subjects, projects, tasks, attendance records, and calendar events — along with all RLS policies. Use the Supabase CLI (supabase db push) or apply them manually in the SQL editor.

Supabase Client Strategy

server/src/lib/supabase.ts exports two factory functions that are used throughout the server:
// Creates a client using the anon key — subject to RLS policies
createAnonClient(): SupabaseClient

// Creates a client authenticated as a specific user — RLS evaluates as that user
createUserClient(accessToken: string): SupabaseClient
Route handlers that act on behalf of an authenticated user call createUserClient(req.user.accessToken) so that Supabase RLS policies see the correct auth.uid() and evaluate row visibility accordingly. Admin routes that need to bypass RLS use a service-role client initialized with SUPABASE_SERVICE_ROLE_KEY.

Row-Level Security

All database tables have RLS enabled. Policies are expressed in SQL using auth.uid() and auth.jwt() ->> 'role' to restrict SELECT, INSERT, UPDATE, and DELETE operations. A student, for example, can only select rows in the attendance table where their user ID matches — the restriction lives in the database, not only in Express middleware.

Role System

Roles are stored as integer values in the users table and carried inside the JWT issued by Supabase on login.
Role nameIntegerExpress middlewareAccessible routes
admin1admin.tsAll routes
profesor2professor.tsAll except admin
alumno3(auth only)Student-facing routes
server/src/lib/roles.ts exports helpers for checking role membership, and the admin.ts / professor.ts middleware files use those helpers to short-circuit unauthorized requests with a 403 Forbidden response before the route handler is ever invoked.

Authentication Flow

The sequence below describes what happens from the moment a user submits the login form to the moment a protected API response is returned.
Browser                    Vite Dev Server           Express             Supabase
   │                            │                       │                    │
   │  POST /api/auth/login       │                       │                    │
   │ ─────────────────────────► │                       │                    │
   │                            │  proxy → :3001        │                    │
   │                            │ ─────────────────────►│                    │
   │                            │                       │  signInWithPassword│
   │                            │                       │ ──────────────────►│
   │                            │                       │   { access_token } │
   │                            │                       │ ◄──────────────────│
   │  { access_token, user }    │                       │                    │
   │ ◄─────────────────────────────────────────────────│                    │
   │                            │                       │                    │
   │  (token stored in          │                       │                    │
   │   localStorage)            │                       │                    │
   │                            │                       │                    │
   │  GET /api/dashboard        │                       │                    │
   │  Authorization: Bearer …   │                       │                    │
   │ ─────────────────────────► │                       │                    │
   │                            │ ─────────────────────►│                    │
   │                            │                  auth middleware            │
   │                            │                  verifies JWT  ───────────►│
   │                            │                       │   { user, role }   │
   │                            │                       │ ◄──────────────────│
   │                            │                  route handler runs        │
   │  200 { dashboardData }     │                       │                    │
   │ ◄─────────────────────────────────────────────────│                    │
If Express returns a 401 at any point, apiFetch on the client side removes classify_access_token from localStorage and redirects the user to /login, ensuring stale or revoked tokens never accumulate silently.

Build docs developers (and LLMs) love