diff --git a/.env.example b/.env.example index 23473d1..91b8d4e 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,8 @@ -MYSQL_ROOT_PASSWORD=root +JWT_SECRET=ADD-YOUR-SECRET + +# Database Configuration +MYSQL_USER=NEW-USER +MYSQL_PASSWORD=CHANGE-PASSWORD MYSQL_HOST=mysql MYSQL_PORT=3306 -MYSQL_DATABASE=taskdb -MYSQL_USER=appuser -MYSQL_PASSWORD=apppassword -JWT_SECRET=supersecretkey +MYSQL_DATABASE=CHANGE-DB-NAME diff --git a/.gitignore b/.gitignore index 94cb000..d941e23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env internal/db/.env tmp/ +.obsidian/ diff --git a/README.md b/README.md index 8009259..ea139cc 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,81 @@ -# TaskFlow - -A REST API for task management built with Go, following clean architecture principles. - -## What I've Built - -### ๐Ÿ“‹ Core Features -- **Task Management**: Create, read, update, delete tasks -- **Status Updates**: Change task status (pending โ†’ in-progress โ†’ completed) -- **Clean Architecture**: Separated layers (handler โ†’ service โ†’ repository โ†’ database) -- **JWT Authentication**: Complete JWT utility package with token creation/validation - -### ๐Ÿ—๏ธ Architecture Implementation -``` -internal/ -โ”œโ”€โ”€ domain/task/ # Task entity and repository interface -โ”œโ”€โ”€ dto/ # Request/response data structures -โ”œโ”€โ”€ handler/ # HTTP handlers with comprehensive tests -โ”œโ”€โ”€ service/ # Business logic layer -โ”œโ”€โ”€ repository/gorm/ # Database layer with GORM -โ””โ”€โ”€ common/ # Shared error responses - -pkg/ -โ””โ”€โ”€ jwt.go # JWT utilities (create, validate, extract username) -``` - -### ๐Ÿงช Testing Coverage -- **Handler Tests**: All HTTP endpoints with success/failure scenarios -- **Service Tests**: Business logic validation with mocks -- **Repository Tests**: Database operations with in-memory SQLite -- **JWT Tests**: Token creation, validation, and error handling -- **Race Detection**: `go test -race` ready - -### ๐Ÿณ Docker Setup -- **Development**: Hot reload with Air, volume mounting for live coding -- **Production**: Multi-stage Alpine build for optimized images -- **Database**: MySQL 8.0 with health checks and proper networking -- **Docker Compose**: Separate configs for dev/prod environments - -### ๐Ÿ“Š API Endpoints Built -| Method | Endpoint | Status | -|--------|----------|--------| -| `POST` | `/api/tasks` | โœ… Complete | -| `GET` | `/api/tasks` | โœ… Complete | -| `GET` | `/api/tasks/:id` | โœ… Complete | -| `PATCH` | `/api/tasks/:id/status` | โœ… Complete | -| `DELETE` | `/api/tasks/:id` | โœ… Complete | - -### ๐Ÿ” JWT Implementation -- Token creation with configurable expiration -- Token validation with proper error handling -- Username extraction from tokens -- Comprehensive test coverage -- Ready for authentication middleware integration +# TaskFlow - Learning Go, Docker & Clean Architecture -## Tech Stack -- **Go** with Gin framework -- **MySQL** with GORM ORM -- **Docker** & Docker Compose -- **Testify** for testing -- **Swagger** documentation (integrated) +> A production-grade REST API built to master Go, Docker, and modern backend development practices. + +[![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go)](https://go.dev/) +[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?style=flat&logo=docker)](https://www.docker.com/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Tests](https://img.shields.io/badge/Tests-Passing-success)](./test) + +TaskFlow is a robust task management API designed to demonstrate **Clean Architecture** principles in Go. It features secure authentication, containerized environments, and a comprehensive testing suite. + +**This is a learning project.** It documents my journey from "Hello World" to a production-ready backend structure. + +--- + +## Key Features + +* **Clean Architecture:** Strict separation of concerns (Handlers โ†’ Services โ†’ Repositories). +* **Secure Auth:** JWT implementation with Bcrypt password hashing. +* **Containerization:** Optimized Multi-stage Docker builds for Dev and Prod. +* **Quality Assurance:** Unit & Integration tests with high coverage + Race detection. +* **Developer Experience:** Hot-reloading (Air), Swagger docs + +--- + +## Documentation + +I have documented the technical details and my learning process in the `docs/` folder: + +* **[Architecture & Design](./docs/ARCHITECTURE.md)** - Breakdowns of the Clean Architecture layers and folder structure. +* **[API Reference](./docs/API.md)** - Endpoints, Request/Response examples, and Auth flow diagrams. +* **[Development Guide](./docs/DEVELOPMENT.md)** - Setup instructions, testing commands, and troubleshooting. +* **[What I Learned](./docs/LEARNING.md)** - A log of challenges faced and concepts mastered (Go routines, Interfaces, Docker networking). + +--- ## Quick Start -1. **Clone and setup**: -```bash -git clone -cd taskflow -``` +The easiest way to run the project is with Docker Compose. -2. **Create `.env` file**: -```env -MYSQL_ROOT_PASSWORD=root -MYSQL_DATABASE=taskdb -MYSQL_USER=appuser -MYSQL_PASSWORD=apppassword -``` +### Prerequisites +* Docker & Docker Compose + +### Run with One Command -3. **Run with Docker**: ```bash -# Development with hot reload -docker-compose -f docker-compose.yml -f docker-compose.override.yml up +# 1. Clone the repo +git clone https://github.com/joshua-sajeev/taskflow.git +cd taskflow -# Production -docker-compose up -``` +# 2. Setup Environment (Important!) +cp .env.example .env +# Open .env and set a secure JWT_SECRET -4. **Access**: - - API: http://localhost:8080/api - - Swagger: http://localhost:8080/swagger/ +# 3. Start the App +docker-compose up --build +```` -## Testing +The API will be available at `http://localhost:8080`. -```bash -# All tests -go test ./... +### Verify it works -# With race detection -go test -race ./... +Visit the Swagger UI to interact with the API: +**[http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html)** +## Tech Stack -# With coverage -go test -cover ./... -``` +| Category | Technology | Usage | +| :------------ | :-------------- | :------------------------- | +| **Language** | **Go (Golang)** | Core logic | +| **Framework** | **Gin** | HTTP Routing & Middleware | +| **Database** | **MySQL 8.0** | Persistent storage | +| **ORM** | **GORM** | Data access & Migrations | +| **DevOps** | **Docker** | Containerization & Compose | +| **Testing** | **Testify** | Assertions & Mocks | +| **Docs** | **Swagger** | API Documentation | -## What's Next -Planning to transform this into a **concurrent, high-performance system** with: -- Redis integration for caching/sessions -- Worker pools for parallel processing -- WebSocket real-time updates -- Event-driven architecture -- User authentication system +----- ---- +## Contributing & Feedback -*Built with Go clean architecture principles and comprehensive testing* +This project is open for code review\! If you see a non-idiomatic Go pattern or a security flaw, please open an issue. \ No newline at end of file diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..9cbcda7 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,649 @@ +# API Documentation + +Complete reference for TaskFlow API endpoints, request/response formats, and status codes. + +--- + +## Base URL + +``` +http://localhost:8080/api +``` + +--- + +## Authentication + +TaskFlow uses **JWT (JSON Web Tokens)** for authentication. + +### Getting a Token +1. [[API#Register User|Register]] or [[#Login User|Login]] to receive a token +2. Include token in all protected requests using: + ``` + Authorization: Bearer + ``` + +### Token Details + +- **Type**: JWT +- **Expiration**: 24 hours +- **Algorithm**: HS256 +- **Storage**: Client-side (in header) + +### Example Request with Token + +```bash +curl -X GET http://localhost:8080/api/tasks \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +--- + +## Response Format + +All responses follow a standard format: + +### Success Response +```json +{ + "id": 1, + "email": "user@example.com", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### Error Response +```json +{ + "error": "invalid credentials" +} +``` + +### HTTP Status Codes + +| Code | Meaning | Example | +|------|---------|---------| +| 200 | OK - Request succeeded | GET /tasks | +| 201 | Created - Resource created | POST /auth/register | +| 400 | Bad Request - Invalid input | Missing required field | +| 401 | Unauthorized - Invalid/missing token | Missing Authorization header | +| 404 | Not Found - Resource doesn't exist | GET /tasks/999 | +| 409 | Conflict - Duplicate resource | Email already exists | +| 500 | Server Error - Internal error | Database connection failed | + +--- + +## Authentication Endpoints + +### Register User + +Create a new user account. + +**Endpoint**: `POST /auth/register` + +**Authentication**: None required + +**Request Body**: +```json +{ + "email": "user@example.com", + "password": "securePassword123" +} +``` + +**Query Parameters**: None + +**Validation**: +- `email`: Required, valid email format +- `password`: Required, minimum 6 characters + +**Response** (201 Created): +```json +{ + "id": 1, + "email": "user@example.com" +} +``` + +**Error Examples**: +```json +// Invalid email format +{ + "error": "invalid email format" +} + +// Password too short +{ + "error": "password must be at least 6 characters" +} + +// Email already exists +{ + "error": "email already exists" +} +``` + +**Example Request**: +```bash +curl -X POST http://localhost:8080/api/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "email": "john@example.com", + "password": "myPassword123" + }' +``` + +--- + +### Login User + +Authenticate with email and password, receive JWT token. + +**Endpoint**: `POST /auth/login` + +**Authentication**: None required + +**Request Body**: +```json +{ + "email": "user@example.com", + "password": "securePassword123" +} +``` + +**Response** (200 OK): +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJpYXQiOjE2OTUzMTg1NjMsImV4cCI6MTY5NTQwNDk2M30.abc123...", + "id": 1, + "email": "user@example.com" +} +``` + +**Error Examples**: +```json +// Invalid credentials +{ + "error": "invalid credentials" +} + +// User not found +{ + "error": "invalid credentials" +} +``` + +**Example Request**: +```bash +curl -X POST http://localhost:8080/api/auth/login \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "email": "john@example.com", + "password": "myPassword123" + }' +``` + +**Usage**: Store the returned `token` and include in `Authorization: Bearer ` header for all protected endpoints. + +--- + +## Task Endpoints + +All task endpoints require authentication. + +### Create Task + +Create a new task for the authenticated user. + +**Endpoint**: `POST /tasks` + +**Authentication**: Required โœ“ + +**Request Body**: +```json +{ + "task": "Buy groceries" +} +``` + +**Validation**: +- `task`: Required, max 20 characters + +**Response** (201 Created): +```json +{ + "task": "Buy groceries" +} +``` + +**Error Examples**: +```json +// Missing task field +{ + "error": "Key: 'CreateTaskRequest.Task' Error:Field validation for 'Task' failed on the 'required' tag" +} + +// Task too long +{ + "error": "Key: 'CreateTaskRequest.Task' Error:Field validation for 'Task' failed on the 'max' tag" +} +``` + +**Example Request**: +```bash +curl -X POST http://localhost:8080/api/tasks \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "task": "Complete project" + }' +``` + +--- + +### Get Task + +Retrieve a single task by ID. + +**Endpoint**: `GET /tasks/{id}` + +**Authentication**: Required โœ“ + +**Path Parameters**: +- `id`: Task ID (integer, minimum 1) + +**Query Parameters**: None + +**Response** (200 OK): +```json +{ + "id": 1, + "task": "Buy groceries", + "status": "pending" +} +``` + +**Error Examples**: +```json +// Invalid ID format +{ + "error": "Invalid ID" +} + +// Task not found or belongs to different user +{ + "error": "Task not found" +} +``` + +**Example Request**: +```bash +curl -X GET http://localhost:8080/api/tasks/1 \ + -H "Authorization: Bearer " +``` + +--- + +### List Tasks + +Retrieve all tasks for the authenticated user. + +**Endpoint**: `GET /tasks` + +**Authentication**: Required โœ“ + +**Query Parameters**: None + +**Response** (200 OK): +```json +{ + "tasks": [ + { + "id": 1, + "task": "Buy groceries", + "status": "pending" + }, + { + "id": 2, + "task": "Write report", + "status": "completed" + } + ] +} +``` + +**Example Request**: +```bash +curl -X GET http://localhost:8080/api/tasks \ + -H "Authorization: Bearer " +``` + +**Example Response with No Tasks**: +```json +{ + "tasks": [] +} +``` + +--- + +### Update Task Status + +Update the status of a task. + +**Endpoint**: `PATCH /tasks/{id}/status` + +**Authentication**: Required โœ“ + +**Path Parameters**: +- `id`: Task ID (integer, minimum 1) + +**Request Body**: +```json +{ + "status": "completed" +} +``` + +**Valid Status Values**: +- `pending` - Task not started +- `completed` - Task finished + +**Response** (200 OK): +```json +{ + "message": "status updated" +} +``` + +**Error Examples**: +```json +// Invalid status value +{ + "error": "Key: 'UpdateStatusRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag" +} + +// Task not found +{ + "error": "Task not found" +} + +// Invalid ID format +{ + "error": "invalid task ID" +} +``` + +**Example Request**: +```bash +curl -X PATCH http://localhost:8080/api/tasks/1/status \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "status": "completed" + }' +``` + +--- + +### Delete Task + +Delete a task. + +**Endpoint**: `DELETE /tasks/{id}` + +**Authentication**: Required โœ“ + +**Path Parameters**: +- `id`: Task ID (integer, minimum 1) + +**Query Parameters**: None + +**Response** (200 OK): +```json +{ + "message": "Task deleted successfully" +} +``` + +**Error Examples**: +```json +// Task not found +{ + "error": "Task not found" +} + +// Invalid ID +{ + "error": "Invalid ID" +} +``` + +**Example Request**: +```bash +curl -X DELETE http://localhost:8080/api/tasks/1 \ + -H "Authorization: Bearer " +``` + +--- + +## User Endpoints + +### Update Password + +Change the password for the authenticated user. + +**Endpoint**: `PATCH /users/password` + +**Authentication**: Required โœ“ + +**Request Body**: +```json +{ + "old_password": "currentPassword123", + "new_password": "newPassword456" +} +``` + +**Validation**: +- `old_password`: Required +- `new_password`: Required, minimum 6 characters + +**Response** (200 OK): +```json +{ + "message": "Password updated successfully" +} +``` + +**Error Examples**: +```json +// Incorrect old password +{ + "error": "invalid old password" +} + +// New password too short +{ + "error": "new password must be at least 6 characters" +} + +// User not found +{ + "error": "user not found" +} +``` + +**Example Request**: +```bash +curl -X PATCH http://localhost:8080/api/users/password \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "old_password": "currentPassword123", + "new_password": "newPassword456" + }' +``` + +--- + +### Delete Account + +Permanently delete the authenticated user account. + +**Endpoint**: `DELETE /users/account` + +**Authentication**: Required โœ“ + +**Request Body**: None + +**Response** (200 OK): +```json +{ + "message": "User account deleted successfully" +} +``` + +**Note**: This performs a soft delete. The user can be recovered from backups but will not appear in normal queries. + +**Error Examples**: +```json +// User not found (already deleted) +{ + "error": "user not found" +} +``` + +**Example Request**: +```bash +curl -X DELETE http://localhost:8080/api/users/account \ + -H "Authorization: Bearer " +``` + +--- + +## Common Workflows + +### Complete Flow: Register, Create Task, Update Status + +```bash +# 1. Register +curl -X POST http://localhost:8080/api/auth/register \ + -H "Content-Type: application/json" \ + -d '{ + "email": "user@example.com", + "password": "password123" + }' +# Returns: { "id": 1, "email": "user@example.com" } + +# 2. Login +curl -X POST http://localhost:8080/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "email": "user@example.com", + "password": "password123" + }' +# Returns: { "token": "eyJ...", "id": 1, "email": "user@example.com" } + +# 3. Create task (use token from login) +TOKEN="eyJ..." +curl -X POST http://localhost:8080/api/tasks \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"task": "Buy milk"}' +# Returns: { "task": "Buy milk" } + +# 4. List tasks +curl -X GET http://localhost:8080/api/tasks \ + -H "Authorization: Bearer $TOKEN" +# Returns: { "tasks": [{ "id": 1, "task": "Buy milk", "status": "pending" }] } + +# 5. Update task status +curl -X PATCH http://localhost:8080/api/tasks/1/status \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"status": "completed"}' +# Returns: { "message": "status updated" } + +# 6. Delete task +curl -X DELETE http://localhost:8080/api/tasks/1 \ + -H "Authorization: Bearer $TOKEN" +# Returns: { "message": "Task deleted successfully" } +``` + +--- + +## Error Handling + +### Common Errors + +| Error | Cause | Solution | +|-------|-------|----------| +| `authorization header required` | Missing Authorization header | Add `Authorization: Bearer ` header | +| `invalid or expired token` | Token invalid or expired | Login again to get new token | +| `user account not found` | User deleted or doesn't exist | Register new account | +| `invalid credentials` | Wrong email or password | Check credentials | +| `email already exists` | Email already registered | Use different email | +| `invalid task ID` | Invalid ID format or < 1 | Use valid integer ID >= 1 | +| `Task not found` | Task doesn't exist or belongs to different user | Create task or check task ID | + +### Error Response Format + +All errors follow this format: +```json +{ + "error": "descriptive error message" +} +``` + +--- + +## Rate Limiting + +Currently no rate limiting is implemented. Future version may include: +- Per-user rate limits +- Per-endpoint rate limits +- Time-window based throttling + +--- + +## Pagination + +Currently not implemented. Tasks endpoint returns all user tasks. Future version may include: +- `limit` query parameter +- `offset` query parameter +- Cursor-based pagination + +--- + +## Filtering & Sorting + +Not currently implemented. Future features: +- Filter tasks by status +- Filter tasks by date range +- Sort by creation date, status, etc. + +--- + +## Swagger UI + +Interactive API documentation available at: + +``` +http://localhost:8080/swagger/index.html +``` + +Browse and test all endpoints directly in the browser with: +- Request body builder +- Response examples +- Status code documentation + +--- + +## Support & Issues + +For API issues or questions: +- Check the [ARCHITECTURE.md](./ARCHITECTURE.md) for system design +- Review [DEVELOPMENT.md](./DEVELOPMENT.md) for setup help +- Open an issue on GitHub with detailed error messages \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..9c60b38 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,473 @@ +# TaskFlow Architecture + +## Overview + +TaskFlow is a task management API built with Go, following a **layered monolithic architecture**. The system is organized into clear separation of concerns across different layers, making it maintainable, testable, and scalable. + +```mermaid +graph TB + Client["Client"] + + subgraph Router["API Layer"] + R["Gin Router"] + end + + subgraph MW["Middleware"] + Auth["JWT Authentication"] + end + + subgraph Handlers["Handlers"] + UH["User Handler"] + TH["Task Handler"] + end + + subgraph Services["Business Logic"] + US["User Service"] + TS["Task Service"] + end + + subgraph Repos["Data Access"] + UR["User Repository"] + TR["Task Repository"] + end + + DB[(MySQL Database)] + + Client -->|HTTP| R + R --> MW + MW --> UH + MW --> TH + UH --> US + TH --> TS + US --> UR + TS --> TR + UR --> DB + TR --> DB +``` + +## Project Structure + +``` +taskflow/ +โ”‚ +โ”œโ”€โ”€ internal/ # Private application code +โ”‚ โ”œโ”€โ”€ auth/ # Authentication & Authorization +โ”‚ โ”‚ โ”œโ”€โ”€ auth.go # JWT middleware implementation +โ”‚ โ”‚ โ”œโ”€โ”€ auth_interface.go # Auth interface contract +โ”‚ โ”‚ โ”œโ”€โ”€ auth_mock.go # Mock for testing +โ”‚ โ”‚ โ””โ”€โ”€ auth_test.go # Auth middleware tests +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ common/ # Shared utilities & types +โ”‚ โ”‚ โ””โ”€โ”€ response.go # Standard API response format +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ domain/ # Core business entities +โ”‚ โ”‚ โ”œโ”€โ”€ task/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ entity.go # Task model with GORM tags +โ”‚ โ”‚ โ””โ”€โ”€ user/ +โ”‚ โ”‚ โ””โ”€โ”€ entity.go # User model with GORM tags +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ dto/ # Data Transfer Objects +โ”‚ โ”‚ โ”œโ”€โ”€ task.go # Task request/response DTOs +โ”‚ โ”‚ โ””โ”€โ”€ user.go # User request/response DTOs +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ handler/ # HTTP request handlers +โ”‚ โ”‚ โ”œโ”€โ”€ task/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ task_handler.go # Task HTTP handlers +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ task_handler_interface.go # Handler contract +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ task_handler_test.go # Handler tests +โ”‚ โ”‚ โ””โ”€โ”€ user/ +โ”‚ โ”‚ โ”œโ”€โ”€ user_handler.go # User HTTP handlers +โ”‚ โ”‚ โ”œโ”€โ”€ user_handler_interface.go # Handler contract +โ”‚ โ”‚ โ””โ”€โ”€ user_handler_test.go # Handler tests +โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€ repository/ # Data access layer (GORM) +โ”‚ โ”‚ โ””โ”€โ”€ gorm/ +โ”‚ โ”‚ โ”œโ”€โ”€ gorm_task/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ task_repository.go # Task DB operations +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ task_repository_interface.go # Repository contract +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ task_repository_mock.go # Mock for testing +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ task_repository_test.go # Integration tests +โ”‚ โ”‚ โ””โ”€โ”€ gorm_user/ +โ”‚ โ”‚ โ”œโ”€โ”€ user_repository.go # User DB operations +โ”‚ โ”‚ โ”œโ”€โ”€ user_repository_interface.go # Repository contract +โ”‚ โ”‚ โ”œโ”€โ”€ user_repo_mock.go # Mock for testing +โ”‚ โ”‚ โ””โ”€โ”€ user_repository_test.go # Integration tests +โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€ service/ # Business logic layer +โ”‚ โ”œโ”€โ”€ task/ +โ”‚ โ”‚ โ”œโ”€โ”€ task_service.go # Task business logic +โ”‚ โ”‚ โ”œโ”€โ”€ task_service_interface.go # Service contract +โ”‚ โ”‚ โ”œโ”€โ”€ task_service_mock.go # Mock for testing +โ”‚ โ”‚ โ””โ”€โ”€ task_service_test.go # Unit tests +โ”‚ โ””โ”€โ”€ user/ +โ”‚ โ”œโ”€โ”€ user_service.go # User business logic +โ”‚ โ”œโ”€โ”€ user_service_interface.go # Service contract +โ”‚ โ”œโ”€โ”€ user_service_mock.go # Mock for testing +โ”‚ โ””โ”€โ”€ user_service_test.go # Unit tests +โ”‚ +โ”œโ”€โ”€ pkg/ # Reusable packages +โ”‚ โ”œโ”€โ”€ database/ +โ”‚ โ”‚ โ””โ”€โ”€ connection.go # DB connection, config, migrations +โ”‚ โ”œโ”€โ”€ env.go # Environment variable helpers +โ”‚ โ””โ”€โ”€ jwt/ +โ”‚ โ”œโ”€โ”€ jwt.go # JWT creation & validation +โ”‚ โ””โ”€โ”€ jwt_test.go # JWT utility tests +โ”‚ +โ”œโ”€โ”€ test/ +โ”‚ โ””โ”€โ”€ integration/ +โ”‚ โ””โ”€โ”€ db_integration_test.go # Database integration tests +โ”‚ +โ”œโ”€โ”€ main.go # Application entry point +โ”œโ”€โ”€ Dockerfile # Production image (multi-stage) +โ”œโ”€โ”€ Dockerfile.dev # Development image (hot-reload) +โ”œโ”€โ”€ docker-compose.yml # Production composition +โ”œโ”€โ”€ docker-compose.override.yml # Development overrides +โ””โ”€โ”€ go.mod, go.sum # Go module dependencies +``` + +--- + +## Architecture Layers + +### 1. **API Layer** (`internal/handler/`) +**Responsibility**: Handle HTTP requests and responses + +- **User Handler** (`user_handler.go`): Manages authentication endpoints + - `Register()` - Create new user account + - `Login()` - Authenticate user and issue JWT + - `UpdatePassword()` - Change user password + - `DeleteUser()` - Delete user account + +- **Task Handler** (`task_handler.go`): Manages task endpoints + - `CreateTask()` - Create new task + - `GetTask()` - Fetch single task + - `ListTasks()` - Fetch all user tasks + - `UpdateStatus()` - Update task status + - `Delete()` - Delete task + +**Key Pattern**: All handlers implement their interface (`TaskHandlerInterface`, `UserHandlerInterface`) for testability and dependency injection. + +### 2. **Middleware Layer** (`internal/auth/`) +**Responsibility**: Cross-cutting concerns (authentication, authorization) + +- **AuthMiddleware**: Validates JWT tokens on protected routes + - Extracts token from `Authorization: Bearer ` header + - Validates token signature and expiration + - Verifies user still exists (prevents deleted user access) + - Sets user ID in request context + - Returns `401 Unauthorized` if token invalid + +- **OptionalAuthMiddleware**: Optional authentication for public routes + - Doesn't reject requests without tokens + - Sets user ID if valid token provided + - Allows route handlers to check if user is authenticated + +### 3. **Service Layer** (`internal/service/`) +**Responsibility**: Implement business logic and validation + +- **User Service** (`user_service.go`): + - `CreateUser()` - Validate email uniqueness, hash password, create user + - `AuthenticateUser()` - Verify credentials, generate JWT token + - `UpdatePassword()` - Validate old password, hash and update new one + - `DeleteUser()` - Soft delete user (GORM handles deletion) + +- **Task Service** (`task_service.go`): + - `CreateTask()` - Validate input, create task with pending status + - `GetTask()` - Fetch task with ownership verification + - `ListTasks()` - Return all tasks for authenticated user + - `UpdateStatus()` - Validate status value, update task + - `Delete()` - Delete task with ownership check + +**Key Pattern**: Services are where validation, business rules, and data transformation happen. They call repositories for DB operations and don't know about HTTP details. + +### 4. **Repository Layer** (`internal/repository/gorm/`) +**Responsibility**: Data access abstraction using GORM ORM + +- **Task Repository**: + - `Create()` - Insert new task + - `GetByID()` - Fetch task by ID with user verification + - `List()` - Query all tasks for a user + - `Update()` - Persist task changes + - `Delete()` - Remove task from database + - `UpdateStatus()` - Update only status field + +- **User Repository**: + - `Create()` - Insert new user + - `GetByID()` - Fetch user by ID + - `GetByEmail()` - Fetch user by email for login + - `Update()` - Update user fields + - `Delete()` - Soft delete user (GORM automatically) + +**Key Pattern**: All repositories implement interfaces. Direct queries are parameterized to prevent SQL injection. GORM automatically handles soft deletes via `DeletedAt` field. + +### 5. **Domain Layer** (`internal/domain/`) +**Responsibility**: Core business entities (data models) + +- **User Entity**: + ```go + type User struct { + ID int // Primary key + Email string // Unique email + Password string // Hashed password + Tasks []Task // Related tasks (cascade delete) + Timestamps // CreatedAt, UpdatedAt + DeletedAt gorm.DeletedAt // Soft delete support + } + ``` + +- **Task Entity**: + ```go + type Task struct { + ID int // Primary key + Task string // Task description + Status string // pending/completed + UserID int // Foreign key to User + CreatedAt time.Time + } + ``` + +**Key Pattern**: Entities contain GORM tags for table structure and SWAGGER tags for API documentation. + +### 6. **DTO Layer** (`internal/dto/`) +**Responsibility**: Request/response data structures (API contracts) + +- **Request DTOs**: Validate and bind incoming JSON + - `CreateTaskRequest` - Task with binding validation + - `CreateUserRequest` - Email and password with min length + - `AuthRequest` - Email/password for login + +- **Response DTOs**: Shape outgoing JSON + - `CreateTaskResponse` - Return created task data + - `ListTasksResponse` - Paginated task list + - `AuthResponse` - JWT token + user info + +**Key Pattern**: DTOs use `binding` tags for validation, `example` tags for Swagger docs, and `json` tags for serialization. + +--- + +## Security Features + +### Authentication +- **JWT (JSON Web Tokens)**: 24-hour expiration +- **Bearer Token**: Passed in `Authorization: Bearer ` header +- **Token Validation**: Signature verification + expiration check + +### Password Security +- **bcrypt Hashing**: Industry-standard password hashing with salt +- **Min Length**: 6 characters enforced at validation layer + +### Data Protection +- **User Verification**: Deleted users cannot access even with valid token +- **Ownership Check**: Users can only access/modify their own tasks +- **SQL Injection Prevention**: GORM parameterized queries + +### Soft Deletes +- Users can be recovered after deletion using `db.Unscoped()` +- Tasks cascade-delete when user is deleted + +--- + +## Authentication Flow + +``` +1. User Registration + POST /auth/register + โ”œโ”€ Validate email format & password length + โ”œโ”€ Hash password with bcrypt + โ”œโ”€ Create user in database + โ””โ”€ Return user ID & email + +2. User Login + POST /auth/login + โ”œโ”€ Look up user by email + โ”œโ”€ Compare provided password with hash + โ”œโ”€ Create JWT token (24h expiration) + โ””โ”€ Return token & user details + +3. Protected Request + GET /tasks (with Authorization header) + โ”œโ”€ Extract token from header + โ”œโ”€ Validate JWT signature + โ”œโ”€ Check token expiration + โ”œโ”€ Verify user exists in database + โ”œโ”€ Set userID in request context + โ””โ”€ Continue to handler +``` + +--- + +## Database Schema + +### Users Table +```sql +CREATE TABLE users ( + id INT PRIMARY KEY AUTO_INCREMENT, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP, + deleted_at TIMESTAMP NULL -- Soft delete +); +``` + +### Tasks Table +```sql +CREATE TABLE tasks ( + id INT PRIMARY KEY AUTO_INCREMENT, + task VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL, -- pending/completed + user_id INT NOT NULL, + created_at TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + INDEX (user_id) +); +``` + +--- + +## Dependency Injection + +All components follow constructor-based dependency injection: + +```go +// Repositories depend on *gorm.DB +repo := gorm_task.NewTaskRepository(db) + +// Services depend on Repositories +svc := task_service.NewTaskService(repo) + +// Handlers depend on Services +handler := task_handler.NewTaskHandler(svc, userAuth) + +// Router connects everything +router.POST("/tasks", handler.CreateTask) +``` + +**Benefits**: +- Easy to test (inject mocks) +- Loose coupling (depend on interfaces) +- Clear dependency graph + +--- + +## Testing Strategy + +### Unit Tests +- **Services**: Mock repositories, test business logic +- **Repositories**: SQLite in-memory database +- **Auth**: Mock user repository, test JWT validation + +### Integration Tests +- **Database**: Docker MySQL container (dockertest) +- **Full stack**: Handler โ†’ Service โ†’ Repository โ†’ DB + +### Mocking Pattern +Each package provides `*Mock` types: +- `TaskServiceMock` - For testing handlers +- `TaskRepoMock` - For testing services +- `MockUserRepository` - For testing auth middleware + +--- + +## Development vs Production + +### Docker Images + +**Production** (`Dockerfile`): +- Multi-stage build (smaller final image) +- Alpine Linux base +- Only compiled binary included + +**Development** (`Dockerfile.dev`): +- Full Go toolchain +- Air (hot-reload on code changes) +- Swag (Swagger doc generation) + +### Docker Compose + +**Production** (`docker-compose.yml`): +- Production Dockerfile +- No volume mounts +- Environment-based config + +**Development** (`docker-compose.override.yml`): +- Development Dockerfile +- Volume mounts for code +- Hot-reload enabled +- Healthchecks for MySQL + +--- + +## API Endpoints + +### Authentication (Public) +``` +POST /api/auth/register # Create account +POST /api/auth/login # Get JWT token +``` + +### Tasks (Protected) +``` +POST /api/tasks # Create task +GET /api/tasks # List all user tasks +GET /api/tasks/:id # Get single task +PATCH /api/tasks/:id/status # Update task status +DELETE /api/tasks/:id # Delete task +``` + +### Users (Protected) +``` +PATCH /api/users/password # Change password +DELETE /api/users/account # Delete account +``` + +--- + +## Error Handling + +Standard error response format: +```json +{ + "error": "authorization header required" +} +``` + +HTTP Status Codes: +- **201**: Created successfully +- **400**: Bad request (validation error) +- **401**: Unauthorized (invalid/missing token) +- **404**: Resource not found +- **409**: Conflict (duplicate email) +- **500**: Server error + +--- + +## Environment Variables + +```bash +# JWT Configuration +JWT_SECRET=ADD-YOUR-SECRET + +# Database Configuration +MYSQL_USER=NEW-USER +MYSQL_PASSWORD=CHANGE-PASSWORD +MYSQL_HOST=mysql +MYSQL_PORT=3306 +MYSQL_DATABASE=CHANGE-DB-NAME +``` + +--- + +## Key Design Patterns + +| Pattern | Usage | Benefit | +|---------|-------|---------| +| **Repository Pattern** | Data access abstraction | Easy to swap databases, testable | +| **Service Layer** | Business logic isolation | Reusable across handlers, testable | +| **Dependency Injection** | Loose coupling | Easy testing with mocks | +| **Interface-based Design** | Define contracts | Mockable, polymorphic | +| **Soft Deletes** | Data recovery | Audit trail, undo capability | +| **JWT Tokens** | Stateless auth | Scalable, no session storage | +| **Layered Architecture** | Clear separation | Maintainable, scalable | + +--- diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..962a62c --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,404 @@ +# Development Guide + +This guide covers how to set up your development environment, run the application locally, and contribute to TaskFlow. + +--- + +## Prerequisites + +- **Go** 1.25+ ([Download](https://golang.org/dl/)) +- **Docker & Docker Compose** ([Download](https://www.docker.com/products/docker-desktop)) +- **Git** ([Download](https://git-scm.com/)) +- **Make** (optional, for running commands) + +### Verify Installation + +```bash +go version # Go 1.25+ +docker --version # Docker 28.5+ +docker-compose --version # Docker Compose 2.40+ +git --version +``` + +--- + +## Quick Start + +### 1. Clone the Repository + +```bash +git clone https://github.com/yourusername/taskflow.git +cd taskflow +``` + +### 2. Set Up Environment Variables + +Create a `.env` file in the root directory: + +```bash +# JWT Configuration +JWT_SECRET=ADD-YOUR-SECRET + +# Database Configuration +MYSQL_USER=NEW-USER +MYSQL_PASSWORD=CHANGE-PASSWORD +MYSQL_HOST=mysql +MYSQL_PORT=3306 +MYSQL_DATABASE=CHANGE-DB-NAME +``` + +### 3. Start Services with Docker Compose + +```bash +# Development mode with hot-reload +docker-compose -f docker-compose.override.yml up + +# Or production mode +docker-compose up +``` + +This starts: +- **MySQL 8.0** on port 3306 +- **Go API** on port 8080 (with hot-reload in dev) +- **MySQL** initialization with `.env` settings + +### 4. Verify the Application + +```bash +# Check API is running +curl http://localhost:8080/swagger/index.html + +# Or test an endpoint +curl -X POST http://localhost:8080/api/auth/register \ + -H "Content-Type: application/json" \ + -d '{"email":"test@example.com","password":"password123"}' +``` + +--- + +## Development Workflow + +### Without Docker (Local Development) + +If you prefer running Go locally: + +```bash +# 1. Start only MySQL +docker-compose up mysql + +# 2. Download dependencies +go mod download + +# 3. Install development tools +go install github.com/air-verse/air@latest +go install github.com/swaggo/swag/cmd/swag@latest + +# 4. Run with Air (hot-reload) +air + +# 5. In another terminal, generate Swagger docs +swag init +``` + +**Air** watches for file changes and automatically rebuilds the app. + +--- + +## Running Tests + +### Unit Tests + +```bash +# Run all unit tests +go test ./... + +# Run tests for specific package +go test ./internal/service/task/... + +# Run with verbose output +go test -v ./... + +# Run with coverage +go test -cover ./... + +# Generate coverage report +go test -coverprofile=coverage.out ./... +go tool cover -html=coverage.out +``` + +### Integration Tests + +```bash +# Run integration tests (requires Docker) +cd test/integration +go test -v . + +# Or from root +go test -v ./test/integration/... +``` + +--- + +## Code Structure & Best Practices + +### Adding a New Endpoint + +1. **Create Handler** in `internal/handler/{entity}/` + ```go + func (h *TaskHandler) MyNewAction(c *gin.Context) { + // Implementation + } + ``` + +2. **Add to Interface** in `internal/handler/{entity}/{entity}_handler_interface.go` + ```go + type TaskHandlerInterface interface { + MyNewAction(c *gin.Context) + } + ``` + +3. **Add Router** in `main.go` + ```go + taskRoutes.GET("/new-action", taskHandler.MyNewAction) + ``` + +4. **Add Tests** in `internal/handler/{entity}/{entity}_handler_test.go` + ```go + func TestTaskHandler_MyNewAction(t *testing.T) { + // Test cases + } + ``` + +### Adding Business Logic + +1. **Add Service Method** in `internal/service/{entity}/{entity}_service.go` + ```go + func (s *{Entity}Service) NewBusinessLogic(/* params */) error { + // Validation & logic + } + ``` + +2. **Add to Interface** in `internal/service/{entity}/{entity}_service_interface.go` + +3. **Call from Handler** + ```go + if err := h.service.NewBusinessLogic(/* args */); err != nil { + c.JSON(http.StatusBadRequest, common.ErrorResponse{Message: err.Error()}) + return + } + ``` + +4. **Test the Service** in `internal/service/{entity}/{entity}_service_test.go` + ```go + func TestService_NewBusinessLogic(t *testing.T) { + mockRepo := new(gorm_entity.MockRepository) + // Mock setup + service := NewService(mockRepo) + // Assertions + } + ``` + +### Adding Database Operations + +1. **Add Repository Method** in `internal/repository/gorm/gorm_{entity}/{entity}_repository.go` + ```go + func (r *{Entity}Repository) NewOperation(/* params */) error { + return r.db.Where("...").Do(...) + } + ``` + +2. **Add to Interface** in `internal/repository/gorm/gorm_{entity}/{entity}_repository_interface.go` + +3. **Add Mock** in `internal/repository/gorm/gorm_{entity}/{entity}_repository_mock.go` + +4. **Test with SQLite** in `internal/repository/gorm/gorm_{entity}/{entity}_repository_test.go` + +--- + +## Database Operations + +### View Database + +```bash +# Connect to MySQL container +docker-compose exec mysql mysql -u -p + +# Inside MySQL shell +SHOW TABLES; +SELECT * FROM users; +SELECT * FROM tasks; +DESCRIBE users; +``` + +### Reset Database + +```bash +# Drop all tables (careful!) +docker-compose exec mysql mysql -u -p -e "DROP TABLE tasks; DROP TABLE users;" + +# Restart services +docker-compose down +docker-compose up +``` + +### Migrations + +Migrations run automatically on startup via `database.MigrateModels()` in `main.go`: + +```go +if err := database.MigrateModels(db, &user.User{}, &task.Task{}); err != nil { + log.Fatal(err) +} +``` + +To add migrations: +1. Modify entity struct in `internal/domain/{entity}/entity.go` +2. Restart the application (GORM auto-migrates) + +--- + +## API Documentation + +### Swagger UI + +Swagger documentation is auto-generated from code comments: + +```bash +# View docs (running locally) +http://localhost:8080/swagger/index.html + +# Generate new docs after changes +swag init +``` + +### Adding Swagger Comments + +```go +// GetTask godoc +// @Summary Get a task by ID +// @Description Returns details of a specific task by ID +// @Tags tasks +// @Produce json +// @Param id path int true "Task ID" minimum(1) example(1) +// @Success 200 {object} dto.GetTaskResponse +// @Failure 404 {object} common.ErrorResponse +// @Router /tasks/{id} [get] +func (h *TaskHandler) GetTask(c *gin.Context) { + // Implementation +} +``` + +--- + +## Common Commands + +```bash +# Build the application +go build -o taskflow . + +# Run linter (requires golangci-lint) +golangci-lint run + +# Format code +go fmt ./... + +# Run go vet (catch common mistakes) +go vet ./... + +# Download/verify dependencies +go mod download +go mod verify + +# Tidy dependencies +go mod tidy + +# View dependency tree +go mod graph +``` + +--- +### Using Logs + +The application uses Go's standard `log` package: + +```go +log.Println("Debug message") +log.Printf("Formatted message: %v", value) +log.Fatal("Fatal error") +``` + +View logs in Docker: + +```bash +docker-compose logs -f goapp # Follow logs +docker-compose logs goapp # View history +``` + +--- + +## Troubleshooting + +### MySQL Connection Issues + +**Error**: `Error 1045 (28000): Access denied for user` + +```bash +# Check credentials in .env +# Verify MySQL is healthy +docker-compose ps + +# Restart MySQL +docker-compose restart mysql + +# View MySQL logs +docker-compose logs mysql +``` + +### Port Already in Use + +```bash +# Kill process using port 8080 +lsof -i :8080 +kill -9 + +# Or use different port in docker-compose.override.yml +ports: + - "8081:8080" +``` + +### Tests Failing + +```bash +# Clear test cache +go clean -testcache + +# Run with verbose output +go test -v ./... + +# Run specific test +go test -run TestName ./package/... +``` + +### Hot-Reload Not Working + +```bash +# Check Air is running +docker-compose logs goapp + +# Restart service +docker-compose restart goapp + +# Check .air.toml config exists +cat .air.toml +``` + +--- + +## Resources + +- [Go Documentation](https://golang.org/doc/) +- [Gin Web Framework](https://gin-gonic.com/) +- [GORM Documentation](https://gorm.io/) +- [JWT-Go](https://github.com/golang-jwt/jwt) +- [Docker Documentation](https://docs.docker.com/) +- [Air Hot Reload](https://github.com/cosmtrek/air) \ No newline at end of file diff --git a/docs/LEARNING.md b/docs/LEARNING.md new file mode 100644 index 0000000..e69de29