# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repository layout Monorepo with two independent npm packages plus a shared Docker dev stack: - `backend/` — Node.js + Express 5 (ESM) API, Prisma ORM over PostgreSQL. - `frontend/` — React 19 + Vite + TypeScript admin dashboard, styled with Tailwind 4 + shadcn/ui. - `docker/` — Dev Dockerfiles + `entrypoint.sh` (used by `docker-compose.dev.yml` at repo root). There is no root-level package script — run npm commands inside `backend/` or `frontend/`. ## Common commands ### Docker (full stack) ```bash docker compose -f docker-compose.dev.yml up --build # backend :5008, frontend :3008, postgres :5432 (internal) docker compose -f docker-compose.dev.yml down ``` The backend container's `entrypoint.sh` runs `prisma generate` then `prisma db push` (NOT `migrate deploy`) on every start — schema changes propagate without a migration step in the Docker dev flow. ### Backend (`cd backend`) ```bash npm run dev # nodemon src/app.js npm start # node src/app.js npm run migrate # npx prisma migrate dev (use this locally; Docker uses db push) npm run generate # npx prisma generate npx prisma studio # GUI for the DB npm run create-user [role] # role defaults to "admin" ``` In Docker, create an admin via: ```bash docker exec -it gg-backend-api-backend-1 node src/utils/createUser.js ``` ### Frontend (`cd frontend`) ```bash npm run dev # Vite dev server npm run build # tsc -b && vite build npm run lint # eslint . npm run preview ``` There is no test runner configured in either package. ## Architecture ### Backend Single Express app assembled in `backend/src/app.js`. Each domain follows the same triplet: - `src/routes/.routes.js` — declares the Express router, applies `jwtAuthMiddleware` per-route (mixed public/protected within the same router; see `blog.routes.js` for the pattern: list/detail public, admin endpoints + writes protected). - `src/controllers/.controller.js` — handler logic; uses Prisma directly. - Mounted at `/api/` in `app.js`. Domains: `departments`, `auth`, `blogs`, `upload`, `doctors`, `careers`, `candidates`, `appointments`, `inquiry`, `academics`, `email`, `newsMedia`, `import`. Static `uploads/` is served at `/uploads`. Cross-cutting pieces: - **Prisma client** — `src/prisma/client.js` exports a singleton. Always import from there rather than instantiating `new PrismaClient()` (the import controller currently instantiates its own — match the singleton pattern when adding new code). - **Auth** — `src/middleware/auth.js` expects `Authorization: Bearer `, attaches the decoded payload to `req.user`. Tokens are issued by `src/utils/jwt.js` using `JWT_SECRET`. Passwords hashed via `src/utils/password.js` (bcrypt). - **Email** — `src/utils/sendEmail.js` wraps Postmark with `EMAIL_FROM` as sender. Recipient lists are looked up by category via `getEmailsByType(type)` against the `EmailConfig` table — controllers call this to fan out notifications (e.g. on new appointments/inquiries). - **CORS** — `CORS_ALLOWED_ORIGINS` is a **space-separated** list (not comma-separated). Requests with no `Origin` header are allowed. - **Body limits** — JSON/urlencoded bodies are capped at 50mb to support image-laden Editor.js payloads and bulk Excel imports. ### Data model (Prisma — `backend/prisma/schema.prisma`) Note the natural-key relations: `Appointment.doctorId` references `Doctor.doctorId` (the string business ID), not `Doctor.id`. Same for `Department`. The `SL_NO` / `GG_ID` columns from imports become these business IDs. `Doctor` ↔ `Department` is many-to-many through `DoctorDepartment`, which has a 1:1 `DoctorTiming` for weekday schedules. `NewsMedia` has cascading `NewsImage` children. ### Frontend - Routing in `src/App.tsx`: `PublicRoute` wraps `/` (Login); everything else is wrapped by `ProtectedRoute` → `DashboardLayout`. Unknown paths redirect to `/department`. - `src/context/AuthContext.tsx` is the auth source of truth; `src/services/api.ts` is the shared Axios instance that auto-injects `Authorization: Bearer ` from `localStorage.getItem("token")`. - `src/api/.ts` files are thin wrappers around that axios client, one per backend domain — keep this pattern when adding endpoints. - Path alias `@/*` → `src/*` (configured in both `vite.config.ts` and `tsconfig`). - File uploads go through Bytescale (`src/components/BytescaleUploader`) — server only stores the resulting URL. - The `/import` page parses Excel via `xlsx` and POSTs structured payloads to `/api/import`, where `importController.js` upserts across multiple tables. When changing import behavior, the frontend's column names (`SL_NO`, `GG_ID`, `Department`, `Working Status`, etc.) and the controller's destructured keys (`departments`, `doctors`, `timings`, …) must stay in sync. ## Environment variables `backend/.env`: ``` DATABASE_URL=postgresql://user:password@db:5432/mydb PORT=5008 JWT_SECRET=... CORS_ALLOWED_ORIGINS=http://localhost:5173 # space-separated for multiple BYTESCALE_SECRET_API_KEY=... POSTMARK_API_KEY=... EMAIL_FROM=admin@example.com ``` `frontend/.env`: ``` VITE_API_URL=http://localhost:5008/api ```