From 7b3898fd7bb8a3ea0b76e79df334d16b48e164b5 Mon Sep 17 00:00:00 2001 From: Arthur Date: Sun, 18 Jan 2026 22:16:37 +0100 Subject: [PATCH 1/3] chore: implement sum-to-n functions using iterative, formula, and recursive approaches --- .gitignore | 37 ++++++++++++++++++++++++++++++ src/problem4/sum_n.ts | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .gitignore create mode 100644 src/problem4/sum_n.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..bcea1ba8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock + +# Environment variables +.env +.env.local +.env.*.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Build outputs +dist/ +build/ +*.log + +# Database +*.sqlite +*.sqlite3 +*.db + +# Temporary files +*.tmp +.cache/ diff --git a/src/problem4/sum_n.ts b/src/problem4/sum_n.ts new file mode 100644 index 000000000..2369d8693 --- /dev/null +++ b/src/problem4/sum_n.ts @@ -0,0 +1,53 @@ +// =============================== +// Implementation A — Iterative +// =============================== + +// Complexity +// Time: O(n) — iterates from 1 to n +// Space: O(1) — constant extra memory +// +// Notes: +// - Very readable and safe +// - Slightly slower for large n due to iteration +// - Best when clarity is preferred over performance +export const sum_to_n_a = (n: number): number => { + let sum = 0; + for (let i = 1; i <= n; i++) { + sum += i; + } + return sum; +}; + +// =============================== +// Implementation B — Mathematical Formula +// =============================== + +// Complexity +// Time: O(1) — constant time calculation +// Space: O(1) — constant extra memory +// +// Notes: +// - Most efficient implementation +// - No loops or recursion +// - Safe under the assumption that the result +// is less than Number.MAX_SAFE_INTEGER +export const sum_to_n_b = (n: number): number => { + return (n * (n + 1)) / 2; +}; + +// =============================== +// Implementation C — Recursive +// =============================== + +// Complexity +// Time: O(n) — one recursive call per decrement +// Space: O(n) — call stack grows linearly +// +// Notes: +// - Elegant and expressive +// - Inefficient for large n due to recursion overhead +// - Risk of stack overflow; not recommended for production +export const sum_to_n_c = (n: number): number => { + if (n <= 1) return n; + return n + sum_to_n_c(n - 1); +}; From 2b288508b8445bcb94d8c6c347777db3a464ae0a Mon Sep 17 00:00:00 2001 From: Arthur Date: Sun, 18 Jan 2026 22:17:04 +0100 Subject: [PATCH 2/3] feat: implement resource CRUD service with validation, pagination, and filters --- src/problem1/.keep | 0 src/problem2/index.html | 27 ---- src/problem2/script.js | 0 src/problem2/style.css | 8 - src/problem3/.keep | 0 src/problem5/README.md | 147 ++++++++++++++++++ src/problem5/package.json | 29 ++++ src/problem5/src/api-test.http | 26 ++++ src/problem5/src/app.ts | 11 ++ src/problem5/src/database.ts | 10 ++ .../src/resource/resource.controller.ts | 58 +++++++ .../src/resource/resource.orm-entity.ts | 19 +++ .../src/resource/resource.repository.ts | 71 +++++++++ src/problem5/src/resource/resource.routes.ts | 23 +++ .../src/resource/resource.usecases.ts | 38 +++++ src/problem5/src/server.ts | 10 ++ src/problem5/tsconfig.json | 31 ++++ 17 files changed, 473 insertions(+), 35 deletions(-) delete mode 100644 src/problem1/.keep delete mode 100644 src/problem2/index.html delete mode 100644 src/problem2/script.js delete mode 100644 src/problem2/style.css delete mode 100644 src/problem3/.keep create mode 100644 src/problem5/README.md create mode 100644 src/problem5/package.json create mode 100644 src/problem5/src/api-test.http create mode 100644 src/problem5/src/app.ts create mode 100644 src/problem5/src/database.ts create mode 100644 src/problem5/src/resource/resource.controller.ts create mode 100644 src/problem5/src/resource/resource.orm-entity.ts create mode 100644 src/problem5/src/resource/resource.repository.ts create mode 100644 src/problem5/src/resource/resource.routes.ts create mode 100644 src/problem5/src/resource/resource.usecases.ts create mode 100644 src/problem5/src/server.ts create mode 100644 src/problem5/tsconfig.json diff --git a/src/problem1/.keep b/src/problem1/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/problem2/index.html b/src/problem2/index.html deleted file mode 100644 index 4058a68bf..000000000 --- a/src/problem2/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Fancy Form - - - - - - - - -
-
Swap
- - - - - - - -
- - - - diff --git a/src/problem2/script.js b/src/problem2/script.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/problem2/style.css b/src/problem2/style.css deleted file mode 100644 index 915af91c7..000000000 --- a/src/problem2/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - min-width: 360px; - font-family: Arial, Helvetica, sans-serif; -} diff --git a/src/problem3/.keep b/src/problem3/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/problem5/README.md b/src/problem5/README.md new file mode 100644 index 000000000..30d84f7a7 --- /dev/null +++ b/src/problem5/README.md @@ -0,0 +1,147 @@ +# Resource Management API + +A RESTful backend server built with Express.js and TypeScript that provides CRUD operations for managing resources. The application uses TypeORM with SQLite for data persistence. + +## Features + +- ✅ Create a resource +- ✅ List resources with pagination and filtering +- ✅ Get details of a specific resource +- ✅ Update resource details +- ✅ Delete a resource + +## Tech Stack + +- **Runtime**: Node.js +- **Framework**: Express.js +- **Language**: TypeScript +- **ORM**: TypeORM +- **Database**: SQLite +- **Validation**: Zod + +## Prerequisites + +- Node.js (v14 or higher) +- npm or yarn + +## Installation + +1. Install dependencies: +```bash +npm install +``` + +## Configuration + +The application uses SQLite database which is automatically created as `database.sqlite` in the project root. No additional configuration is required for development. + +The server runs on port `3000` by default. You can change this by setting the `PORT` environment variable: + +```bash +export PORT=3000 # Linux/Mac +set PORT=3000 # Windows +``` + +## Running the Application + +### Development Mode + +Run the server in development mode with hot-reload: + +```bash +npm run dev +``` + +The server will start on `http://localhost:3000` (or the port specified in the `PORT` environment variable). + +### Production Mode + +1. Build the TypeScript code: +```bash +npm run build +``` + +2. Start the server: +```bash +npm start +``` + +## API Endpoints + +All endpoints are prefixed with `/resources`. + +### Create a Resource + +```http +POST /resources +Content-Type: application/json + +{ + "name": "My Resource", + "description": "Resource description (optional)" +} +``` + +**Response**: `201 Created` with the created resource + +### List Resources + +```http +GET /resources?page=1&limit=10&name=search +``` + +**Query Parameters**: +- `page` (optional): Page number (default: 1) +- `limit` (optional): Items per page (default: 10) +- `name` (optional): Filter by name + +**Response**: `200 OK` with paginated list of resources + +### Get Resource Details + +```http +GET /resources/:id +``` + +**Response**: `200 OK` with resource details or `404 Not Found` + +### Update a Resource + +```http +PUT /resources/:id +Content-Type: application/json + +{ + "name": "Updated Name", + "description": "Updated description" +} +``` + +**Response**: `200 OK` with updated resource or `404 Not Found` + +### Delete a Resource + +```http +DELETE /resources/:id +``` + +**Response**: `204 No Content` on success or `400 Bad Request` if ID is invalid + +## Project Structure + +``` +src/ +├── app.ts # Express app configuration +├── server.ts # Server entry point +├── database.ts # TypeORM data source configuration +└── resource/ + ├── resource.controller.ts # Request handlers + ├── resource.usecases.ts # Business logic + ├── resource.repository.ts # Data access layer + ├── resource.orm-entity.ts # TypeORM entity + └── resource.routes.ts # Route definitions +``` + +## License + +ISC diff --git a/src/problem5/package.json b/src/problem5/package.json new file mode 100644 index 000000000..122925057 --- /dev/null +++ b/src/problem5/package.json @@ -0,0 +1,29 @@ +{ + "name": "problem5", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "dev": "ts-node-dev --respawn --transpile-only src/server.ts", + "build": "tsc", + "start": "node dist/server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "dotenv": "^17.2.3", + "express": "^5.2.1", + "reflect-metadata": "^0.2.2", + "sqlite3": "^5.1.7", + "typeorm": "^0.3.28", + "zod": "^4.3.5" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^25.0.9", + "ts-node-dev": "^2.0.0", + "typescript": "^5.9.3" + } +} diff --git a/src/problem5/src/api-test.http b/src/problem5/src/api-test.http new file mode 100644 index 000000000..57a6ddd29 --- /dev/null +++ b/src/problem5/src/api-test.http @@ -0,0 +1,26 @@ +### Create a Resource +POST http://localhost:3000/resources +Content-Type: application/json + +{ + "name": "My Test Resource", + "description": "Hello REST Client" +} + +### List Resources +GET http://localhost:3000/resources?page=1&limit=5 + +### Get a single Resource +GET http://localhost:3000/resources/890ae7b9-9c96-406f-b64b-2b24efeeedeb + +### Update a Resource +PUT http://localhost:3000/resources/890ae7b9-9c96-406f-b64b-2b24efeeedeb +Content-Type: application/json + +{ + "name": "Updated Resource", + "description": "Updated description" +} + +### Delete a Resource +DELETE http://localhost:3000/resources/1 diff --git a/src/problem5/src/app.ts b/src/problem5/src/app.ts new file mode 100644 index 000000000..223827b04 --- /dev/null +++ b/src/problem5/src/app.ts @@ -0,0 +1,11 @@ +import express from "express"; +import resourceRoutes from "./resource/resource.routes"; +import { AppDataSource } from "./database"; + +export const createApp = async () => { + await AppDataSource.initialize(); + const app = express(); + app.use(express.json()); + app.use("/resources", resourceRoutes); + return app; +}; diff --git a/src/problem5/src/database.ts b/src/problem5/src/database.ts new file mode 100644 index 000000000..edbfb436e --- /dev/null +++ b/src/problem5/src/database.ts @@ -0,0 +1,10 @@ +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import { ResourceOrmEntity } from "./resource/resource.orm-entity"; + +export const AppDataSource = new DataSource({ + type: "sqlite", + database: "database.sqlite", + synchronize: true, + entities: [ResourceOrmEntity], +}); diff --git a/src/problem5/src/resource/resource.controller.ts b/src/problem5/src/resource/resource.controller.ts new file mode 100644 index 000000000..300ca7348 --- /dev/null +++ b/src/problem5/src/resource/resource.controller.ts @@ -0,0 +1,58 @@ +import { Request, Response } from "express"; +import { z } from "zod"; +import { ResourceUseCases } from "./resource.usecases"; + +const CreateDto = z.object({ + name: z.string().min(1), + description: z.string().optional(), +}); + +const UpdateDto = CreateDto.partial(); + +export class ResourceController { + constructor(private useCases: ResourceUseCases) {} + + create = async (req: Request, res: Response) => { + const data = CreateDto.parse(req.body); + res.status(201).json(await this.useCases.create(data)); + }; + + list = async (req: Request, res: Response) => { + const page = Number(req.query.page) || 1; + const limit = Number(req.query.limit) || 10; + const name = typeof req.query.name === "string" ? req.query.name : undefined; + + res.json( + await this.useCases.list({ + page, + limit, + ...(name !== undefined && { name }), + }) + ); + }; + + get = async (req: Request, res: Response) => { + const id = typeof req.params.id === "string" ? req.params.id : undefined; + if (!id) return res.sendStatus(400); + const resource = await this.useCases.get(id); + if (!resource) return res.sendStatus(404); + res.json(resource); + }; + + update = async (req: Request, res: Response) => { + const id = typeof req.params.id === "string" ? req.params.id : undefined; + if (!id) return res.sendStatus(400); + const data = UpdateDto.parse(req.body); + const resource = await this.useCases.update(id, data); + if (!resource) return res.sendStatus(404); + res.json(resource); + }; + + delete = async (req: Request, res: Response) => { + const id = typeof req.params.id === "string" ? req.params.id : undefined; + if (!id) return res.sendStatus(400); + await this.useCases.delete(id); + res.status(204).json({ message: "Resource deleted successfully" }); + }; + } + diff --git a/src/problem5/src/resource/resource.orm-entity.ts b/src/problem5/src/resource/resource.orm-entity.ts new file mode 100644 index 000000000..8bca21e6f --- /dev/null +++ b/src/problem5/src/resource/resource.orm-entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; +import { CreateDateColumn } from "typeorm"; + + + @Entity("resources") + export class ResourceOrmEntity { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column() + name!: string; + + @Column({ nullable: true }) + description?: string; + + @CreateDateColumn() + createdAt!: Date; + } + \ No newline at end of file diff --git a/src/problem5/src/resource/resource.repository.ts b/src/problem5/src/resource/resource.repository.ts new file mode 100644 index 000000000..cdf6da808 --- /dev/null +++ b/src/problem5/src/resource/resource.repository.ts @@ -0,0 +1,71 @@ +import { Repository } from "typeorm"; +import { ResourceOrmEntity } from "./resource.orm-entity"; + +export class ResourceRepository { + constructor(private repo: Repository) {} + + async create(data: { + name: string; + description?: string | undefined; + }) { + const entity = this.repo.create({ + name: data.name, + ...(data.description !== undefined && { + description: data.description, + }), + }); + + return this.repo.save(entity); + } + + async update( + id: string, + data: { + name?: string | undefined; + description?: string | undefined; + } + ) { + const entity = await this.repo.findOneBy({ id }); + if (!entity) return null; + + Object.assign(entity, { + ...(data.name !== undefined && { name: data.name }), + ...(data.description !== undefined && { + description: data.description, + }), + }); + + return this.repo.save(entity); + } + + async findAll({ + page, + limit, + name, + }: { + page: number; + limit: number; + name?: string; + }) { + const qb = this.repo.createQueryBuilder("r"); + + if (name) { + qb.where("r.name LIKE :name", { name: `%${name}%` }); + } + + const [data, total] = await qb + .skip((page - 1) * limit) + .take(limit) + .getManyAndCount(); + + return { data, total }; + } + + findById(id: string) { + return this.repo.findOneBy({ id }); + } + + delete(id: string) { + return this.repo.delete(id); + } +} diff --git a/src/problem5/src/resource/resource.routes.ts b/src/problem5/src/resource/resource.routes.ts new file mode 100644 index 000000000..41560b6dd --- /dev/null +++ b/src/problem5/src/resource/resource.routes.ts @@ -0,0 +1,23 @@ +import { Router } from "express"; +import { ResourceController } from "./resource.controller"; +import { ResourceUseCases } from "./resource.usecases"; +import { ResourceRepository } from "./resource.repository"; +import { AppDataSource } from "../database"; +import { ResourceOrmEntity } from "./resource.orm-entity"; + +const repo = new ResourceRepository( + AppDataSource.getRepository(ResourceOrmEntity) +); + +const useCases = new ResourceUseCases(repo); +const controller = new ResourceController(useCases); + +const router = Router(); + +router.post("/", controller.create); +router.get("/", controller.list); +router.get("/:id", controller.get); +router.put("/:id", controller.update); +router.delete("/:id", controller.delete); + +export default router; diff --git a/src/problem5/src/resource/resource.usecases.ts b/src/problem5/src/resource/resource.usecases.ts new file mode 100644 index 000000000..79c1fe775 --- /dev/null +++ b/src/problem5/src/resource/resource.usecases.ts @@ -0,0 +1,38 @@ +import { ResourceRepository } from "./resource.repository"; + +export class ResourceUseCases { + constructor(private repo: ResourceRepository) {} + + create(data: { + name: string; + description?: string | undefined; + }) { + return this.repo.create(data); + } + + list(params: { + page: number; + limit: number; + name?: string ; + }) { + return this.repo.findAll(params); + } + + get(id: string) { + return this.repo.findById(id); + } + + update( + id: string, + data: { + name?: string | undefined; + description?: string | undefined; + } + ) { + return this.repo.update(id, data); + } + + delete(id: string) { + return this.repo.delete(id); + } +} diff --git a/src/problem5/src/server.ts b/src/problem5/src/server.ts new file mode 100644 index 000000000..30550740b --- /dev/null +++ b/src/problem5/src/server.ts @@ -0,0 +1,10 @@ +import "reflect-metadata"; +import { createApp } from "./app"; + +const PORT = process.env.PORT || 3000; + +createApp().then((app) => { + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); +}); diff --git a/src/problem5/tsconfig.json b/src/problem5/tsconfig.json new file mode 100644 index 000000000..b6360cf12 --- /dev/null +++ b/src/problem5/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + /* Project Structure */ + "rootDir": "./src", + "outDir": "./dist", + + /* Language & Environment */ + "target": "ES2020", + "module": "CommonJS", + "lib": ["ES2020"], + "types": ["node"], + + /* Module Resolution */ + "esModuleInterop": true, + "resolveJsonModule": true, + + /* Type Safety */ + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + /* Decorators */ + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + + /* Build */ + "sourceMap": true, + "skipLibCheck": true + }, + "include": ["src"] +} From 8b1786edad960cae947a3ff31a968f1b0f3ea920 Mon Sep 17 00:00:00 2001 From: Arthur Date: Sun, 18 Jan 2026 22:18:02 +0100 Subject: [PATCH 3/3] docs: add API service specification and execution flow for live scoreboard --- src/problem6/README.md | 411 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 src/problem6/README.md diff --git a/src/problem6/README.md b/src/problem6/README.md new file mode 100644 index 000000000..12de784e3 --- /dev/null +++ b/src/problem6/README.md @@ -0,0 +1,411 @@ +# Scoreboard API Module Specification + +## Overview + +This module handles score updates and provides real-time scoreboard functionality for a gaming/application website. It ensures secure, authorized score updates and maintains a live-updating leaderboard showing the top 10 user scores. + +## Requirements Summary + +1. **Scoreboard Display**: Show top 10 user scores in real-time +2. **Live Updates**: Scoreboard updates automatically when scores change +3. **Score Updates**: API endpoint to increment user scores upon action completion +4. **Authorization**: Prevent unauthorized users from manipulating scores +5. **Security**: Protect against malicious score injection + +## Architecture Overview + +### Components + +1. **Score Update API**: REST endpoint to receive score increment requests +2. **Authentication/Authorization Service**: Validates user identity and permissions +3. **Score Storage**: Database/backend storage for user scores +4. **Real-time Notification System**: Pushes scoreboard updates to clients +5. **Rate Limiting**: Prevents abuse and excessive API calls +6. **Score Validation**: Ensures score updates are legitimate and within constraints + +## API Endpoints + +### 1. Update Score + +**Endpoint**: `POST /api/v1/scores/update` + +**Description**: Increments a user's score by a specified amount after validating authorization. + +**Request Headers**: +``` +Authorization: Bearer +Content-Type: application/json +``` + +**Request Body**: +```json +{ + "actionId": "string", // Unique identifier for the action completed + "scoreIncrement": number, // Amount to add to current score (typically 1) + "timestamp": number // Unix timestamp of action completion (for validation) +} +``` + +**Success Response** (200 OK): +```json +{ + "success": true, + "message": "Score updated successfully", + "data": { + "userId": "string", + "previousScore": number, + "newScore": number, + "rank": number // Current rank in leaderboard + } +} +``` + +**Error Responses**: + +- `401 Unauthorized`: Invalid or missing authentication token +- `403 Forbidden`: User not authorized to update score +- `400 Bad Request`: Invalid request payload or validation failure +- `429 Too Many Requests`: Rate limit exceeded +- `500 Internal Server Error`: Server error + +### 2. Get Scoreboard + +**Endpoint**: `GET /api/v1/scores/leaderboard?limit=10` + +**Description**: Retrieves the current top N users on the leaderboard. + +**Query Parameters**: +- `limit` (optional): Number of top users to return (default: 10, max: 50) + +**Success Response** (200 OK): +```json +{ + "success": true, + "data": { + "leaderboard": [ + { + "userId": "string", + "username": "string", + "score": number, + "rank": number, + "lastUpdated": "ISO8601_timestamp" + } + ], + "generatedAt": "ISO8601_timestamp" + } +} +``` + +### 3. WebSocket Connection (Real-time Updates) + +**Endpoint**: `WS /api/v1/scores/stream` + +**Description**: WebSocket connection for real-time scoreboard updates. + +**Authentication**: Token passed in connection query parameter or initial handshake message. + +**Messages**: + +**Client → Server**: +```json +{ + "type": "subscribe", + "token": "JWT_TOKEN" +} +``` + +**Server → Client** (scoreboard update): +```json +{ + "type": "leaderboard_update", + "data": { + "leaderboard": [...], + "updatedAt": "ISO8601_timestamp" + } +} +``` + +## Flow Diagram + +```mermaid +sequenceDiagram + participant User as User/Client + participant Frontend as Frontend App + participant API as Score API Service + participant Auth as Auth Service + participant DB as Database + participant WS as WebSocket Service + participant Clients as Connected Clients + + User->>Frontend: Completes Action + Frontend->>Frontend: Validate Action Completion + Frontend->>API: POST /scores/update
(JWT Token + Action Data) + + API->>Auth: Verify JWT Token + Auth-->>API: Token Valid/Invalid + + alt Token Invalid + API-->>Frontend: 401 Unauthorized + else Token Valid + API->>API: Rate Limit Check + API->>API: Validate Action Data + API->>DB: Get Current User Score + DB-->>API: Current Score + + API->>API: Validate Score Increment
(Check constraints) + + API->>DB: Update User Score + DB-->>API: Updated Score + Rank + + API->>DB: Get Top 10 Leaderboard + DB-->>API: Leaderboard Data + + API-->>Frontend: 200 OK (Score Updated) + API->>WS: Broadcast Leaderboard Update + WS->>Clients: Push Update to All Connected Clients + Clients->>Frontend: Update UI Automatically + end +``` + +## Implementation Details + +### 1. Authentication & Authorization + +- **JWT Token Validation**: All score update requests must include a valid JWT token in the Authorization header +- **User Identity Verification**: Extract user ID from validated JWT token (do not trust client-provided userId) +- **Action Verification**: Validate that the action being completed is legitimate and authorized for the user + +**Implementation Requirements**: +- Token expiration validation +- Token signature verification +- User session validation +- Role-based access control if needed + +### 2. Score Update Validation + +- **Action ID Validation**: Verify actionId exists and is valid +- **Score Increment Limits**: Enforce maximum increment per action (e.g., typically 1, but configurable) +- **Timestamp Validation**: Verify action timestamp is recent (within reasonable window to prevent replay attacks) +- **Duplicate Prevention**: Check for duplicate action completions using actionId + userId + +### 3. Rate Limiting + +Implement rate limiting to prevent abuse: +- **Per User**: Maximum X score updates per minute/hour +- **Per IP**: Additional IP-based rate limiting as secondary defense +- **Action-specific**: Different limits for different action types if applicable + +**Recommended Limits**: +- Per user: 60 updates/minute, 1000 updates/hour +- Per IP: 100 updates/minute +- Return `429 Too Many Requests` with `Retry-After` header when exceeded + +### 4. Real-time Updates + +**Option A: WebSocket (Recommended)** +- Maintain persistent WebSocket connections for clients +- Broadcast leaderboard updates when scores change +- Handle connection lifecycle (connect, disconnect, reconnection) + +**Option B: Server-Sent Events (SSE)** +- Simpler unidirectional approach +- Easier to implement with HTTP/2 +- Suitable if bidirectional communication isn't needed + +**Option C: Polling** +- Fallback option, less efficient +- Client polls leaderboard endpoint at intervals (e.g., every 2-5 seconds) +- Higher server load, not true real-time + +### 5. Database Schema + +**Users Table**: +```sql +CREATE TABLE users ( + user_id VARCHAR(255) PRIMARY KEY, + username VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +**Scores Table**: +```sql +CREATE TABLE scores ( + user_id VARCHAR(255) PRIMARY KEY, + score BIGINT NOT NULL DEFAULT 0, + last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id) +); + +-- Index for leaderboard queries +CREATE INDEX idx_score_desc ON scores(score DESC); +``` + +**Action Logs Table** (for audit trail): +```sql +CREATE TABLE action_logs ( + log_id BIGSERIAL PRIMARY KEY, + user_id VARCHAR(255) NOT NULL, + action_id VARCHAR(255) NOT NULL, + score_increment INT NOT NULL, + previous_score BIGINT, + new_score BIGINT, + ip_address VARCHAR(45), + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id), + UNIQUE(user_id, action_id) -- Prevent duplicate actions +); + +CREATE INDEX idx_user_action ON action_logs(user_id, action_id); +CREATE INDEX idx_timestamp ON action_logs(timestamp); +``` + +### 6. Security Considerations + +1. **Input Sanitization**: Validate and sanitize all input data +2. **SQL Injection Prevention**: Use parameterized queries/ORM +3. **XSS Prevention**: Sanitize data before sending to clients +4. **CSRF Protection**: Use CSRF tokens for state-changing operations +5. **Action Replay Prevention**: Use unique action IDs, timestamps, and nonces +6. **Audit Logging**: Log all score update attempts (successful and failed) +7. **Encryption**: Use HTTPS/TLS for all API communication +8. **Secrets Management**: Store JWT secrets and API keys securely (environment variables, secret managers) + +### 7. Error Handling + +- Return appropriate HTTP status codes +- Provide clear, actionable error messages (without exposing system details) +- Log errors server-side with sufficient context for debugging +- Implement retry logic for transient failures + +### 8. Performance Considerations + +- **Database Indexing**: Index scores table for efficient leaderboard queries +- **Caching**: Cache leaderboard in Redis/memory cache with TTL +- **Database Connection Pooling**: Use connection pools for database access +- **Async Processing**: Consider queue-based processing for high-traffic scenarios +- **Load Balancing**: Design for horizontal scalability + +## Testing Requirements + +### Unit Tests +- Score update logic +- Validation functions +- Rate limiting logic +- Authentication middleware + +### Integration Tests +- API endpoint integration +- Database operations +- WebSocket connection handling +- End-to-end score update flow + +### Security Tests +- Authentication bypass attempts +- Rate limit enforcement +- SQL injection attempts +- Authorization checks +- Duplicate action prevention + +### Performance Tests +- Leaderboard query performance under load +- Concurrent score updates +- WebSocket connection limits +- Database query optimization + +## Additional Improvement Comments + +### 1. Enhanced Security + +- **Two-Factor Verification**: For critical actions, require additional verification +- **Device Fingerprinting**: Track and validate devices to detect unusual patterns +- **Anomaly Detection**: Implement ML-based anomaly detection for suspicious score patterns +- **IP Geolocation**: Track and flag unusual geographic patterns +- **Behavioral Analysis**: Monitor user behavior patterns to detect bots or scripts + +### 2. Performance Optimizations + +- **Redis Leaderboard**: Use Redis Sorted Sets for O(log N) leaderboard operations +- **Write-Ahead Logging**: Batch score updates for high-traffic scenarios +- **Read Replicas**: Use database read replicas for leaderboard queries +- **CDN Integration**: Cache static leaderboard data at edge locations +- **Compression**: Compress WebSocket messages for bandwidth efficiency + +### 3. Feature Enhancements + +- **Historical Leaderboards**: Store snapshots for historical rankings +- **Seasonal Leaderboards**: Support time-bound competitions +- **Categories/Leagues**: Multiple leaderboards for different game modes +- **Score Decay**: Implement score decay over time to encourage active participation +- **Achievements**: Badge/achievement system tied to milestones + +### 4. Monitoring & Observability + +- **Metrics**: Track API response times, error rates, active connections +- **Alerting**: Set up alerts for unusual patterns (sudden score spikes, error rate increases) +- **Logging**: Structured logging with correlation IDs for request tracing +- **Health Checks**: Implement health check endpoints for load balancers +- **Distributed Tracing**: Use tracing tools (e.g., Jaeger, Zipkin) for microservices + +### 5. User Experience + +- **Pagination**: Support pagination for extended leaderboard views +- **User Search**: Allow users to search for their own ranking +- **Personal Stats**: Provide detailed statistics per user +- **Notification System**: Notify users when they enter/exit top 10 +- **Leaderboard History**: Show ranking changes over time + +### 6. Scalability Considerations + +- **Microservices Architecture**: Split into separate services (Auth, Scores, Real-time) +- **Message Queue**: Use message queue (RabbitMQ, Kafka) for decoupling score updates from notifications +- **Caching Strategy**: Multi-layer caching (application, database query cache, CDN) +- **Database Sharding**: Partition users across multiple database shards if needed +- **Event Sourcing**: Consider event sourcing for audit trail and replay capabilities + +### 7. Data Integrity + +- **Transaction Management**: Ensure atomic score updates +- **Idempotency**: Make score update operations idempotent (same action won't be processed twice) +- **Consistency Checks**: Periodic jobs to verify score integrity +- **Backup Strategy**: Regular backups with point-in-time recovery capability + +### 8. Documentation + +- **API Documentation**: Use OpenAPI/Swagger for interactive API docs +- **WebSocket Protocol Docs**: Document WebSocket message formats and connection lifecycle +- **Deployment Guide**: Step-by-step deployment instructions +- **Runbook**: Operational procedures for common issues +- **Architecture Decision Records (ADRs)**: Document key architectural decisions + +## Deployment Checklist + +- [ ] Environment variables configured (JWT secret, DB connection, etc.) +- [ ] Database migrations applied +- [ ] SSL/TLS certificates configured +- [ ] Rate limiting configured and tested +- [ ] Monitoring and alerting set up +- [ ] Log aggregation configured +- [ ] Backup strategy implemented +- [ ] Load testing completed +- [ ] Security audit performed +- [ ] API documentation published +- [ ] WebSocket load balancer configuration verified + +## Technology Stack Recommendations + +- **API Framework**: Express.js, Fastify, NestJS (Node.js) or FastAPI, Django (Python) +- **Database**: PostgreSQL (primary), Redis (caching/leaderboard) +- **Real-time**: Socket.io, ws (WebSocket), or Server-Sent Events +- **Authentication**: JWT with refresh tokens +- **Rate Limiting**: express-rate-limit, redis-rate-limit +- **Validation**: Joi, Zod, or class-validator +- **Monitoring**: Prometheus, Grafana, or DataDog +- **Logging**: Winston, Pino, or structured logging framework + +--- + +**Document Version**: 1.0 +**Last Updated**: [Date] +**Author**: Backend Architecture Team +**Review Status**: Ready for Implementation