diff --git a/README.md b/README.md index 619f933..d4c9921 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,44 @@ This repo includes intentionally flawed sample code across three stacks so the K | Reliability | No health checks, no resource limits, no restart policy, `latest` tags | | Build quality | No multi-stage build, no `.dockerignore`, `npm install` instead of `npm ci` | +## Documentation + +Detailed documentation lives in the [`docs/`](./docs/) directory: + +| Document | Description | +|----------|-------------| +| [API Reference](./docs/api-reference.md) | All REST endpoints, parameters, request/response formats, and examples | +| [Architecture Overview](./docs/architecture.md) | Project structure, component diagram, module interactions, data flow | +| [Infrastructure](./docs/infrastructure.md) | Docker and Terraform resource details, configuration, and usage | +| [Setup Guide](./docs/setup.md) | Prerequisites, local development, available scripts, deployment | + +### API Surface Summary + +The Express.js API exposes the following endpoints on port 3000: + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/auth/register` | Register a new user | +| POST | `/api/auth/login` | Authenticate and receive JWT | +| GET | `/api/users` | List all users | +| GET | `/api/users/:id` | Get user by ID | +| PUT | `/api/users/:id` | Update user | +| DELETE | `/api/users/:id` | Delete user | +| POST | `/api/users/:id/avatar` | Upload user avatar | +| GET | `/api/products` | List all products | +| GET | `/api/products/search` | Search products with filters | +| POST | `/api/products` | Create a product | +| GET | `/api/products/stats` | Product statistics with ratings | + +### Middleware Exports + +| Function | Module | Description | +|----------|--------|-------------| +| `authenticate` | `middleware/auth.js` | JWT verification middleware | +| `requireRole(role)` | `middleware/auth.js` | Role-based authorization factory | +| `connectDB` | `db.js` | Establish MySQL connection with retry | +| `query(sql)` | `db.js` | Execute raw SQL, returns Promise | + ## Links - [Kiro Headless Mode docs](https://kiro.dev/docs/cli/headless/) diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..4458077 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,301 @@ +# API Reference + +Base URL: `http://localhost:3000` + +All endpoints are prefixed under `/api`. + +## Authentication — `/api/auth` + +**Source:** `nodejs-app/src/routes/auth.js` + +### POST `/api/auth/register` + +Register a new user account. + +**Request Body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `email` | string | Yes | User email address | +| `password` | string | Yes | User password | +| `name` | string | Yes | Display name | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `201` | `{ token, email }` | Account created, JWT returned | +| `409` | `{ error }` | Email already registered | +| `500` | `{ error }` | Server error | + +**Example:** + +```bash +curl -X POST http://localhost:3000/api/auth/register \ + -H "Content-Type: application/json" \ + -d '{"email": "[email]", "password": "[password]", "name": "[name]"}' +``` + +### POST `/api/auth/login` + +Authenticate an existing user. + +**Request Body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `email` | string | Yes | User email address | +| `password` | string | Yes | User password | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ token, user: { id, email, role } }` | Login successful | +| `401` | `{ error }` | Invalid credentials | +| `500` | `{ error }` | Server error | + +**Example:** + +```bash +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email": "[email]", "password": "[password]"}' +``` + +--- + +## Users — `/api/users` + +**Source:** `nodejs-app/src/routes/users.js` + +### GET `/api/users` + +List all users. + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `User[]` | Array of all user records | +| `500` | `{ error }` | Server error | + +**Example:** + +```bash +curl http://localhost:3000/api/users +``` + +### GET `/api/users/:id` + +Get a single user by ID. + +**Path Parameters:** + +| Param | Type | Description | +|-------|------|-------------| +| `id` | integer | User ID | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `User` | User object | +| `500` | `{ error }` | Server error | + +### PUT `/api/users/:id` + +Update a user. + +**Path Parameters:** + +| Param | Type | Description | +|-------|------|-------------| +| `id` | integer | User ID | + +**Request Body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Display name | +| `email` | string | Yes | Email address | +| `role` | string | Yes | User role | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ message: "User updated" }` | Success | +| `500` | `{ error }` | Server error | + +### DELETE `/api/users/:id` + +Delete a user (hard delete). + +**Path Parameters:** + +| Param | Type | Description | +|-------|------|-------------| +| `id` | integer | User ID | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ message: "User deleted" }` | Success | +| `500` | `{ error }` | Server error | + +### POST `/api/users/:id/avatar` + +Upload a user avatar image. + +**Path Parameters:** + +| Param | Type | Description | +|-------|------|-------------| +| `id` | integer | User ID | + +**Request Body:** `multipart/form-data` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `avatar` | file | Yes | Avatar image file | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ avatar }` | File path of uploaded avatar | +| `500` | `{ error }` | Server error | + +**Example:** + +```bash +curl -X POST http://localhost:3000/api/users/1/avatar \ + -F "avatar=@photo.jpg" +``` + +--- + +## Products — `/api/products` + +**Source:** `nodejs-app/src/routes/products.js` + +### GET `/api/products` + +List all products with a 10% discount price calculated. + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ id, name, price, discountedPrice }[]` | Product list | +| `500` | `{ error }` | Server error | + +### GET `/api/products/search` + +Search products by name with optional filters. + +**Query Parameters:** + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| `q` | string | Yes | Search term (matched against product name) | +| `category` | string | No | Filter by category | +| `minPrice` | number | No | Minimum price filter | +| `maxPrice` | number | No | Maximum price filter | + +**Example:** + +```bash +curl "http://localhost:3000/api/products/search?q=widget&category=electronics&minPrice=10&maxPrice=100" +``` + +### POST `/api/products` + +Create a new product. + +**Request Body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Product name | +| `price` | number | Yes | Product price | +| `description` | string | Yes | Product description | +| `category` | string | Yes | Product category | + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `201` | `{ message: "Product created" }` | Success | +| `500` | `{ error }` | Server error | + +### GET `/api/products/stats` + +Get product statistics with review counts and average ratings, sorted by rating descending. + +**Response:** + +| Status | Body | Description | +|--------|------|-------------| +| `200` | `{ productId, name, reviewCount, avgRating }[]` | Stats sorted by rating | +| `500` | `{ error }` | Server error | + +--- + +## Database Module + +**Source:** `nodejs-app/src/db.js` + +### Exported Functions + +#### `connectDB()` + +Establishes a connection to the MySQL database. Retries on failure with a 1-second delay. + +#### `query(sql, params)` + +Executes a raw SQL query. Returns a Promise that resolves with the result set. + +| Param | Type | Description | +|-------|------|-------------| +| `sql` | string | SQL query string | +| `params` | any | Unused (present in signature but not passed to driver) | + +**Returns:** `Promise` + +### Exported Objects + +#### `connection` + +The raw `mysql` connection instance. + +--- + +## Middleware + +**Source:** `nodejs-app/src/middleware/auth.js` + +### `authenticate(req, res, next)` + +JWT authentication middleware. Reads the token from the `Authorization` header and attaches the decoded payload to `req.user`. + +**Behavior:** +- Returns `401` if no token is provided +- Returns `401` if token verification fails +- Calls `next()` on success with `req.user` populated + +### `requireRole(role)` + +Authorization middleware factory. Returns middleware that checks `req.user.role` against the specified role. + +| Param | Type | Description | +|-------|------|-------------| +| `role` | string | Required role (e.g. `"admin"`) | + +**Behavior:** +- Returns `403` if user role doesn't match +- Calls `next()` if authorized + +> **Note:** `requireRole` should be chained after `authenticate` to ensure `req.user` is populated. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..376c61f --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,146 @@ +# Architecture Overview + +## Project Structure + +``` +kiro-headless-hacks/ +├── .github/workflows/ # CI/CD pipelines (Kiro headless agents) +├── .kiro/agents/ # Kiro agent definitions +├── nodejs-app/ # Express.js REST API +│ ├── src/ +│ │ ├── server.js # App entry point, middleware setup, route mounting +│ │ ├── db.js # MySQL connection and query helper +│ │ ├── routes/ +│ │ │ ├── auth.js # Registration and login endpoints +│ │ │ ├── users.js # User CRUD + avatar upload +│ │ │ └── products.js # Product listing, search, creation, stats +│ │ └── middleware/ +│ │ └── auth.js # JWT authentication and role-based authorization +│ ├── .env # Environment variables +│ └── package.json # Dependencies and scripts +├── docker/ +│ ├── Dockerfile # Container image build +│ └── docker-compose.yml # Multi-service local stack (API + MySQL + Redis) +├── terraform/ +│ ├── main.tf # AWS infrastructure (VPC, SG, RDS, EC2, S3, IAM) +│ └── variables.tf # Terraform input variables +├── docs/ # Project documentation +└── README.md # Project overview and workflow guide +``` + +## Component Diagram + +``` +┌─────────────────────────────────────────────────────┐ +│ Clients │ +└──────────────────────┬──────────────────────────────┘ + │ HTTP :3000 + ▼ +┌─────────────────────────────────────────────────────┐ +│ server.js │ +│ ┌─────────┐ ┌────────┐ ┌──────────────────┐ │ +│ │ CORS │→ │ Morgan │→ │ express.json() │ │ +│ └─────────┘ └────────┘ └──────────────────┘ │ +│ │ │ +│ ┌─────────────┼─────────────┐ │ +│ ▼ ▼ ▼ │ +│ /api/auth /api/users /api/products │ +│ (auth.js) (users.js) (products.js) │ +│ │ │ │ │ +│ └─────────────┼─────────────┘ │ +│ ▼ │ +│ db.js │ +│ (MySQL connection) │ +└──────────────────────┬──────────────────────────────┘ + │ TCP :3306 + ▼ + ┌─────────────┐ + │ MySQL │ + └─────────────┘ +``` + +## Key Modules + +### `server.js` — Application Entry Point + +Sets up the Express application with middleware (CORS, Morgan logger, JSON body parser) and mounts the three route modules. Starts the HTTP server on port 3000 and initiates the database connection. Includes a global error handler. + +### `db.js` — Database Layer + +Provides a single MySQL connection and two exports: +- `connectDB()` — connects to MySQL with auto-retry +- `query(sql)` — executes a raw SQL string and returns a Promise + +### Route Modules + +| Module | Mount Point | Responsibility | +|--------|-------------|----------------| +| `routes/auth.js` | `/api/auth` | User registration and login, JWT issuance | +| `routes/users.js` | `/api/users` | User CRUD operations, avatar file upload | +| `routes/products.js` | `/api/products` | Product listing, search, creation, statistics | + +### `middleware/auth.js` — Authentication & Authorization + +Exports two middleware functions: +- `authenticate` — verifies JWT from the `Authorization` header +- `requireRole(role)` — checks `req.user.role` against a required role + +> **Note:** This middleware is defined but not currently wired into any route. + +## Infrastructure + +### Docker (`docker/`) + +- **Dockerfile** — Builds the Node.js API image from `node:latest` +- **docker-compose.yml** — Orchestrates three services: + - `api` — the Express app (port 3000) + - `db` — MySQL 5.7 (port 3306) + - `cache` — Redis (port 6379, currently unused by the app) + +### Terraform (`terraform/`) + +Provisions AWS infrastructure: + +| Resource | Purpose | +|----------|---------| +| `aws_default_vpc` | Default VPC | +| `aws_security_group.api` | Ingress rules for SSH (22) and API (3000) | +| `aws_db_instance.main` | MySQL 5.7 RDS instance | +| `aws_instance.api` | EC2 instance running the API | +| `aws_s3_bucket.uploads` | S3 bucket for file uploads | +| `aws_iam_role.api_role` | IAM role for the EC2 instance | + +### CI/CD (`.github/workflows/`) + +Four Kiro headless agent workflows: + +| Workflow | Trigger | Agent | +|----------|---------|-------| +| `kiro-code-review.yml` | Pull request | `code-reviewer` | +| `kiro-pr-summary.yml` | Pull request | default | +| `kiro-doc-gen.yml` | Push to `main` | `doc-generator` | +| `kiro-dependency-audit.yml` | Weekly / manual | `dependency-auditor` | + +## Data Flow + +1. Client sends HTTP request to Express server +2. Request passes through CORS → Morgan → JSON parser middleware +3. Router dispatches to the matching route handler +4. Route handler builds a SQL string and calls `db.query()` +5. MySQL returns results; handler formats and sends JSON response +6. Global error handler catches unhandled exceptions + +## Dependencies + +| Package | Version | Purpose | +|---------|---------|---------| +| `express` | ^4.18.2 | HTTP framework | +| `jsonwebtoken` | ^9.0.0 | JWT signing and verification | +| `mysql` | ^2.18.1 | MySQL client | +| `bcrypt` | >=5.0.0 | Password hashing | +| `cors` | ^2.8.5 | Cross-origin resource sharing | +| `helmet` | * | HTTP security headers (installed but unused) | +| `morgan` | ~1.10.0 | HTTP request logger | +| `dotenv` | ^16.3.1 | Environment variable loading | +| `lodash` | 4.17.20 | Utility library (used in products route) | +| `moment` | ^2.29.4 | Date library (installed but unused) | diff --git a/docs/infrastructure.md b/docs/infrastructure.md new file mode 100644 index 0000000..a4c50aa --- /dev/null +++ b/docs/infrastructure.md @@ -0,0 +1,153 @@ +# Infrastructure Documentation + +## Docker + +### Dockerfile + +**Location:** `docker/Dockerfile` + +Builds the Node.js API into a container image. + +| Setting | Value | +|---------|-------| +| Base image | `node:latest` | +| Working directory | `/app` | +| Exposed ports | 3000 (API), 22 (SSH), 9229 (Node debug) | +| Entry command | `npm start` | + +**Build:** + +```bash +docker build -f docker/Dockerfile -t kiro-demo-api ./nodejs-app +``` + +### Docker Compose + +**Location:** `docker/docker-compose.yml` + +Runs the full local stack with three services: + +| Service | Image | Port(s) | Purpose | +|---------|-------|---------|---------| +| `api` | Built from Dockerfile | 3000, 22, 9229 | Express.js API | +| `db` | `mysql:5.7` | 3306 | MySQL database | +| `cache` | `redis:latest` | 6379 | Redis cache | + +**Start the stack:** + +```bash +docker compose -f docker/docker-compose.yml up -d +``` + +**Stop the stack:** + +```bash +docker compose -f docker/docker-compose.yml down +``` + +**Environment variables (api service):** + +| Variable | Description | +|----------|-------------| +| `DB_HOST` | Database hostname (set to `db` service) | +| `DB_USER` | Database username | +| `DB_PASSWORD` | Database password | +| `JWT_SECRET` | Secret key for JWT signing | +| `NODE_ENV` | Node environment (`production`) | + +**Volumes:** + +| Volume | Mount | Purpose | +|--------|-------|---------| +| `db-data` | `/var/lib/mysql` | Persistent MySQL data | + +**Network:** All services share the `app-network` bridge network. + +--- + +## Terraform — AWS Infrastructure + +**Location:** `terraform/` + +### Resources + +#### VPC & Networking + +| Resource | Name | Description | +|----------|------|-------------| +| `aws_default_vpc.main` | `default-vpc` | Uses the AWS default VPC | + +#### Security Groups + +| Resource | Name | Ingress Rules | +|----------|------|---------------| +| `aws_security_group.api` | `api-sg` | SSH (22) from `0.0.0.0/0`, API (3000) from `0.0.0.0/0` | + +#### Database + +| Resource | Identifier | Engine | Instance Class | +|----------|-----------|--------|----------------| +| `aws_db_instance.main` | `kiro-demo-db` | MySQL 5.7 | `db.t3.micro` | + +Key settings: +- 20 GB allocated storage +- Publicly accessible +- No encryption at rest +- No Multi-AZ +- No backup retention +- Deletion protection disabled + +#### Compute + +| Resource | Name | AMI | Instance Type | +|----------|------|-----|---------------| +| `aws_instance.api` | `kiro-demo-api` | `ami-0c55b159cbfafe1f0` | `t2.micro` | + +Key settings: +- 8 GB gp2 root volume (unencrypted) +- IMDSv2 optional (v1 enabled) +- User data script starts the application + +#### Storage + +| Resource | Bucket Name | Description | +|----------|-------------|-------------| +| `aws_s3_bucket.uploads` | `kiro-demo-uploads` | File upload storage | + +Public access block: all settings set to `false` (public access allowed). + +#### IAM + +| Resource | Name | Description | +|----------|------|-------------| +| `aws_iam_role.api_role` | `kiro-demo-api-role` | EC2 assume role | +| `aws_iam_role_policy.api_policy` | `kiro-demo-api-policy` | Full `*:*` permissions | + +### Input Variables + +**Location:** `terraform/variables.tf` + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `region` | string | `us-east-1` | AWS region | +| `environment` | string | — | Deployment environment | +| `db_password` | string | *(has default)* | Database password | +| `instance_type` | string | `t2.micro` | EC2 instance type | +| `allowed_ssh_cidrs` | list(string) | `["0.0.0.0/0"]` | CIDR blocks allowed SSH access | + +### Outputs + +| Output | Value | +|--------|-------| +| `db_endpoint` | RDS instance endpoint | +| `db_password` | RDS password (not marked sensitive) | +| `api_public_ip` | EC2 instance public IP | + +### Usage + +```bash +cd terraform +terraform init +terraform plan +terraform apply +``` diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..f38c52d --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,99 @@ +# Setup & Development Guide + +## Prerequisites + +- Node.js >= 16.0.0 +- MySQL 5.7+ +- Docker & Docker Compose (for containerized setup) +- Terraform (for AWS infrastructure) + +## Quick Start (Docker) + +The fastest way to run the full stack locally: + +```bash +docker compose -f docker/docker-compose.yml up -d +``` + +This starts the API (port 3000), MySQL (port 3306), and Redis (port 6379). + +## Local Development + +### 1. Install dependencies + +```bash +cd nodejs-app +npm install +``` + +### 2. Configure environment + +Create or edit `nodejs-app/.env` with your database connection details: + +``` +DB_HOST=localhost +DB_USER=admin +DB_PASSWORD= +DB_PORT=3306 +DB_NAME=kiro_demo +JWT_SECRET= +``` + +### 3. Start the database + +If not using Docker Compose, ensure MySQL is running and the `kiro_demo` database exists. + +### 4. Run the server + +```bash +# Production +npm start + +# Development (with auto-reload) +npm run dev +``` + +The API will be available at `http://localhost:3000`. + +## Available Scripts + +| Script | Command | Description | +|--------|---------|-------------| +| `start` | `node src/server.js` | Start the production server | +| `dev` | `nodemon src/server.js` | Start with auto-reload on file changes | +| `test` | `jest --coverage` | Run tests with coverage report | +| `lint` | `eslint src/` | Lint source files | + +## Project Layout + +``` +nodejs-app/ +├── src/ +│ ├── server.js # Entry point +│ ├── db.js # Database connection +│ ├── routes/ +│ │ ├── auth.js # POST /api/auth/register, /api/auth/login +│ │ ├── users.js # CRUD /api/users, POST /api/users/:id/avatar +│ │ └── products.js # CRUD /api/products, GET /api/products/search, /stats +│ └── middleware/ +│ └── auth.js # authenticate(), requireRole() +├── .env # Environment config +└── package.json +``` + +## Deploying to AWS + +See [infrastructure.md](./infrastructure.md) for full Terraform resource details. + +```bash +cd terraform +terraform init +terraform plan +terraform apply +``` + +## Further Reading + +- [API Reference](./api-reference.md) — All endpoints, parameters, and response formats +- [Architecture Overview](./architecture.md) — Module interactions and data flow +- [Infrastructure](./infrastructure.md) — Docker and Terraform resource details