From 3189dd9a4d74f9928475a48e483154d43663f756 Mon Sep 17 00:00:00 2001 From: Harish-Naruto Date: Thu, 18 Jun 2026 22:09:01 +0530 Subject: [PATCH] docs: update README with project structure, setup instructions, and standardize API v1 route prefixing --- README.md | 146 +++++++++++++++++++++++++++++++----------- src/routes/members.ts | 28 ++++---- 2 files changed, 121 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 6232c9c..defde45 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # COC-API -This repository contains the common Express.js API for the backends of our Coding Club's websites , backed by PostgreSQL (via Prisma) and Supabase storage. We’re using **Bun** as our runtime. +This repository contains the shared Express.js API for the backends of our Coding Club's websites, backed by PostgreSQL (via Prisma) and Supabase storage. We use **Bun** as our runtime. ## πŸ“‚ Folder Structure @@ -8,70 +8,90 @@ This repository contains the common Express.js API for the backends of our Codin / β”œβ”€β”€ prisma/ # Prisma schema and migration files β”‚ β”œβ”€β”€ schema.prisma -β”‚ β”œβ”€β”€ .env # your DATABASE_URL, etc. -β”‚ └── migrations/ # auto‑generated by `bun prisma migrate` +β”‚ └── migrations/ # auto-generated by `bun run migrate` +β”‚ +β”œβ”€β”€ seed/ +β”‚ └── dump.sql # local seed file loaded by the setup script +β”‚ +β”œβ”€β”€ scripts/ +β”‚ └── setup-local.sh # automates local environment bring-up β”‚ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ config/ # environment/configuration loaders -β”‚ β”‚ └── index.ts # loads process.env and exports typed config β”‚ β”‚ β”‚ β”œβ”€β”€ db/ # database client initialization -β”‚ β”‚ └── client.ts # `export const prisma = new PrismaClient()` β”‚ β”‚ β”‚ β”œβ”€β”€ routes/ # Express route definitions -β”‚ β”‚ β”œβ”€β”€ index.ts # main router that mounts sub‑routers +β”‚ β”‚ β”œβ”€β”€ index.ts # main router β€” mounts all sub-routers under /api/v1 β”‚ β”‚ β”œβ”€β”€ members.ts β”‚ β”‚ β”œβ”€β”€ projects.ts β”‚ β”‚ β”œβ”€β”€ achievements.ts +β”‚ β”‚ β”œβ”€β”€ interviews.ts β”‚ β”‚ β”œβ”€β”€ topics.ts β”‚ β”‚ β”œβ”€β”€ questions.ts -β”‚ β”‚ β”œβ”€β”€ interviews.ts -β”‚ β”‚ └── progress.ts +β”‚ β”‚ β”œβ”€β”€ progress.ts +β”‚ β”‚ β”œβ”€β”€ site-content.ts +β”‚ β”‚ └── email.ts β”‚ β”‚ β”‚ β”œβ”€β”€ controllers/ # controllers: take req β†’ call services β†’ send res β”‚ β”‚ β”œβ”€β”€ member.controller.ts β”‚ β”‚ β”œβ”€β”€ project.controller.ts β”‚ β”‚ β”œβ”€β”€ achievement.controller.ts +β”‚ β”‚ β”œβ”€β”€ interview.controller.ts β”‚ β”‚ β”œβ”€β”€ topic.controller.ts β”‚ β”‚ β”œβ”€β”€ question.controller.ts -β”‚ β”‚ β”œβ”€β”€ interview.controller.ts -β”‚ β”‚ └── progress.controller.ts +β”‚ β”‚ β”œβ”€β”€ progress.controller.ts +β”‚ β”‚ β”œβ”€β”€ site-content.controller.ts +β”‚ β”‚ └── emailTemplate.controller.ts β”‚ β”‚ β”‚ β”œβ”€β”€ services/ # business logic / Prisma queries β”‚ β”‚ β”œβ”€β”€ member.service.ts β”‚ β”‚ β”œβ”€β”€ project.service.ts β”‚ β”‚ β”œβ”€β”€ achievement.service.ts +β”‚ β”‚ β”œβ”€β”€ interview.service.ts β”‚ β”‚ β”œβ”€β”€ topic.service.ts β”‚ β”‚ β”œβ”€β”€ question.service.ts -β”‚ β”‚ β”œβ”€β”€ interview.service.ts -β”‚ β”‚ └── progress.service.ts +β”‚ β”‚ β”œβ”€β”€ progress.service.ts +β”‚ β”‚ β”œβ”€β”€ site-content.service.ts +β”‚ β”‚ └── emailTemplate.service.ts β”‚ β”‚ -β”‚ β”œβ”€β”€ utils/ # shared helpers (e.g. error wrappers, validators) -β”‚ β”‚ └── apiError.ts +β”‚ β”œβ”€β”€ utils/ # shared helpers +β”‚ β”‚ β”œβ”€β”€ apiError.ts # custom error class and global error handler +β”‚ β”‚ β”œβ”€β”€ imageUtils.ts # image upload / Supabase storage helpers +β”‚ β”‚ β”œβ”€β”€ logger.ts # Winston logger instance +β”‚ β”‚ └── supabaseClient.ts β”‚ β”‚ β”‚ β”œβ”€β”€ app.ts # configure Express app, mount routes, error handler -β”‚ └── server.ts # start HTTP server (calls `app.listen`) +β”‚ └── server.ts # start HTTP server β”‚ -β”œβ”€β”€ tests/ # integration and unit tests (Jest or Mocha) -β”‚ β”œβ”€β”€ members.test.ts -β”‚ └── ... +β”œβ”€β”€ tests/ # unit tests (Jest + ts-jest) +β”‚ β”œβ”€β”€ Member.test.ts +β”‚ β”œβ”€β”€ Project.test.ts +β”‚ β”œβ”€β”€ Achievement.test.ts +β”‚ β”œβ”€β”€ Interview.test.ts +β”‚ β”œβ”€β”€ Topics.test.ts +β”‚ β”œβ”€β”€ Question.test.ts +β”‚ β”œβ”€β”€ Progress.test.ts +β”‚ β”œβ”€β”€ SiteContent.test.ts +β”‚ └── imageUtils.test.ts β”‚ β”œβ”€β”€ .env.example # template for environment variables β”œβ”€β”€ package.json -└── tsconfig.json # TypeScript configuration +└── tsconfig.json ``` ## πŸš€ Getting Started -### Prerequisite +### Prerequisites - Install [Bun](https://bun.sh/) on your machine. +- Install [Docker & Docker Compose](https://docs.docker.com/get-docker/) for the local database. ### 1. Clone the repo ```bash -git clone https://github.com/your-org/coding-club-api.git -cd coding-club-api +git clone https://github.com/call-0f-code/COC-API.git +cd COC-API ``` ### 2. Install dependencies @@ -82,39 +102,87 @@ bun install ### 3. Configure environment -- Copy `.env.example` to `.env` -- Update `.env` with your Supabase/PostgreSQL connection URL and any other variables: +Copy `.env.example` to `.env` and fill in the required values: + +```bash +cp .env.example .env +``` + +Key variables: + +| Variable | Description | +| ------------------------- | --------------------------------------------------------------------------- | +| `DATABASE_URL` | Supabase connection-pooling URL (used by Prisma at runtime) | +| `DIRECT_URL` | Direct DB connection URL (used by Prisma Migrate) | +| `SUPABASE_URL` | Your Supabase project URL | +| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service-role secret key | +| `SESSION_POOLER` | Session-mode pooler URL β€” used by `setup-local.sh` for `pg_dump` (IPv4) | +| `NODE_ENV` | `development` \| `production` | + +### 4. Local development (Docker) + +The setup script starts a local Postgres container and seeds it automatically: -### 4. Initialize Prisma & Database +```bash +bun run local +``` + +> See [LOCAL_DEVELOPMENT.md](LOCAL_DEVELOPMENT.md) for full details, flags, and troubleshooting. + +### 5. Initialize / run migrations + +For a brand-new database: ```bash -bun prisma migrate dev --name init -bun prisma generate +bun run migrate:first # runs: bunx prisma migrate dev --name init +bun run generate # runs: bunx prisma generate ``` -### 5. Run in development +For subsequent schema changes: + +```bash +bun run migrate # runs: bunx prisma migrate dev +``` + +### 6. Start the server + +```bash +bun run start +``` + +The server listens on `http://localhost:3000` by default. +All API routes are prefixed with `/api/v1`, e.g. `http://localhost:3000/api/v1/members`. + +### 7. API Documentation + +Generate and serve the API docs: ```bash -bun run dev +bun run apidoc # generates static docs into /doc ``` -- By default, the server listens on `http://localhost:3000` -- `app.ts` sets up your Express instance; `server.ts` starts the HTTP listener +Then visit `http://localhost:3000/docs` while the server is running. -### 6. Run tests +### 8. Run tests ```bash -bun test +bun run test # runs: jest ``` ## πŸ“¦ Scripts -| Command | Description | -| ------------------------------- | --------------------------------------- | -| `bun run dev` | Start the dev server with hot reloading | -| `bun prisma migrate dev --name` | Apply migrations in development | -| `bun prisma generate` | Generate Prisma client | -| `bun test` | Run tests (Jest or Mocha) | +| Command | Description | +| -------------------- | -------------------------------------------------- | +| `bun run start` | Start the server (`bun src/server.ts`) | +| `bun run local` | Spin up local Docker environment and seed the DB | +| `bun run migrate` | Run pending Prisma migrations in development | +| `bun run migrate:first` | Apply initial migration (`--name init`) | +| `bun run generate` | Regenerate Prisma client | +| `bun run test` | Run all tests with Jest | +| `bun run apidoc` | Generate API documentation into `/doc` | +| `bun run lint` | Lint `src/` with ESLint | +| `bun run lint:fix` | Lint and auto-fix `src/` | +| `bun run format` | Format `src/` with Prettier | --- diff --git a/src/routes/members.ts b/src/routes/members.ts index 3084d72..c0a97bb 100644 --- a/src/routes/members.ts +++ b/src/routes/members.ts @@ -17,7 +17,7 @@ export default function membersRouter( * @apiSuccess {Object[]} unapprovedMembers List of unapproved members. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/unapproved + * curl -X GET http://localhost:3000/api/v1/members/unapproved */ router.get("/unapproved", memberCtrl.getUnapprovedMembers); @@ -33,7 +33,7 @@ export default function membersRouter( * @apiSuccess {Object[]} members List of ghosted member objects (includes ghostedBy admin info). * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/dead-zone + * curl -X GET http://localhost:3000/api/v1/members/dead-zone */ router.get("/dead-zone", memberCtrl.getDeadZoneMembers); @@ -48,7 +48,7 @@ export default function membersRouter( * @apiError (Error 400) BadRequest No memberId provided. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123 + * curl -X GET http://localhost:3000/api/v1/members/123 */ router.get("/:memberId", memberCtrl.getUserDetails); @@ -70,10 +70,10 @@ export default function membersRouter( * @apiError (400) IncorrectEmail The provided email does not match any user. * * @apiExample {curl} Example usage (list all): - * curl -X GET http://localhost:3000/members + * curl -X GET http://localhost:3000/api/v1/members * * @apiExample {curl} Example usage (get by email): - * curl -X GET "http://localhost:3000/members?email=john@example.com" + * curl -X GET "http://localhost:3000/api/v1/members?email=john@example.com" */ router.get("/", memberCtrl.listAllApprovedMembers); @@ -102,7 +102,7 @@ export default function membersRouter( * -F "password=securePass123" \ * -F "passoutYear=2026" \ * -F "provider=credentials" \ - * http://localhost:3000/members + * http://localhost:3000/api/v1/members */ router.post("/", upload.single("file"), memberCtrl.createAMember(supabase)); @@ -140,7 +140,7 @@ export default function membersRouter( * @apiExample {curl} Example usage: * curl -X PATCH -F "file=@profile.jpg" \ * -F 'memberData={"name":"John Doe","email":"john@example.com"}' \ - * http://localhost:3000/members/123 + * http://localhost:3000/api/v1/members/123 */ router.patch( "/:memberId", @@ -162,7 +162,7 @@ export default function membersRouter( * @apiError (Error 400) BadRequest Missing required fields. * * @apiExample {curl} Example usage: - * curl -X PATCH http://localhost:3000/members/approve/123 \ + * curl -X PATCH http://localhost:3000/api/v1/members/approve/123 \ * -H "Content-Type: application/json" \ * -d '{"isApproved": true, "adminId": "admin123"}' */ @@ -190,12 +190,12 @@ export default function membersRouter( * @apiError (Error 403) Forbidden Only Admins and Super Admins can ghost members. * * @apiExample {curl} Ghost a member: - * curl -X PATCH http://localhost:3000/members/ghost/123 \ + * curl -X PATCH http://localhost:3000/api/v1/members/ghost/123 \ * -H "Content-Type: application/json" \ * -d '{"adminId": "admin-id", "ghost": true}' * * @apiExample {curl} Unghost a member: - * curl -X PATCH http://localhost:3000/members/ghost/123 \ + * curl -X PATCH http://localhost:3000/api/v1/members/ghost/123 \ * -H "Content-Type: application/json" \ * -d '{"adminId": "admin-id", "ghost": false}' */ @@ -212,7 +212,7 @@ export default function membersRouter( * @apiSuccess {Object[]} achievements List of achievements. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/achievements + * curl -X GET http://localhost:3000/api/v1/members/123/achievements */ router.get("/:memberId/achievements", memberCtrl.getUserAchievements); @@ -226,7 +226,7 @@ export default function membersRouter( * @apiSuccess {Object[]} projects List of projects. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/projects + * curl -X GET http://localhost:3000/api/v1/members/123/projects */ router.get("/:memberId/projects", memberCtrl.getUserProjects); @@ -240,7 +240,7 @@ export default function membersRouter( * @apiSuccess {Object[]} interviews List of interviews. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/interviews + * curl -X GET http://localhost:3000/api/v1/members/123/interviews */ router.get("/:memberId/interviews", memberCtrl.getUserInterviews); @@ -261,7 +261,7 @@ export default function membersRouter( * @apiError (Error 403) Forbidden Only Super Admins can assign roles. * * @apiExample {curl} Example usage: - * curl -X PATCH http://localhost:3000/members/123/role \ + * curl -X PATCH http://localhost:3000/api/v1/members/123/role \ * -H "Content-Type: application/json" \ * -d '{"adminId": "superadmin-id", "role": "ADMIN"}' */