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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
19 changes: 18 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
node_modules
.env
.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/
194 changes: 194 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -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 <App/> inside providers
├── App.jsx # Toaster + <AppRoutes/>
├── 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.
104 changes: 104 additions & 0 deletions CONSTITUTION.md
Original file line number Diff line number Diff line change
@@ -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/<feature>/`,
`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 `<label>` elements; icons have `aria-label` when they stand alone.
- Color is never the only channel for meaning — status is reinforced by text and
iconography.

## Article VII — Amendments

To change this constitution:

1. Open a PR titled `chore(constitution): <summary>`.
2. Explain the motivation in the PR body.
3. Add or update the corresponding section here **and** any affected docs
(`ARCHITECTURE.md`, `CONTRIBUTING.md`, `README.md`).
4. Obtain maintainer approval.

---

_Last ratified: v1.0_
Loading