Skip to content
Merged
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
146 changes: 107 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,97 @@
# 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

```
/
├── 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 subrouters
│ │ ├── 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
Expand All @@ -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 |

---

Expand Down
28 changes: 14 additions & 14 deletions src/routes/members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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",
Expand All @@ -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"}'
*/
Expand Down Expand Up @@ -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}'
*/
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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"}'
*/
Expand Down
Loading