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
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
node_modules/
.next/
out/
dist/
build/

# Build artifacts
tsconfig.tsbuildinfo
*.log
next-*.log
verify-*.log
*.err.log
*.out.log

# Temporary files
migrate-*.mjs
apply-migration.mjs
run-migration.mjs
migration-*.sql
migration-*.txt
Comment on lines +19 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't ignore versioned SQL migrations by filename pattern.

migration-*.sql and migration-*.txt are broad enough to hide real schema migrations, which makes database changes easy to ship without source control. Scope these ignores to a temp directory or to exact generated filenames instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 19 - 24, The .gitignore currently uses broad
patterns (migration-*.sql and migration-*.txt) that will hide versioned
migrations; update the ignore entries so they only match temporary/generated
migration files (for example by moving them under a tmp/ or dist/ directory or
by using exact generated filenames) instead of the general patterns; keep any
necessary script ignores like migrate-*.mjs, apply-migration.mjs,
run-migration.mjs but replace migration-*.sql and migration-*.txt with scoped
patterns that target only transient artifacts (e.g., tmp/migration-*.sql or
generated/migration-12345.txt) or remove them entirely so real schema migration
files remain tracked.


.DS_Store
*.pem
Expand Down
358 changes: 332 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,344 @@
# Appointment Booking
# Appointment Booking System

## Setup
A full-stack appointment booking platform built with Next.js, TypeScript, and Supabase with comprehensive testing and role-based access control.

1. Go to [supabase.com](https://supabase.com), sign in, and create a new project
---

2. Once the project is ready, go to **SQL Editor** and run the contents of `schema.sql`
## Table of Contents

3. Place the `.env` file you received into the root of this project
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [API Endpoints](#api-endpoints)
- [Workflows](#workflows)
- [Database Schema](#database-schema)
- [Testing](#testing)
Comment on lines +13 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the broken table-of-contents links.

#database-schema and #testing do not have matching headings in this document, so those entries currently jump nowhere.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 13-13: Link fragments should be valid

(MD051, link-fragments)


[warning] 14-14: Link fragments should be valid

(MD051, link-fragments)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 13 - 14, The table-of-contents entries use anchors
that don't match actual headings; update either the TOC links or the headings so
they match exactly (e.g., change the TOC entries "- [Database
Schema](`#database-schema`)" and "- [Testing](`#testing`)" to the correct anchors
that correspond to the actual headings in the README, or rename the headings to
"Database Schema" and "Testing" so the anchors resolve correctly), ensuring the
link text and heading text are identical so the anchors jump to the proper
sections.

- [Credentials](#test-credentials)

4. Install dependencies:
```bash
npm install
```
---

5. Seed the database (creates all users, slots, and a sample appointment):
```bash
npm run seed
```
## Quick Start

6. Start the dev server:
```bash
npm run dev
```
### 1. Setup Supabase

Go to [supabase.com](https://supabase.com), sign in, and create a new project. Once ready:

- Go to **SQL Editor**
- Click **"+ New Query"**
- Paste contents of `schema.sql`
- Click **Run**

### 2. Configure Environment

Place the `.env` file in the root directory:

```bash
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
```

### 3. Install & Seed

```bash
npm install
npm run seed # Creates users, admin, slots, and sample appointment
npm run dev # Start dev server
```

Open [http://localhost:3000](http://localhost:3000)

### 4. Run Tests

```bash
npm test # Run all tests
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
```

---

## Architecture

### System Components

```
┌─────────────────────────────────────────────────────────────┐
│ Next.js Frontend │
├──────────────────┬──────────────────┬──────────────────────┤
│ Patient Login │ Doctor Login │ Admin Login │
│ Book/Cancel │ Mark Done │ View All Appts │
└──────────────────┴──────────────────┴──────────────────────┘
│ │ │
├────────────────┼────────────────────┤
▼ ▼ ▼
┌────────────────────────────────────────────────────┐
│ API Routes (Next.js) │
├────────────────────────────────────────────────────┤
│ POST /api/appointments/book │
│ POST /api/appointments/cancel │
│ POST /api/admin/login │
└────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ Supabase (PostgreSQL + Auth) │
├────────────────────────────────────────────────────┤
│ • Row-Level Security (RLS) Policies │
│ • RPC Functions with SECURITY DEFINER │
│ • Database Constraints & Indexes │
└────────────────────────────────────────────────────┘
```
Comment on lines +64 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add fence languages to the ASCII blocks.

markdownlint is already flagging these unlabeled code fences. Using text here will keep the current rendering and clear the lint errors.

Also applies to: 307-333

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 64-64: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 64 - 90, The unlabeled triple-backtick code fences in
README.md (e.g., the ASCII diagram block shown and the similar block around
lines 307-333) are causing markdownlint warnings; update each fence to include a
language tag such as text (replace ``` with ```text) so the fences are labeled
and rendering is unchanged, ensuring every occurrence of ``` in those diagram
sections is updated to ```text.


### Security Layers

1. **Authentication**: Supabase Auth (JWT tokens)
2. **Authorization**: Bearer tokens + role-based checks
3. **Database**: RLS policies + RPC functions with SECURITY DEFINER
4. **Sessions**: httpOnly cookies (24-hour expiry, sameSite="lax")

---

## API Endpoints

| Method | Endpoint | Authentication | Description |
| :--- | :--- | :--- | :--- |
| `POST` | `/api/admin/login` | None | Authenticates system admin and sets session cookie. |
| `POST` | `/api/appointments/book` | Bearer Token | Books an appointment for a patient. |
| `POST` | `/api/appointments/cancel` | Bearer Token | Cancels or completes an appointment. |


### 1. Book Appointment

**POST** `/api/appointments/book`

**Authentication**: Bearer Token (Required)

**Request Body**:

```json
{
"slotId": "uuid",
"doctorId": "uuid"
}
```

**Response** (201 Created):

```json
{
"id": "uuid",
"patient_id": "uuid",
"doctor_id": "uuid",
"slot_id": "uuid",
"status": "active",
"created_at": "2026-04-29T10:00:00Z"
}
```

**Error Responses**:

- `401 Unauthorized` - Missing or invalid token
- `400 Bad Request` - Missing slotId or doctorId
- `404 Not Found` - Slot not found
- `409 Conflict` - Slot already booked, invalid doctor, slot in past, or duplicate active appointment

**Business Rules**:

- Slot must be available (is_booked = false)
- Slot must be in the future
- Slot must belong to selected doctor
- Patient cannot have another active appointment with same doctor
- All validated before database operation

---

### 2. Cancel/Done Appointment

**POST** `/api/appointments/cancel`

**Authentication**: Bearer Token (Required)

**Request Body**:

```json
{
"appointmentId": "uuid",
"action": "cancel" | "done"
}
```

**Response** (200 OK):

```json
{
"id": "uuid",
"patient_id": "uuid",
"doctor_id": "uuid",
"slot_id": "uuid",
"status": "cancelled" | "done",
"created_at": "2026-04-29T10:00:00Z"
}
```



**Business Rules**:

- Patient can cancel anytime (except within 1 hour of start)
- Doctor can cancel/mark done anytime
- Cannot cancel already done/cancelled appointments
- Slot marked as available (is_booked = false) on cancellation
- Only doctors can mark appointments as "done"

**Role-Based Access**:
| Role | Can Cancel | Can Mark Done | 1-Hour Rule |
|------|-----------|---------------|------------|
| Patient | Yes (>1hr) | No | Yes |
| Doctor | Yes | Yes | No |

---

### 3. Admin Login

**POST** `/api/admin/login`

**Authentication**: None (Public)

**Request Body**:

```json
{
"email": "admin@test.com",
"password": "admin123"
}
```

**Response** (200 OK):

```json
{
"success": true
}
```

Sets `admin_session` cookie: `admin_<adminId>` (httpOnly, 24-hour expiry)

**Error Responses**:

- `400 Bad Request` - Missing email or password
- `401 Unauthorized` - Invalid credentials

---

## Workflows

### Booking Flow

```mermaid
sequenceDiagram
Patient->>UI: Enter slot & doctor
UI->>API: POST /api/appointments/book<br/>Authorization: Bearer token
API->>API: Validate token & user
API->>DB: Fetch slot details
API->>API: Validate slot available
API->>API: Validate slot in future
API->>API: Validate no duplicate booking
API->>DB: RPC book_appointment()
DB->>DB: Lock slot FOR UPDATE
DB->>DB: Verify slot available
DB->>DB: Update slot is_booked=true
DB->>DB: Insert appointment
DB-->>API: Return appointment
API-->>UI: 201 Created + appointment
UI->>UI: Show success & refresh
```

### Cancellation Flow

```mermaid
sequenceDiagram
User->>UI: Click cancel appointment
UI->>API: POST /api/appointments/cancel<br/>Authorization: Bearer token
API->>API: Validate token & user
API->>DB: Fetch appointment
API->>API: Check authorization (owner)
API->>DB: Fetch slot details
API->>API: Validate appointment cancellable
API->>API: Check cancellation window (if patient)
DB->>DB: Update appointment status='cancelled'
DB->>DB: Update slot is_booked=false
DB-->>API: Return updated appointment
API-->>UI: 200 OK + updated status
UI->>UI: Show success & refresh
```

### Admin Dashboard Flow

```mermaid
sequenceDiagram
Admin->>UI: Visit /admin/dashboard
UI->>UI: Check admin_session cookie
UI->>DB: Fetch all appointments (server-side)
DB->>DB: Apply RLS policies
DB-->>UI: Return appointments
UI->>UI: Render table
```

---

---

## Test Credentials

| Role | Email | Password |
|----------|---------------------|------------|
| Doctor 1 | doctor1@test.com | doctor123 |
| Doctor 2 | doctor2@test.com | doctor123 |
| Doctor 3 | doctor3@test.com | doctor123 |
| Patient 1| patient1@test.com | patient123 |
| Patient 2| patient2@test.com | patient123 |
| Patient 3| patient3@test.com | patient123 |
| Admin | admin@test.com | admin123 |
| Role | Email | Password |
| --------- | ----------------- | ---------- |
| Doctor 1 | doctor1@test.com | doctor123 |
| Doctor 2 | doctor2@test.com | doctor123 |
| Doctor 3 | doctor3@test.com | doctor123 |
| Patient 1 | patient1@test.com | patient123 |
| Patient 2 | patient2@test.com | patient123 |
| Patient 3 | patient3@test.com | patient123 |
| Admin | admin@test.com | admin123 |

---

## Project Structure

```
├── app/
│ ├── api/
│ │ ├── admin/login/ # Admin authentication
│ │ └── appointments/
│ │ ├── book/ # Booking endpoint
│ │ └── cancel/ # Cancellation endpoint
│ ├── admin/
│ │ ├── dashboard/ # Admin dashboard (SSR)
│ │ └── login/ # Admin login page
│ ├── doctor/dashboard/ # Doctor portal
│ ├── patient/dashboard/ # Patient portal
│ └── layout.tsx
├── lib/
│ ├── supabase.ts # Client Supabase
│ ├── supabaseAdmin.ts # Admin Supabase
│ └── validators.ts # Business logic
├── tests/
│ ├── unit/validators.test.ts
│ ├── integration/
│ │ ├── booking.test.ts
│ │ ├── cancellation.test.ts
│ │ └── guards.test.ts
├── schema.sql # Database schema
└── seed.mjs # Database seeding

```

---

## Security Considerations

### Implemented

- **Token Validation** - All API routes verify JWT tokens
- **Role-Based Access** - Different rules for patient/doctor/admin
- **SQL Injection Prevention** - Using RPC functions & parameterized queries
- **Password Security** - bcrypt hashing (admin passwords)
Loading