Files
2026-05-26 15:48:01 +05:30

5.3 KiB

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)

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)

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 <username> <password> [role]   # role defaults to "admin"

In Docker, create an admin via:

docker exec -it gg-backend-api-backend-1 node src/utils/createUser.js <name> <password> <role>

Frontend (cd frontend)

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/<domain>.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/<domain>.controller.js — handler logic; uses Prisma directly.
  • Mounted at /api/<domain> 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 clientsrc/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).
  • Authsrc/middleware/auth.js expects Authorization: Bearer <jwt>, 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).
  • Emailsrc/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).
  • CORSCORS_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. DoctorDepartment 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 ProtectedRouteDashboardLayout. 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 <token> from localStorage.getItem("token").
  • src/api/<domain>.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