diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c388968 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# ----- Server ----- +NODE_ENV=development +PORT=3000 + +# ----- JWT ----- +JWT_SECRET=replace-me-with-a-long-random-string +JWT_EXPIRES_IN=30d +JWT_COOKIE_NAME=jwtCookie + +# ----- PostgreSQL ----- +DB_USER=postgres +DB_HOST=localhost +DATABASE=issuetrack +DB_PASSWORD=postgres +DB_PORT=5432 diff --git a/.gitignore b/.gitignore index 1dcef2d..9cf3b7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,19 @@ node_modules -.env \ No newline at end of file +.env +.env.local +.env.*.local + +# build output +frontend/dist +dist/ + +# logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# editors / OS +.DS_Store +.vscode/ +.idea/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..d6b0d0d --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,194 @@ +# Architecture + +A bird's-eye tour of how IssueTrack is organized, how data flows through it, and why. + +## 1. High-level topology + +``` +┌──────────────┐ HTTPS ┌────────────────────────┐ SQL ┌──────────────┐ +│ Browser │ ◀────────────────▶│ Express API (Node) │◀───────────▶│ PostgreSQL │ +│ React SPA │ /api/* (proxy) │ backend/src │ │ │ +└──────────────┘ └────────────────────────┘ └──────────────┘ + ▲ ▲ + │ httpOnly JWT cookie │ bcrypt, jsonwebtoken, pg + └──────────────────────────────────────┘ +``` + +- **Frontend** — Vite + React 19 + Tailwind v4. No global state library — Context is + enough for the current surface area. +- **Backend** — Express 5 (ESM) with a small, opinionated middleware stack. +- **Database** — a single PostgreSQL instance (three tables). + +## 2. Backend layout + +``` +backend/src/ +├── app.js # composes the Express app +├── server.js # boots the app; handles signals +├── config/ +│ ├── env.js # validated env access +│ └── constants.js # ROLES, ISSUE_STATUS, cookie TTL +├── db/ +│ ├── pool.js # pg Pool singleton +│ └── migrate.js # idempotent table creation +├── middlewares/ +│ ├── asyncHandler.js # wraps async controllers, forwards errors +│ ├── authenticate.js # verifies JWT cookie → req.user +│ ├── authorize.js # role allow-list factory +│ ├── errorHandler.js # central error serializer +│ ├── notFound.js # 404 fallback +│ └── requestLogger.js # method / path / status / duration +├── modules/ +│ ├── auth/ { controller, service, repository, routes } +│ ├── issues/ { controller, service, repository, routes } +│ └── solutions/ { controller, service, repository, routes } +└── utils/ + ├── ApiError.js # typed error class + ├── jwt.js # sign/verify + cookie helpers + ├── logger.js # leveled console logger + └── password.js # bcrypt helpers +``` + +### Layered flow + +``` +HTTP request + │ + ▼ +routes.js ── applies authenticate / authorize middleware + │ + ▼ +controller.js ── parses req, delegates to service, serializes res + │ + ▼ +service.js ── validates input, enforces invariants, throws ApiError + │ + ▼ +repository.js ── parameterized SQL against pg Pool + │ + ▼ +PostgreSQL +``` + +Any thrown error (including `throw` from a service) is caught by `asyncHandler` and +formatted by `errorHandler` into `{ error, code?, details? }` with the right status. + +## 3. Frontend layout + +``` +frontend/src/ +├── main.jsx # mounts inside providers +├── App.jsx # Toaster + +├── routes/ +│ ├── AppRoutes.jsx # route table +│ ├── ProtectedRoute.jsx # requires authUser +│ └── GuestRoute.jsx # redirects logged-in users +├── api/ +│ ├── client.js # fetch wrapper + ApiClientError +│ ├── auth.api.js +│ ├── issues.api.js +│ └── solutions.api.js +├── context/ +│ └── AuthContext.jsx # authUser + localStorage sync +├── hooks/ +│ └── useAuth.js +├── components/ +│ ├── layout/ # AppShell, TopBar, Footer, PageHeader +│ ├── ui/ # Button, Input, Textarea, Panel, … +│ ├── issues/ # IssueCard, IssueDetails, IssueForm +│ └── solutions/ # SolutionPanel, SolutionForm +├── pages/ # Home, Login, Register, Issues, Issue, NotFound +├── lib/ # format helpers, cn() +└── styles/ + └── index.css # Tailwind entry + design tokens +``` + +### Data flow + +``` +User action (click, submit) + │ + ▼ +Component handler + │ + ▼ +hook or direct call → api/*.api.js + │ + ▼ +api/client.js (fetch + error normalization) + │ + ▼ +Express backend +``` + +Pages own fetching for their own data. Components receive data as props and report +events via callbacks — they never reach back into parents. + +## 4. Error model + +### Backend + +`ApiError(statusCode, message, { code, details })` is the **only** way to fail. Helpers: + +| Factory | Status | +| --------------------------- | ------ | +| `ApiError.badRequest` | 400 | +| `ApiError.unauthorized` | 401 | +| `ApiError.forbidden` | 403 | +| `ApiError.notFound` | 404 | +| `ApiError.conflict` | 409 | +| `ApiError.internal` | 500 | + +Wire format (all errors): `{ "error": "message", "code"?: "SLUG", "details"?: … }`. + +### Frontend + +`ApiClientError(message, { status, code, details })` is thrown by `api/client.js` on any +non-2xx response. Components catch it, surface the message via `react-hot-toast`, and +fall back to a sane generic message if needed. + +## 5. Auth flow + +``` +POST /api/auth/register | /api/auth/login + │ + ▼ +service validates credentials / uniqueness + │ + ▼ +repository INSERT / SELECT + │ + ▼ +controller signs JWT, sets httpOnly cookie, returns user JSON +``` + +Subsequent requests rely on `authenticate` middleware which re-hydrates `req.user` from +the cookie. `authorize(ROLES.ADMIN)` gates admin-only routes (solution creation). + +## 6. Database schema + +See `backend/src/db/migrate.js` for the canonical definitions. Tables are created +idempotently at boot time. + +``` +users (id, name, admission_number UNIQUE, password, gender, profilePic, role, created_at) +issues (id, title, description, status, user_id → users ON DELETE SET NULL, created_at, updated_at) +solutions (id, issue_id → issues ON DELETE CASCADE, user_id → users ON DELETE SET NULL, + description, created_at) +``` + +## 7. Environments + +| Env | Command | Notes | +| -------------- | ----------------------- | --------------------------------------- | +| Dev — API | `npm run server` | nodemon, reads `.env` | +| Dev — Web | `cd frontend && npm run dev` | Vite dev server on :5000, proxy → :3000 | +| Prod — API | `npm start` | plain `node backend/src/server.js` | +| Prod — Web | `cd frontend && npm run build` | emits `frontend/dist/` | + +## 8. Future extensions + +- Swap repositories for a single-file `drizzle-orm`/`kysely` query builder if SQL grows. +- Add `zod` for service-level input validation (currently hand-rolled). +- Add integration tests under `backend/tests/` using `supertest`. +- Introduce `react-query` once pages need shared caching. diff --git a/CONSTITUTION.md b/CONSTITUTION.md new file mode 100644 index 0000000..b137b09 --- /dev/null +++ b/CONSTITUTION.md @@ -0,0 +1,104 @@ +# IssueTrack · Project Constitution + +> The principles, guarantees, and non-negotiables that govern every line of code, every +> pull request, and every architectural decision in this project. + +--- + +## Article I — Mission + +IssueTrack exists to give institutions a **fast, honest, and auditable** way to move a +problem from *"someone complained"* to *"here is the fix"* — without losing the issue in +a mailbox, a spreadsheet, or a Slack thread. The platform is intentionally small. It +should remain **small**, **inspectable**, and **self-hostable** by a single maintainer. + +## Article II — Non-Negotiables + +These rules are not up for debate in a normal PR. Changing any of them requires an +explicit proposal in `ARCHITECTURE.md` and a maintainer sign-off. + +1. **One source of truth per concern.** + Business logic lives in services. Data access lives in repositories. Controllers are + thin request/response adapters. Components render; hooks own state; the API layer + owns IO. +2. **Fail loudly, recover gracefully.** + Every backend handler throws typed `ApiError`s; a single `errorHandler` middleware + formats them. Frontend uses a single `apiClient` that normalizes errors. `console.log` + is never a substitute for a thrown error. +3. **Sharp edges only.** + The UI has a deliberate editorial / neo-brutalist aesthetic. `border-radius` is + globally zero. Every surface has a real border. No UI library smooths this over. +4. **Env is validated at boot.** + `backend/src/config/env.js` fails fast when any required variable is missing. + Secrets never appear in code, in Git, or in logs. +5. **No silent role checks.** + Authorization is expressed through named middlewares (`authenticate`, + `authorize("admin")`). Controllers do not inline role comparisons. +6. **Typed error taxonomy.** + `ApiError.badRequest | unauthorized | forbidden | notFound | conflict | internal` is + the only way to signal failure. Clients receive `{ error, code?, details? }` with a + meaningful HTTP status. +7. **Commits reflect intent, not sprawl.** + One logical change per commit. No mixing formatting with refactors with features. +8. **Lint and build are required.** + `npm run lint` and `npm run build` must both succeed locally before opening a PR. +9. **No generated or binary files in `src/`.** + `dist/`, `node_modules/`, and log files are always ignored. +10. **User data is treated as radioactive.** + Passwords are hashed with bcrypt. Auth tokens are httpOnly cookies with + `sameSite: strict` and `secure` in production. Error responses never leak stack + traces to clients in production. + +## Article III — Module Boundaries + +- **Backend module** = `config/`, `db/`, `middlewares/`, `utils/`, and one folder per + feature under `modules/`. A feature folder owns its `*.controller.js`, + `*.routes.js`, `*.service.js`, and `*.repository.js` — in that order of reading. +- **Frontend module** = `api/`, `components/ui/`, `components//`, + `components/layout/`, `context/`, `hooks/`, `pages/`, `routes/`, `lib/`, `styles/`. + Pages assemble components; components don't fetch their own data when a page can hoist + it. +- Cross-module imports flow **down only**: controller → service → repository. Services + never import controllers. Components never import each other's siblings through deep + paths. + +## Article IV — Design System + +- **Palette:** `neutral-950` ink, `neutral-50` paper, `yellow-400` primary accent, + `red-500` destructive, `emerald-500` positive, `sky-400` informational. +- **Typography:** Outfit (display), JetBrains Mono (metadata, labels, eyebrows). +- **Surfaces:** `border-2 border-black` + `shadow-brutal` (`6px 6px 0 0 #0a0a0a`). +- **Motion:** translate offsets on hover; never scale, never blur. +- **Primitives:** `Button`, `Input`, `Textarea`, `Panel`, `StatTile`, `StatusTag`, + `Spinner`, `EmptyState`. New UI should reuse these before inventing a variant. + +## Article V — Security & Privacy + +- Auth: JWT in an `httpOnly` cookie, rotated on login and cleared on logout. +- Passwords: `bcrypt`, cost factor 10 minimum. +- Input validation: performed in the service layer, not the controller. +- CORS / CSRF: cookies are `sameSite: strict`; the API is same-origin via Vite proxy in + dev and same-host in prod. +- Logging: requests are logged with method, path, status, and duration. **Never** log + passwords, tokens, or request bodies. + +## Article VI — Accessibility & UX + +- Every interactive element is reachable by keyboard and shows a visible focus ring. +- Labels use real `