diff --git a/.env.example b/.env.example index 93a8af0..b3f6ba6 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,14 @@ +VITE_API_URL=http://localhost:8080 + CLIENT_PORT=8000 SERVER_PORT=8080 POSTGRES_USER=capuchin_user POSTGRES_PASSWORD=capuchin POSTGRES_DB=capuchin_dev -POSTGRES_HOST=capuchin-db POSTGRES_PORT=5432 +# Only needed when running the backend outside Docker (e.g. `go run` or `air` directly). +# In compose, the host is hardcoded to the postgres container name (capuchin-db). +# POSTGRES_HOST=localhost JWT_SECRET=your_jwt_secret_here diff --git a/.gitignore b/.gitignore index de2736c..01365c4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ dist-ssr/ # Test binary, built with `go test -c` *.test server +test.sh # Go workspace file go.work @@ -97,3 +98,8 @@ crash.*.log # personal docs/ideas.md backup/ +.kiro + + +# removing for now +.github/ diff --git a/Makefile b/Makefile index c355356..e00d379 100644 --- a/Makefile +++ b/Makefile @@ -6,34 +6,56 @@ ifneq (, $(shell command -v docker 2> /dev/null)) CONTAINER_RUNTIME := docker endif -# Docker Dev Mode (Hot Reload) -dev: +.DEFAULT_GOAL := help + +help: ## Show available targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' + +# ── Dev (hot reload via Docker) ─────────────────────────────────────────────── + +dev: ## Start all services in dev mode (hot reload) $(CONTAINER_RUNTIME) compose --env-file .env.example -f compose-dev.yml up --build -d -dev-logs: - $(CONTAINER_RUNTIME) compose -f compose-dev.yml logs +dev-logs: ## Tail dev logs + $(CONTAINER_RUNTIME) compose -f compose-dev.yml logs -f -dev-down: +dev-down: ## Stop dev services $(CONTAINER_RUNTIME) compose -f compose-dev.yml down -clean: + +clean: ## Stop dev services and remove volumes, images, orphans $(CONTAINER_RUNTIME) compose -f compose-dev.yml down --volumes --remove-orphans --rmi all +# ── Prod ────────────────────────────────────────────────────────────────────── -prod: - $(CONTAINER_RUNTIME) compose --env-file .env -f compose.yml up +prod: ## Start all services in prod mode (detached) + $(CONTAINER_RUNTIME) compose --env-file .env -f compose.yml up -d -logs: +logs: ## Tail prod logs $(CONTAINER_RUNTIME) compose -f compose.yml logs -f -down: +down: ## Stop prod services $(CONTAINER_RUNTIME) compose -f compose.yml down +# ── Local dev (outside Docker) ──────────────────────────────────────────────── -frontend: +frontend: ## Start frontend dev server cd frontend && npm run dev -backend: +backend: ## Start backend with hot reload (requires air: go install github.com/air-verse/air@v1.61.7) cd backend && air -.PHONY: dev dev-logs dev-down prod logs down +# ── Database ────────────────────────────────────────────────────────────────── + +migrate: ## Run migrations against localhost DB (reads .env for credentials) + @set -a && . ./.env.example && set +a && export POSTGRES_HOST=localhost && cd backend/migration && go run ./cmd/migrate up + +migrate-down: ## Roll back the last migration against localhost DB + @set -a && . ./.env.example && set +a && export POSTGRES_HOST=localhost && cd backend/migration && go run ./cmd/migrate down + +seed: ## Seed dev database with sample data (reads .env.example for credentials) + @set -a && . ./.env.example && set +a && export POSTGRES_HOST=localhost && cd backend && go run ./cmd/seed + +migrate-build: ## Build migration Docker image + docker build -f backend/migration/Dockerfile -t capuchin-migration ./backend +.PHONY: help dev dev-logs dev-down clean prod logs down frontend backend migrate migrate-down seed migrate-build diff --git a/backend/.dockerignore b/backend/.dockerignore index 03192e5..712faca 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -2,6 +2,7 @@ vendor bin server +tmp *.exe *.exe~ *.dll diff --git a/backend/Dockerfile b/backend/Dockerfile index ac91466..9988de8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,41 +1,37 @@ -FROM golang:1.25.5-alpine AS deps +FROM golang:1.26-alpine AS deps WORKDIR /app -# Download dependencies COPY go.mod go.sum ./ RUN go mod download # Build Stage FROM deps AS builder -# Copy source code COPY . . -# Build the application # CGO_ENABLED=0 ensures a statically linked binary RUN CGO_ENABLED=0 GOOS=linux go build -o server cmd/server/main.go -# Development Stage +# Development Stage - pinned air version for reproducible dev builds FROM deps AS dev -RUN go install github.com/air-verse/air@latest +RUN go install github.com/air-verse/air@v1.61.7 CMD ["air", "-c", "air.toml"] - -# Final Stage +# Final Stage - minimal image, non-root user for security FROM scratch -# Set working directory to the app root WORKDIR /app -# Copy the binary from the builder stage +# Copy passwd so the non-root user exists in scratch +COPY --from=builder /etc/passwd /etc/passwd + COPY --from=builder /app/server ./ -# Expose the application port EXPOSE 8080 -# Run the application -CMD ["./server"] +USER nobody +CMD ["./server"] diff --git a/backend/air.toml b/backend/air.toml index 38dc962..01d1c2f 100644 --- a/backend/air.toml +++ b/backend/air.toml @@ -3,8 +3,8 @@ tmp_dir = "tmp" [build] cmd = "go build -o ./tmp/main ./cmd/server/main.go" - bin = "./tmp/main" - full_bin = "" + bin = "" + entrypoint = "./tmp/main" include_ext = ["go", "tpl", "tmpl", "html"] exclude_dir = ["assets", "tmp", "vendor"] include_dir = [] diff --git a/compose-dev.yml b/compose-dev.yml index de4b465..858549b 100644 --- a/compose-dev.yml +++ b/compose-dev.yml @@ -1,45 +1,40 @@ services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - target: dev - container_name: capuchin-server - depends_on: - capuchin-db: - condition: service_healthy - environment: - MODE: dev - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_HOST: ${POSTGRES_HOST} - ports: - - "${SERVER_PORT:-8080}:8080" - restart: unless-stopped - volumes: - - ./backend:/app - capuchin-db: container_name: capuchin-db environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_HOST: ${POSTGRES_HOST} - ports: - "5432:5432" - healthcheck: interval: 5s retries: 5 - test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] timeout: 5s image: postgres:17-alpine volumes: - - ./backup:/var/lib/postgresql - - ./backend/db/init.sql:/docker-entrypoint-initdb.d/init.sql + # Separate dev volume - keeps dev data isolated from prod backup/data + - capuchin-dev-data:/var/lib/postgresql/data + - ./backend/db/init.sql:/docker-entrypoint-initdb.d/init.sql + + backend: + build: + context: ./backend + dockerfile: Dockerfile + target: dev + container_name: capuchin-server + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_HOST: capuchin-db + JWT_SECRET: ${JWT_SECRET} + ports: + - "${SERVER_PORT:-8080}:8080" + restart: unless-stopped + volumes: + - ./backend:/app frontend: build: @@ -53,6 +48,8 @@ services: - "${CLIENT_PORT:-5173}:5173" restart: unless-stopped volumes: - - ./frontend:/app - - /app/node_modules + - ./frontend:/app + - /app/node_modules +volumes: + capuchin-dev-data: diff --git a/compose.yml b/compose.yml index 1332d08..9b527b6 100644 --- a/compose.yml +++ b/compose.yml @@ -5,50 +5,39 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_HOST: ${POSTGRES_HOST} + # Healthcheck is informational - backend manages its own DB connection retry. healthcheck: interval: 5s retries: 5 - test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] timeout: 5s image: postgres:17-alpine volumes: - - ./backup:/var/lib/postgresql - - ./backend/db/init.sql:/docker-entrypoint-initdb.d/init.sql - + - ./backup/data:/var/lib/postgresql/data backend: container_name: capuchin-server - build: context: ./backend dockerfile: Dockerfile ports: - "${SERVER_PORT:-8080}:8080" - volumes: - - ./backend/db:/app/db restart: unless-stopped - environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_HOST: ${POSTGRES_HOST} - - depends_on: - capuchin-db: - condition: service_healthy + POSTGRES_HOST: capuchin-db + JWT_SECRET: ${JWT_SECRET} frontend: container_name: capuchin-client - build: context: ./frontend dockerfile: Dockerfile target: prod args: VITE_API_URL: ${VITE_API_URL} - ports: - "${CLIENT_PORT:-8000}:80" restart: unless-stopped diff --git a/docs/backend_api.md b/docs/backend_api.md deleted file mode 100644 index 05625b9..0000000 --- a/docs/backend_api.md +++ /dev/null @@ -1,137 +0,0 @@ -# Capuchin Backend API Contract - -This document provides a comprehensive overview of all available backend endpoints, their expected JSON payloads, requirements, and responses. - -## Base URL -When running locally: `http://localhost:8080` - -All endpoints return JSON responses. Errors are formatted as `{"error": "description"}`. - ---- - -## Public Endpoints - -### 1. Health Check -Checks if the server is running. -- **URL**: `/health` -- **Method**: `GET` -- **Auth Required**: No -- **Response**: `200 OK` - ```json - {"status": "ok"} - ``` - -### 2. User Signup -Registers a new user account. -- **URL**: `/signup` -- **Method**: `POST` -- **Auth Required**: No -- **Payload**: - ```json - { - "email": "user@example.com", // Required, must be valid email - "password": "strongpassword123" // Required, min 8 characters - } - ``` -- **Responses**: - - `201 Created`: `{"message": "User created successfully"}` - - `400 Bad Request`: Validation failure (missing fields or password < 8 chars) - - `409 Conflict`: User with the specified email already exists - -### 3. User Login -Authenticates a user and returns a JWT token. -- **URL**: `/login` -- **Method**: `POST` -- **Auth Required**: No -- **Payload**: - ```json - { - "email": "user@example.com", - "password": "strongpassword123" - } - ``` -- **Responses**: - - `200 OK`: `{"token": "ey..."}` (Use this token as a Bearer token in subsequent Protected requests) - - `401 Unauthorized`: Invalid credentials - ---- - -## Protected Endpoints -All protected endpoints are grouped under `/api/user/`. They require a valid JWT token in the `Authorization` header. -**Header Format:** `Authorization: Bearer ` - -### 4. Logout User (Revoke Token) -Logs the current user out by adding their JWT to a blacklist. -- **URL**: `/api/user/logout` -- **Method**: `POST` -- **Auth Required**: Yes -- **Responses**: - - `200 OK`: `{"message": "Logged out successfully"}` - - `401 Unauthorized`: Token is missing, expired, or already revoked - -### 5. Get All Todos -Retrieves all todo items belonging strictly to the authenticated user. -- **URL**: `/api/user/todo` -- **Method**: `GET` -- **Auth Required**: Yes -- **Responses**: - - `200 OK`: - ```json - [ - { - "id": "uuid-string", - "item": "Buy groceries", - "completed": false - } - ] - ``` - -### 6. Create Todo -Adds a new todo item for the authenticated user. -- **URL**: `/api/user/todo` -- **Method**: `POST` -- **Auth Required**: Yes -- **Payload**: - ```json - { - "item": "Review pull requests", // Required - "completed": false // Optional, defaults to false - } - ``` -- **Responses**: - - `200 OK`: - ```json - { - "id": "new-uuid-string", - "item": "Review pull requests", - "completed": false - } - ``` - - `400 Bad Request`: Missing the required `item` field - -### 7. Partially Update Todo (`PATCH`) -Updates specific fields (the text content, the completion status, or both) of an existing todo. It strictly enforces that the todo `id` provided in the path belongs to the authenticated user. -- **URL**: `/api/user/todo/:id` (Replace `:id` with the UUID of the todo) -- **Method**: `PATCH` -- **Auth Required**: Yes -- **Payload**: Provide one or both fields. - ```json - { - "item": "Review 5 pull requests", // Optional - "completed": true // Optional - } - ``` -- **Responses**: - - `200 OK`: Returns the updated todo schema as seen in GET. - - `400 Bad Request`: Invalid UUID format in URL or invalid JSON - - `404 Not Found`: Todo does not exist or does not belong to the user - -### 8. Delete Todo -Deletes a specific todo belonging strictly to the authenticated user. -- **URL**: `/api/user/todo/:id` (Replace `:id` with the UUID of the todo) -- **Method**: `DELETE` -- **Auth Required**: Yes -- **Responses**: - - `200 OK`: `{"message": "Todo deleted successfully"}` - - `400 Bad Request`: Invalid UUID format in URL - - `404 Not Found`: Todo does not exist or does not belong to the user diff --git a/docs/backend_architecture.md b/docs/backend_architecture.md deleted file mode 100644 index d77b135..0000000 --- a/docs/backend_architecture.md +++ /dev/null @@ -1,75 +0,0 @@ -# Capuchin Backend Architecture - -This document outlines the architectural design and structural patterns used in the Capuchin Go backend. - -## Overview - -The backend is built using **Go** and the **Gin Web Framework**. It follows a variation of the **Clean Architecture** and the **Standard Go Project Layout**, ensuring separation of concerns, scalability, and maintainability. - -The application interacts with a **PostgreSQL** database using the standard `database/sql` library and uses **JWT (JSON Web Tokens)** for stateless authentication. - -## Directory Structure - -The codebase is strictly divided into `cmd` for entry points and `internal` for private application code, preventing external imported usage of our core logic. - -```text -backend/ -├── cmd/ -│ └── server/ -│ └── main.go # Application entry point. Wires dependencies. -├── internal/ -│ ├── config/ # Environment loading and validation -│ ├── database/ # Global DB connection pool and schema init -│ ├── handlers/ # HTTP transport layer (Controllers) -│ ├── middleware/ # HTTP intercepts (Auth, Error Recovery) -│ ├── models/ # Domain data structures -│ ├── routes/ # Centralized route registration -│ └── services/ # Core business logic -└── ... -``` - -## Layered Architecture - -The application handles requests through three primary layers: - -1. **Routing Layer (`internal/routes`)** - - Registers all endpoints to their corresponding handler functions. - - Applies necessary middlewares (e.g., `AuthRequired`) to protected routes. - -2. **Transport / Handler Layer (`internal/handlers`)** - - Extracts and validates incoming HTTP requests (JSON body, Path params, Headers). - - Calls the appropriate Service methods. - - Formats the response (JSON) and returns appropriate HTTP status codes (200, 400, 404, 500). - - **Rule:** Handlers contain *no business logic* or direct database queries. - -3. **Service Layer (`internal/services`)** - - Contains all the core business logic. - - Enforces business rules (e.g., hashing passwords, verifying credentials, associating items). - - Communicates directly with the data store (`internal/database`). - - Returns business-level errors (e.g., `ErrUserExists`, `ErrTodoNotFound`) decoupled from HTTP transport. - -## Dependency Injection - -The application uses constructor injection to pass dependencies down the chain. This is primarily seen in the relationship between Handlers and Services: - -```go -// main.go initializes components and wires them together -todoService := services.NewTodoService() -todoHandler := handlers.NewTodoHandler(todoService) -``` - -This decouples the handler from a strictly concrete service implementation, paving the way for easier unit testing via mocked services in the future. - -## Database & Persistence - -- **Connection Pool:** A centralized `sql.DB` connection pool (`database.DB`) is initialized at startup. It configures connection lifetimes, max open, and max idle connections to prevent resource exhaustion. -- **Relational Integrity:** Uses standard PostgreSQL relations (e.g., `todos.user_id REFERENCES users(id)`). -- **UUIDs:** Primary keys are decentralized using UUIDs. - -## Authentication Flow - -Authentication is stateless and managed via JWTs: - -1. **Login:** A user logs in, the service verifies the hashed password via `bcrypt`, and generates an HS256 JWT containing the `user_id` and an expiration time. -2. **Authorization:** Protected routes use `middleware.AuthRequired()`, which intercepts requests, strictly validates the `Authorization` bearer token against the signing key, enforces the signing method, and extracts the `user_id` into the Gin context. -3. **Logout:** The application tracks revoked tokens using a database table `blacklisted_tokens`. When a user logs out, their specific token is inserted into this table. The auth middleware inherently rejects any blacklisted tokens. A background goroutine cleans up expired tokens hourly. diff --git a/docs/backend_best_practices.md b/docs/backend_best_practices.md deleted file mode 100644 index dd5579c..0000000 --- a/docs/backend_best_practices.md +++ /dev/null @@ -1,48 +0,0 @@ -# Capuchin Backend Best Practices - -This document outlines the coding standards, patterns, and best practices strictly enforced across the Go backend codebase. - -## 1. Centralized Configuration -Environment variables should never be accessed arbitrarily via `os.Getenv` throughout the business logic. -- All environment variables are loaded, parsed, and validated cleanly within `internal/config`. -- Missing required configurations immediately trigger a `log.Fatal()`, preventing the application from booting into a broken state. - -## 2. Interface-Driven Services -Services are defined using Go interfaces. -```go -type TodoService interface { - GetTodos(userID uuid.UUID) ([]models.Todo, error) - // ... -} -``` -This enables decoupled abstractions. If we decide to swap the database layer out for an ORM or a NoSQL database, we only rewrite the struct that satisfies the interface, leaving the handlers untouched. It also allows for generating mock services for unit testing the handler layer. - -## 3. Strong Typing and Struct Binding -We utilize Gin's `ShouldBindJSON` alongside struct tags to strictly map and validate incoming requests before processing them. We refuse requests with an HTTP 400 Bad Request if they violate validation tags (e.g., `binding:"required,min=8"` for passwords). - -```go -var reqBody struct { - Email string `json:"email" binding:"required,email"` - Password string `json:"password" binding:"required,min=8"` -} -``` - -## 4. Centralized Domain Errors -The Service layer does not return HTTP status codes or Gin contexts. Instead, it returns standard Go `error` types defined natively within the package. -```go -var ( - ErrUserExists = errors.New("user with this email already exists") - ErrInvalidCredentials = errors.New("invalid credentials") -) -``` -The Handler layer is responsible for translating these domain errors into the correct semantic HTTP response codes (e.g., 409 Conflict, 401 Unauthorized, 404 Not Found). - -## 5. Security Practices -- **Password Hashing:** Passwords are never stored or logged in plain text. We utilize the industry-standard `golang.org/x/crypto/bcrypt` to hash and salt passwords with an appropriate computational cost. -- **JWT Hardening:** The JWT middleware strictly forces the `jwt.WithValidMethods([]string{"HS256"})` and `jwt.WithExpirationRequired()` validators to prevent token tampering or downgrade attacks. -- **Data Isolation:** All protected routes fetch the user ID strictly from the verified JWT token (`c.MustGet("userID")`) injected by the middleware. We never trust `user_id` passed in the HTTP body, effectively preventing lateral data access (IDOR). -- **Graceful Error Recovery:** A global error recovery middleware traps unhandled panics, logs them securely on the server-side, and returns a generic `500 Internal Server Error` to the client, preventing stack trace exposure. - -## 6. Resource Management -- **Database Iterator Safety:** When iterating through `rows.Next()`, we explicitly check `rows.Err()` afterward. This catches scenarios where the iteration abruptly halted due to mid-network disconnects or corruption. -- **Background Cleanup:** Dead data (expired logout tokens) is swept away gracefully by an isolated Go routine initialized at startup `go func() { ... }()`, preventing table bloat over time. diff --git a/docs/backend_schema.md b/docs/backend_schema.md deleted file mode 100644 index 56b3daa..0000000 --- a/docs/backend_schema.md +++ /dev/null @@ -1,64 +0,0 @@ -# Capuchin Backend Database Schema - -This document outlines the data structures, tables, and relational constraints defined within the backend's PostgreSQL database. The schema is automatically initialized when the backend server boots via `database.InitSchema()`. - -## Tables Overview - -The application utilizes three primary tables: `users`, `todos`, and `blacklisted_tokens`. - ---- - -### 1. `users` -Stores all registered user accounts and their authentication data. - -| Column | Type | Constraints | Description | -| :--- | :--- | :--- | :--- | -| `id` | `UUID` | `PRIMARY KEY` | Unique identifier generated on server during signup. | -| `email` | `TEXT` | `UNIQUE NOT NULL` | The user's email address. Uniqueness is enforced at the DB level prevent race condition duplicate signups. | -| `password_hash` | `TEXT` | `NOT NULL` | The bcrypt-hashed representation of the user's password. Plain-text is never stored. | - ---- - -### 2. `todos` -Stores the individual to-do list items, referencing their owning user. - -| Column | Type | Constraints | Description | -| :--- | :--- | :--- | :--- | -| `id` | `UUID` | `PRIMARY KEY` | Unique identifier generated on server when todo is created. | -| `item` | `TEXT` | `NOT NULL` | The actual text content/task description. | -| `completed` | `BOOLEAN` | `DEFAULT FALSE` | Status flag denoting if the task is finished. | -| `user_id` | `UUID` | `REFERENCES users(id)` | **Foreign Key** linking the item to its owner. Enforces data ownership and multi-tenancy rules at the database level. | - ---- - -### 3. `blacklisted_tokens` -Stores JWT tokens that have been explicitly revoked by users logging out before the tokens' natural expiration time. This forms the backbone of the backend's stateless logout logic. - -| Column | Type | Constraints | Description | -| :--- | :--- | :--- | :--- | -| `token` | `TEXT` | `PRIMARY KEY` | The raw JWT string that has been logged out. | -| `expired_at` | `TIMESTAMP` | `NOT NULL` | The exact time the token would have naturally expired. A backend goroutine runs hourly discarding any rows where `expired_at < time.Now()` to prevent database bloat. | - ---- - -## Entity-Relationship Diagram (ERD) - -```mermaid -erDiagram - USERS ||--o{ TODOS : owns - USERS { - uuid id PK - text email UK - text password_hash - } - TODOS { - uuid id PK - text item - boolean completed - uuid user_id FK - } - BLACKLISTED_TOKENS { - text token PK - timestamp expired_at - } -``` diff --git a/docs/readme.md b/docs/readme.md deleted file mode 100644 index b108ede..0000000 --- a/docs/readme.md +++ /dev/null @@ -1,172 +0,0 @@ -## 📜 Capuchin: A basic Todo app -A basic full-stack todo list application with a Go (Golang) REST API backend and a React frontend with a professional-grade storage architecture. - -## 🚀 Features Implemented - -* **Backend (Go + Gin):** RESTful API with distinct layers (Handlers, Services, DB) and robust error handling. -* **Authentication:** Secure Signup, Login, and Logout using JWT tokens. -* **Database (PostgreSQL):** Relational persistence using `database/sql` with schema initialization on startup. -* **Frontend (React + Vite):** Modern reactive UI with Hooks (useState, useEffect). -* **Styling (Tailwind CSS):** Dark-mode interface with optimistic UI. -* **Architecture:** Clean architecture enforcing separation of concerns in 'internal'. -* **Containerization:** Docker & Docker Compose for Dev/Prod. - -## 📂 Project Structure - -``` -capuchin/ -├── backend/ -│ ├── cmd/ -│ │ └── server/ -│ │ └── main.go # Entry point -│ ├── internal/ -│ │ ├── config/ # Environment & Config setup -│ │ ├── database/ # PostgreSQL connection & init -│ │ ├── handlers/ # HTTP Route handlers -│ │ ├── middleware/ # Auth & Error middleware -│ │ ├── models/ # Data structures -│ │ ├── routes/ # API route definitions -│ │ └── services/ # Core business logic -│ ├── Dockerfile # Backend Container -│ ├── air.toml # Hot Reload Config -│ ├── go.mod # Dependencies -│ └── go.sum -├── frontend/ -│ ├── src/ -│ │ ├── App.tsx -│ │ ├── App.css -│ │ └── main.tsx -│ ├── Dockerfile # Frontend Container -│ ├── vite.config.ts # Build Config -│ └── package.json -├── compose.yml # Prod Orchestration -├── compose-dev.yml # Dev Mode Overrides -└── Makefile # Command shortcuts -└── package.json - -``` - -## 💻 Tech Stack -## 💻 Tech Stack -* **Backend:** Go (REST API, Clean Architecture) -* **Backend Framework:** Gin -* **Frontend:** React, TypeScript -* **Containerize:** Docker -* **Database:** PostgreSQL - -## 🛠️ How to Run - -### Method 1: In separate terminals - - - -#### Backend: - -Open Terminal 1 -``` Bash -cd backend -go run cmd/server/main.go -``` -`Server runs on localhost:8080` - -#### Frontend: - -Open Terminal 2 -``` Bash -cd frontend -npm run dev -``` -`Client opens at localhost:5173` - - ---- - -### Method 2: Using npm Script (In project home directory) - -Install npm packages -``` Bash -npm i -``` -Run npx script - -``` Bash -npx concurrently "cd ./backend/cmd/server && go run main.go" "npm run dev --prefix ./frontend" -``` - -- **Frontend**: http://localhost:5173 -- **Health Check**: http://localhost:8080/health -- **Backend API**: http://localhost:8080/todos - - ---- - - -### Method 3: Docker (In project home directory) - -We support two modes: **Development** (Hot-Reload) and **Production** (Lean Static Builds). - -#### Development Mode -Runs the backend with `Air` (Go hot-reload) and Frontend with `Vite` (HMR). Changes to code are reflected instantly. - -```bash -make dev -# OR -docker compose --env-file .env.example -f compose-dev.yml up --build -``` -- **Frontend**: http://localhost:5173 -- **Health Check**: http://localhost:8080/health -- **Backend API**: http://localhost:8080/todos - -#### Production Mode -Runs a lean, production-ready build (`scratch` image for Go, `nginx` for React). - -```bash -make prod -# OR -docker compose --env-file .env -f compose.yml up --build -``` -- **App**: http://localhost -- **Health Check**: http://localhost:8080/health -- **Backend API**: http://localhost:8080/todos - -#### Stop Containers -```bash -make down -# OR -#in active terminal -ctrl+c or cmd+c -``` - ---- - - - - -## 🧠 Key Concepts Implemented (can be seen in comments) - -For an in-depth dive into the structure and patterns, please refer to our dedicated documentation: -- [Backend Architecture Reference](backend_architecture.md) -- [Backend Best Practices](backend_best_practices.md) -- [Backend API Contract](backend_api.md) -- [Backend Database Schema](backend_schema.md) - -* **Go:** Structs, Slices, JSON Marshalling, Modules, Package Exporting, Clean Architecture. -* **React:** Functional Components, Hooks, API Integration (fetch, async/await), Controlled Inputs. -* **Testing:** Included a robust `backend/verify_backend.sh` shell script to instantly orchestrate E2E integration tests against all API endpoints. -* **Docker:** Multi-stage builds, Scratch images, Docker Compose overrides. -* **General:** REST API Design, CORS, JSON Persistence, Refactoring,TypeScript(for styling), axios (for API calls) - - -Long term plans: - -folder todo -collaborators -real time update -organization -authentication -groups and access -sharelink -auth login -schedule with reminder -version control -mcp server diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..d3ca05b --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,4 @@ +VITE_API_URL=http://localhost:8080 + +CLIENT_PORT=8000 +SERVER_PORT=8080 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..692379c --- /dev/null +++ b/readme.md @@ -0,0 +1,115 @@ +## 📜 Capuchin: A robust Todo application +A feature-rich full-stack todo list application with a Go (Golang) REST API backend and a React/Vite frontend using a professional-grade decoupled architecture. + +## 🚀 Features Implemented + +* **Backend (Go + Gin):** RESTful API with distinct layers (`handlers`, `services`, `database`, `middleware`) and robust error handling. +* **Authentication:** Secure Signup, Login, and Logout using short-lived JWT tokens with a database-backed token blacklisting mechanism. +* **Database (PostgreSQL):** Relational persistence mapped implicitly to user context to enforce cross-tenant data isolation. +* **Frontend (React + Vite):** Modern reactive UI with custom asynchronous Hooks (`useTodos`, `useAuth`) abstracting away native `fetch` requests. +* **Offline-friendly mode:** Supports an unauthenticated Guest mode backed tightly by `localStorage`. +* **Containerization:** Clean Docker Compose multi-stage orchestrations covering both isolated local development profiles and production scratch-image deployment. + +## 📂 Project Structure + +```text +capuchin/ +├── backend/ +│ ├── cmd/ +│ │ ├── server/ # Entry point for the REST server +│ │ ├── migrate/ # Standalone binary runner for schema definitions +│ │ └── seed/ # Dev DB seed runner +│ ├── internal/ +│ │ ├── config/ # Environment & Config map parsing +│ │ ├── database/ # PostgreSQL driver configuration & pooling limits +│ │ ├── handlers/ # HTTP Route logic & payload validation +│ │ ├── middleware/ # Identity resolution & security guards +│ │ ├── models/ # Data structures +│ │ ├── routes/ # Mux mappings setup +│ │ └── services/ # Identity and persistence core logic workflows +│ ├── Dockerfile # Multi-stage Backend Container +│ ├── air.toml # Hot Reload configs +│ ├── go.mod # Go Dependencies +│ └── test.sh # Integration / E2E endpoint bash test harness +├── frontend/ +│ ├── src/ +│ │ ├── components/ # Presentational layout components +│ │ ├── hooks/ # Primary React state workflows (`useAuth`, `useTodos`) +│ │ ├── lib/ # Core native-fetch wrapper API logic +│ │ ├── pages/ # Page-level route views +│ │ ├── types/ # TypeScript definitions +│ │ ├── App.tsx +│ │ └── main.tsx +│ ├── Dockerfile # Nginx + React Multi-stage Frontend Container +│ ├── vite.config.ts # Vite bundling settings +│ └── package.json +├── compose.yml # Lean Production Orchestration +├── compose-dev.yml # Dev Mode (Air/Vite) overrides +└── Makefile # Command shortcuts +``` + +## 💻 Tech Stack +* **Backend:** Go (REST API, Clean Architecture) +* **Backend Framework:** Gin +* **Frontend:** React, TypeScript, Vite +* **Runtime Orchestration:** Docker, Make +* **Database:** PostgreSQL +* **Migrations:** Goose v3 (Inside Docker) + +## 🛠️ How to Run + +### Method 1: Docker (Recommended) +This approach encapsulates all dependencies securely via Docker Engine configurations. + +#### For Development (Hot-Reloading) +Runs the Go backend natively through Air for hot-schema reload mappings, and the React frontend via Vite HMR. +```sh +make dev +# OR +docker compose --env-file .env.example -f compose-dev.yml up --build +``` +- **Frontend App**: `http://localhost:5173` +- **Backend API Base**: `http://localhost:8080` + +#### For Production +Runs a lean production-ready sequence packaging the Go engine natively in a `scratch` container, and distributing the React codebase via `nginx`. +```bash +make prod +# OR +docker compose --env-file .env -f compose.yml up --build +``` + +### Method 2: Native via NPM script +Requires Go, Node.js, and Postgres installed natively on your machine! +Ensure your root `.env` accurately targets your native Postgres installation. +```bash +npm i +npx concurrently "cd ./backend/cmd/server && go run main.go" "npm run dev --prefix ./frontend" +``` + +--- + +## 🧠 Documentation & Key Concepts + +For an in-depth dive into the structure, API contract, database schema, and best practices, please refer to our full documentation on the **[GitHub Wiki](https://github.com/the-monkeys/capuchin/wiki)**. + +Key concepts utilized: +* **Go:** Structs, Slices, JSON Marshalling, Clean Architecture. +* **React:** Functional Components, Custom Hooks (`useTodos`, `useAuth`), fetch wrappers. +* **Testing:** `backend/test.sh` for E2E integration tests against API endpoints. +* **Docker:** Multi-stage builds, Scratch images, Docker Compose overrides. +* **General:** REST API Design, JWT Auth isolation, Postgres parameterization. + +Long term plans: + +folder todo +collaborators +real time update +organization +authentication +groups and access +sharelink +auth login +schedule with reminder +version control +mcp server