diff --git a/.vibe/skills/gitea-client/scripts/gitea-client.sh b/.vibe/skills/gitea-client/scripts/gitea-client.sh index 5353a49..105dc3d 100755 --- a/.vibe/skills/gitea-client/scripts/gitea-client.sh +++ b/.vibe/skills/gitea-client/scripts/gitea-client.sh @@ -203,6 +203,31 @@ cmd_wait_job() { } # Comment on PR +# Create a pull request +cmd_create_pr() { + local owner="$1" + local repo="$2" + local title="$3" + local body="$4" + local head="$5" + local base="${6:-main}" + + if [[ -z "$owner" || -z "$repo" || -z "$title" || -z "$head" ]]; then + echo "Usage: $0 create-pr <body> <head_branch> [base_branch]" >&2 + exit 1 + fi + + local endpoint="/repos/${owner}/${repo}/pulls" + local data + data=$(jq -n \ + --arg title "$title" \ + --arg body "$body" \ + --arg head "$head" \ + --arg base "$base" \ + '{title: $title, body: $body, head: $head, base: $base}') + api_request "POST" "$endpoint" "$data" +} + cmd_comment_pr() { local owner="$1" local repo="$2" @@ -215,7 +240,8 @@ cmd_comment_pr() { fi local endpoint="/repos/${owner}/${repo}/issues/${pr_number}/comments" - local data="{\"body\": \"${comment}\"}" + local data + data=$(jq -n --arg body "$comment" '{body: $body}') api_request "POST" "$endpoint" "$data" } @@ -250,6 +276,7 @@ main() { monitor-workflow) cmd_monitor_workflow "$@" ;; diagnose-job) cmd_diagnose_job "$@" ;; recent-workflows) cmd_recent_workflows "$@" ;; + create-pr) cmd_create_pr "$@" ;; comment-pr) cmd_comment_pr "$@" ;; pr-status) cmd_pr_status "$@" ;; list-issues) cmd_list_issues "$@" ;; @@ -274,6 +301,7 @@ main() { echo " monitor-workflow <owner> <repo> <workflow_run_id> [interval_seconds]" >&2 echo " diagnose-job <owner> <repo> <job_id>" >&2 echo " recent-workflows <owner> <repo> [limit] [status_filter]" >&2 + echo " create-pr <owner> <repo> <title> <body> <head_branch> [base_branch]" >&2 echo " comment-pr <owner> <repo> <pr_number> <comment>" >&2 echo " pr-status <owner> <repo> <pr_number>" >&2 echo " list-issues <owner> <repo> [state]" >&2 diff --git a/AGENTS.md b/AGENTS.md index 827bf7e..6b41084 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,84 +1,8 @@ -# dance-lessons-coach - AI Agent Documentation +# dance-lessons-coach โ€” Agent Documentation -This file documents the AI agents, tools, and development workflow for the dance-lessons-coach project. +AI agent reference for developing, testing, and operating the dance-lessons-coach service. -## ๐ŸŽฏ Project Overview - -**dance-lessons-coach** is a Go-based web service with CLI capabilities, featuring: -- RESTful JSON API with Chi router -- High-performance Zerolog logging -- Interface-based architecture -- Context-aware services -- Comprehensive testing - -## ๐Ÿ“‹ Development Timeline - -### Phase 1: Foundation (Completed โœ…) -- Go 1.26.1 environment setup -- Project structure with `cmd/` and `pkg/` directories -- Core Greet service implementation -- CLI interface -- Unit tests - -### Phase 2: Web API (Completed โœ…) -- Chi router integration -- Versioned API endpoints (`/api/v1`) -- Health endpoint (`/api/health`) -- JSON responses with proper headers - -### Phase 3: Logging & Architecture (Completed โœ…) -- Zerolog integration with Trace level -- Context-aware logging -- Interface-based design patterns -- Dependency injection - -### Phase 4: Documentation & Testing (Completed โœ…) -- Comprehensive AGENTS.md -- README.md with usage instructions -- Server management guide -- API endpoint documentation - -### Phase 5: Configuration Management (Completed โœ…) -- Viper integration for configuration -- Environment variable support with DLC_ prefix -- Customizable server host/port -- Configurable shutdown timeout -- Configuration validation and logging -- Example configuration file - -### Phase 6: Graceful Shutdown (Completed โœ…) -- Context-aware server initialization -- Signal-based termination (SIGINT, SIGTERM) -- Configurable shutdown timeout -- Readiness endpoint for Kubernetes/service mesh integration -- Proper resource cleanup during shutdown -- Health endpoint remains healthy during graceful shutdown - -### Phase 7: OpenTelemetry Integration (Completed โœ…) -- OpenTelemetry Go libraries integration -- Jaeger compatibility for distributed tracing -- Middleware-only approach using otelhttp.NewHandler -- Configurable sampling strategies -- Graceful shutdown of tracer provider -- OTLP exporter with gRPC support - -### Phase 8: Build System & Documentation (Completed โœ…) -- Build script for binary compilation -- Binary output to `bin/` directory -- Comprehensive commit conventions with gitmoji reference -- Updated documentation with Jaeger integration guide -- Cleaned up configuration files -- Enhanced logging configuration with file output support - -### Phase 9: Final Refinements (Completed โœ…) -- Removed unnecessary time.Sleep for log flushing -- Changed server operational logs from Info to Trace level -- Moved all logging setup logic to config package -- Simplified server entrypoint to 27 lines -- Verified all functionality with comprehensive testing -- Updated documentation to reflect final architecture - -## ๐Ÿ› ๏ธ Tools & Technologies +## Tech Stack | Component | Technology | Version | |-----------|------------|---------| @@ -86,1211 +10,182 @@ This file documents the AI agents, tools, and development workflow for the dance | Router | Chi | v5.2.5 | | Logging | Zerolog | v1.35.0 | | Configuration | Viper | v1.21.0 | -| Testing | Standard Library | - | -| Dependency Management | Go Modules | - | | Telemetry | OpenTelemetry | v1.43.0 | -| Tracing | Jaeger | Compatible | -## ๐Ÿ—บ๏ธ Project Structure +## Project Structure ``` dance-lessons-coach/ โ”œโ”€โ”€ adr/ # Architecture Decision Records -โ”‚ โ”œโ”€โ”€ README.md # ADR guidelines and index -โ”‚ โ”œโ”€โ”€ 0001-go-1.26.1-standard.md -โ”‚ โ”œโ”€โ”€ 0002-chi-router.md -โ”‚ โ”œโ”€โ”€ 0003-zerolog-logging.md -โ”‚ โ”œโ”€โ”€ 0004-interface-based-design.md -โ”‚ โ”œโ”€โ”€ 0005-graceful-shutdown.md -โ”‚ โ”œโ”€โ”€ 0006-configuration-management.md -โ”‚ โ”œโ”€โ”€ 0007-opentelemetry-integration.md -โ”‚ โ”œโ”€โ”€ 0008-bdd-testing.md -โ”‚ โ””โ”€โ”€ 0009-hybrid-testing-approach.md โ”œโ”€โ”€ cmd/ -โ”‚ โ”œโ”€โ”€ greet/ # CLI application -โ”‚ โ”‚ โ””โ”€โ”€ main.go -โ”‚ โ””โ”€โ”€ server/ # Web server -โ”‚ โ””โ”€โ”€ main.go +โ”‚ โ”œโ”€โ”€ greet/ # CLI application +โ”‚ โ””โ”€โ”€ server/ # Web server entry point โ”œโ”€โ”€ pkg/ -โ”‚ โ”œโ”€โ”€ config/ # Configuration management -โ”‚ โ”‚ โ””โ”€โ”€ config.go # Viper-based config -โ”‚ โ”œโ”€โ”€ greet/ # Core domain logic -โ”‚ โ”‚ โ”œโ”€โ”€ api_v1.go # API handlers -โ”‚ โ”‚ โ”œโ”€โ”€ greet.go # Service implementation -โ”‚ โ”‚ โ””โ”€โ”€ greet_test.go # Unit tests -โ”‚ โ”œโ”€โ”€ server/ # HTTP server -โ”‚ โ”‚ โ””โ”€โ”€ server.go -โ”‚ โ””โ”€โ”€ telemetry/ # OpenTelemetry instrumentation -โ”‚ โ””โ”€โ”€ telemetry.go -โ”œโ”€โ”€ go.mod # Dependencies -โ”œโ”€โ”€ go.sum # Dependency checksums -โ”œโ”€โ”€ config.yaml # Configuration file -โ”œโ”€โ”€ scripts/ # Server control and build scripts -โ”‚ โ”œโ”€โ”€ start-server.sh # Server lifecycle management -โ”‚ โ”œโ”€โ”€ build.sh # Binary compilation -โ”‚ โ””โ”€โ”€ test-opentelemetry.sh # OpenTelemetry testing -โ”œโ”€โ”€ README.md # User documentation -โ”œโ”€โ”€ AGENTS.md # This file -โ””โ”€โ”€ .gitignore # Ignore patterns +โ”‚ โ”œโ”€โ”€ config/ # Viper-based configuration +โ”‚ โ”œโ”€โ”€ greet/ # Core domain logic + API handlers +โ”‚ โ”œโ”€โ”€ server/ # HTTP server, routing, graceful shutdown +โ”‚ โ”œโ”€โ”€ telemetry/ # OpenTelemetry instrumentation +โ”‚ โ”œโ”€โ”€ user/ # User domain (auth, JWT, repository) +โ”‚ โ””โ”€โ”€ validation/ # Request validation +โ”œโ”€โ”€ scripts/ # Server lifecycle, build, test scripts +โ”œโ”€โ”€ config.yaml # Configuration file +โ””โ”€โ”€ config.example.yaml # Configuration template ``` -## ๐ŸŽฎ CLI Management - -### New Cobra CLI (Recommended) - -dance-lessons-coach now includes a modern CLI built with Cobra framework: +## Server Management ```bash -# Show help and available commands -./bin/dance-lessons-coach --help - -# Show version information -./bin/dance-lessons-coach version - -# Greet someone by name -./bin/dance-lessons-coach greet John - -# Start the server -./bin/dance-lessons-coach server -``` - -**Available Commands:** -- `version` - Print version information -- `server` - Start the dance-lessons-coach server -- `greet [name]` - Greet someone by name -- `help` - Built-in help system -- `completion` - Generate shell completion scripts - -**Server Command Flags:** -- `--config` - Config file path -- `--env` - Environment (dev, staging, prod) -- `--debug` - Enable debug logging - -### Version Information - -The server provides runtime version information: - -```bash -# Check version using new CLI -./bin/dance-lessons-coach version - -# Check version using server binary -./bin/server --version - -# Output: -dance-lessons-coach Version Information: - Version: 1.0.0 - Commit: abc1234 - Built: 2026-04-05T10:00:00+0000 - Go: go1.26.1 -``` - -### Using the Server Control Script - -A convenient shell script is provided for managing the server lifecycle: - -```bash -# Navigate to project directory -cd /Users/gabrielradureau/Work/Vibe/dance-lessons-coach - -# Start the server +# Start / stop / restart ./scripts/start-server.sh start +./scripts/start-server.sh stop +./scripts/start-server.sh restart -# Check server status +# Status and logs ./scripts/start-server.sh status - -# Test API endpoints -./scripts/start-server.sh test - -# View server logs ./scripts/start-server.sh logs -# Stop the server -./scripts/start-server.sh stop +# Test all API endpoints +./scripts/start-server.sh test ``` -**Server Control Script Commands:** -- `start` - Start the server in background with proper logging -- `stop` - Stop the server gracefully -- `restart` - Restart the server -- `status` - Check if server is running -- `logs` - Show recent server logs -- `test` - Test all API endpoints +## Configuration -### Manual Server Management +All settings can be provided via `config.yaml` or environment variables (`DLC_` prefix). -If you prefer manual control: - -```bash -# Navigate to project directory -cd /Users/gabrielradureau/Work/Vibe/dance-lessons-coach - -# Run server in background using control script -./scripts/start-server.sh start -``` - -**Expected output:** -``` -Server running on :8080 -[INF] Starting HTTP server on :8080 -[TRC] Registering greet routes -[TRC] Greet routes registered -``` - -**Features:** -- Context-aware server initialization -- Graceful shutdown handling -- Signal-based termination (SIGINT, SIGTERM) -- 30-second shutdown timeout -- Proper resource cleanup - -### Configuration Management - -The server supports flexible configuration through environment variables with the `DLC_` prefix: - -**Available Configuration Options:** - -| Option | Environment Variable | Default Value | Description | -|--------|---------------------|---------------|-------------| -| Host | `DLC_SERVER_HOST` | `0.0.0.0` | Server bind address | -| Port | `DLC_SERVER_PORT` | `8080` | Server listening port | -| Shutdown Timeout | `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown timeout | -| JSON Logging | `DLC_LOGGING_JSON` | `false` | Enable JSON format logging | -| Log Output | `DLC_LOGGING_OUTPUT` | `""` | Log output file path (empty for stderr) | - -**Usage Examples:** - -```bash -# Custom port -export DLC_SERVER_PORT=9090 -./scripts/start-server.sh start - -# Custom host and port -export DLC_SERVER_HOST="127.0.0.1" -export DLC_SERVER_PORT=8081 -./scripts/start-server.sh start - -# Custom shutdown timeout -export DLC_SHUTDOWN_TIMEOUT=45s -./scripts/start-server.sh start - -# Enable JSON logging -export DLC_LOGGING_JSON=true -./scripts/start-server.sh start - -# Log to file -export DLC_LOGGING_OUTPUT="server.log" -./scripts/start-server.sh start - -# Combined: JSON logging to file -export DLC_LOGGING_JSON=true -export DLC_LOGGING_OUTPUT="server.json.log" -./scripts/start-server.sh start -``` - -**Configuration File Support:** - -A `config.example.yaml` file is provided as a template. By default, the application looks for `config.yaml` in the current working directory. - -To specify a custom config file path, set the `DLC_CONFIG_FILE` environment variable: - -```bash -DLC_CONFIG_FILE="/path/to/config.yaml" go run ./cmd/server -``` - -Example `config.yaml`: +| Option | Env var | Default | Description | +|--------|---------|---------|-------------| +| Host | `DLC_SERVER_HOST` | `0.0.0.0` | Bind address | +| Port | `DLC_SERVER_PORT` | `8080` | Listening port | +| Shutdown timeout | `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown window | +| JSON logging | `DLC_LOGGING_JSON` | `false` | Structured JSON output | +| Log output | `DLC_LOGGING_OUTPUT` | `""` | File path (empty = stderr) | +| API v2 | `DLC_API_V2_ENABLED` | `false` | Enable `/api/v2` routes | +| Config file | `DLC_CONFIG_FILE` | `./config.yaml` | Override config path | +Minimal `config.yaml`: ```yaml server: host: "0.0.0.0" port: 8080 - shutdown: timeout: 30s - logging: json: false ``` -**Configuration Loading:** -- **File-based configuration** takes highest precedence -- **Environment variables** override defaults but are overridden by config file -- **Default values** are used when no other configuration is provided -- All configuration is validated on startup -- Invalid configurations cause server startup failure -- Configuration values and source are logged at startup +**Priority**: env var > config file > default. -**Verification:** -```bash -# Test with custom configuration -DLC_SERVER_PORT=9090 DLC_SERVER_HOST="127.0.0.1" ./scripts/start-server.sh start +## API Endpoints -# Verify it's running on the custom port -curl http://127.0.0.1:9090/api/health -# Expected: {"status":"healthy"} -``` - -### Checking Server Status +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/health` | Liveness โ€” always `{"status":"healthy"}` | +| GET | `/api/ready` | Readiness โ€” 200 when ready, 503 during shutdown | +| GET | `/api/version` | Version info (`?format=plain\|full\|json`) | +| GET | `/api/v1/greet/` | Default greeting | +| GET | `/api/v1/greet/{name}` | Personalized greeting | +| POST | `/api/v2/greet` | V2 greeting with validation (feature-flagged) | +| GET | `/swagger/` | Swagger UI | +| GET | `/swagger/doc.json` | OpenAPI spec | ```bash -# Check health endpoint -curl -s http://localhost:8080/api/health - -# Check readiness endpoint -curl -s http://localhost:8080/api/ready -``` - -**Expected responses:** -- Health: `{"status":"healthy"}` -- Readiness (normal): `{"ready":true}` -- Readiness (during shutdown): `{"ready":false}` with HTTP 503 - -**Endpoint Differences:** -- **Health endpoint** (`/api/health`): Indicates if the application is running and functional -- **Readiness endpoint** (`/api/ready`): Indicates if the application is ready to accept traffic - -**Use Cases:** -- **Health**: Used by load balancers to check if the app is alive -- **Readiness**: Used by Kubernetes/service meshes to determine if the app can accept new requests - -**During Graceful Shutdown:** -- Health endpoint continues to return `{"status":"healthy"}` -- Readiness endpoint returns `{"ready":false}` with HTTP 503 Service Unavailable -- This allows existing requests to complete while preventing new requests - -### Stopping the Server - -To stop the server gracefully: -```bash -# Send SIGTERM for graceful shutdown -kill -TERM $(lsof -ti :8080) - -# Or send SIGINT (Ctrl+C equivalent) -pkill -INT -f "go run" -``` - -**Graceful shutdown process:** -1. Server receives termination signal -2. Logs shutdown message -3. Stops accepting new connections -4. Waits up to 30 seconds for active requests to complete -5. Closes all connections cleanly -6. Exits with proper cleanup - -For force stop (if graceful shutdown hangs): -```bash -kill -9 $(lsof -ti :8080) -``` - -**Verification:** -```bash -curl -s http://localhost:8080/api/health -# Should return connection refused -``` - -## ๐ŸŒ API Endpoints - -### Base URL -``` -http://localhost:8080 -``` - -### OpenAPI Documentation - -**Swagger UI:** `http://localhost:8080/swagger/` -**OpenAPI Spec:** `http://localhost:8080/swagger/doc.json` - -The API provides interactive documentation using Swagger UI with complete OpenAPI 2.0 specification. All endpoints, request/response models, and validation rules are documented using a **hierarchical tagging system**. - -**Features:** -- Interactive API exploration with hierarchical organization -- Try-it-out functionality for all endpoints -- Model schemas with examples -- Response examples with validation rules -- **Hierarchical tag structure** for better navigation - -**Generation:** Documentation is auto-generated from code annotations using [swaggo/swag](https://github.com/swaggo/swag) with the command: - -```bash -go generate ./pkg/server/ -``` - -**Tag Organization:** -- `API/v1/Greeting` - Version 1 greeting endpoints -- `API/v2/Greeting` - Version 2 greeting endpoints -- `System/Health` - Health and readiness endpoints - -**Hierarchical Benefits:** -- Clear separation between API domains (API vs System) -- Version organization within each domain -- Natural hierarchy in Swagger UI -- Scalable for future API growth - -```bash -# Generate documentation -go generate ./pkg/server/ -``` - -**Embedded Documentation:** The OpenAPI spec is embedded in the binary using Go's `//go:embed` directive for single-binary deployment. - ---- - -### Health Check - -### Health Check -```http -GET /api/health -``` - -**Response:** -```json -{"status":"healthy"} -``` - -### Readiness Check -```http -GET /api/ready -``` - -**Responses:** -- Normal operation: `{"ready":true}` (HTTP 200) -- During shutdown: `{"ready":false}` (HTTP 503 Service Unavailable) - -**Purpose:** Indicates whether the server is ready to accept new requests. Returns false during graceful shutdown to allow existing requests to complete while preventing new ones. - -### Greet Service -```http -GET /api/v1/greet/ -GET /api/v1/greet/{name} -``` - -**Examples:** -```bash -# Default greeting -curl http://localhost:8080/api/v1/greet/ -# Response: {"message":"Hello world!"} - -# Personalized greeting -curl http://localhost:8080/api/v1/greet/John -# Response: {"message":"Hello John!"} - -# Another example +curl http://localhost:8080/api/health +curl http://localhost:8080/api/ready curl http://localhost:8080/api/v1/greet/Alice -# Response: {"message":"Hello Alice!"} -``` - -### Greet Service v2 (Feature Flag Enabled) -```http -POST /api/v2/greet -``` - -**Request Body:** -```json -{ - "name": "John" -} -``` - -**Examples:** -```bash -# Valid request curl -X POST http://localhost:8080/api/v2/greet \ - -H "Content-Type: application/json" \ - -d '{"name":"John"}' -# Response: {"message":"Hello my friend John!"} - -# Empty name (valid, returns default) -curl -X POST http://localhost:8080/api/v2/greet \ - -H "Content-Type: application/json" \ - -d '{"name":""}' -# Response: {"message":"Hello my friend!"} - -# Missing name field (valid, returns default) -curl -X POST http://localhost:8080/api/v2/greet \ - -H "Content-Type: application/json" \ - -d '{}' -# Response: {"message":"Hello my friend!"} - -# Name too long (validation error) -curl -X POST http://localhost:8080/api/v2/greet \ - -H "Content-Type: application/json" \ - -d '{"name":"ThisNameIsWayTooLongAndShouldFailValidationBecauseItExceedsTheMaximumAllowedLengthOf100Characters!!!!"}' -# Response: {"error":"validation_failed","message":"Invalid request data","details":[{"message":"Name failed validation for 'max' (parameter: 100)"}]}' + -H "Content-Type: application/json" -d '{"name":"Alice"}' ``` -**Validation Rules:** -- `name`: Maximum length 100 characters (optional field) - -**Feature Flag:** Enable with `DLC_API_V2_ENABLED=true` or in config file with `api.v2_enabled: true` - -## ๐Ÿ”— OpenTelemetry & Jaeger Integration - -The application supports OpenTelemetry for distributed tracing with Jaeger compatibility. - -### Configuration - -Enable OpenTelemetry in your `config.yaml`: - -```yaml -telemetry: - enabled: true - otlp_endpoint: "localhost:4317" - service_name: "dance-lessons-coach" - insecure: true - sampler: - type: "parentbased_always_on" - ratio: 1.0 -``` - -Or via environment variables: +## Testing ```bash -export DLC_TELEMETRY_ENABLED=true -export DLC_TELEMETRY_OTLP_ENDPOINT="localhost:4317" -export DLC_TELEMETRY_SERVICE_NAME="dance-lessons-coach" -export DLC_TELEMETRY_INSECURE=true -export DLC_TELEMETRY_SAMPLER_TYPE="parentbased_always_on" -export DLC_TELEMETRY_SAMPLER_RATIO=1.0 -``` +# Unit + integration tests +go test ./... +go test -v ./... -### Testing with Jaeger +# Graceful shutdown + JSON logging validation +./scripts/test-graceful-shutdown.sh -1. **Start Jaeger in Docker:** -```bash -docker run -d --name jaeger \ - -e COLLECTOR_OTLP_ENABLED=true \ - -p 16686:16686 \ - -p 4317:4317 \ - jaegertracing/all-in-one:latest -``` - -2. **Start the server with OpenTelemetry enabled:** -```bash -# Using config file -./scripts/start-server.sh start - -# Or with environment variables -DLC_TELEMETRY_ENABLED=true ./scripts/start-server.sh start -``` - -3. **Make API requests:** -```bash -curl http://localhost:8080/api/v1/greet/John -``` - -4. **View traces in Jaeger UI:** -Open http://localhost:16686 and select the "dance-lessons-coach" service. - -### Sampler Types - -- `always_on`: Sample all traces -- `always_off`: Sample no traces -- `traceidratio`: Sample based on trace ID ratio -- `parentbased_always_on`: Sample based on parent span (always on) -- `parentbased_always_off`: Sample based on parent span (always off) -- `parentbased_traceidratio`: Sample based on parent span with ratio - -### Testing Script - -Use the provided test script: -```bash +# OpenTelemetry end-to-end ./scripts/test-opentelemetry.sh ``` -This script: -1. Starts Jaeger container -2. Starts the server with OpenTelemetry -3. Makes test API calls -4. Shows Jaeger UI URL -5. Cleans up on exit +**Note:** Do not call `go generate` unless editing API endpoint annotations. +When needed: `go generate ./pkg/server/` -## ๐Ÿ”ง Development Workflow - -### 1. Check Server Status -```bash -curl -s http://localhost:8080/api/health -``` - -### 2. Start Development Server -```bash -cd /Users/gabrielradureau/Work/Vibe/dance-lessons-coach -./scripts/start-server.sh start -``` - -### 3. Test API Endpoints -```bash -# Test all endpoints as shown above -curl http://localhost:8080/api/v1/greet/YourName -``` - -### 4. Run Tests -```bash -# Run all tests -go test ./... - -# Run specific package -go test ./pkg/greet/ -``` - -### 5. Build Binaries - -The project uses a build script to compile binaries into the `bin/` directory: +## Build ```bash -# Build both server and greet binaries ./scripts/build.sh - -# This creates: -# - ./bin/server - The web server binary -# - ./bin/greet - The CLI greeting tool -``` - -**Binary Usage:** -```bash -# Run the server -./bin/server - -# Use the greet CLI -./bin/greet # Output: Hello world! -./bin/greet John # Output: Hello John! -``` - -**The `bin/` directory is gitignored** to prevent binary files from being committed to the repository. - -### 6. Make Changes -- Edit source files in `pkg/` or `cmd/` -- Follow existing patterns and interfaces -- Add tests for new functionality - -### 7. Stop and Restart -```bash -./scripts/start-server.sh restart -``` - -## ๐Ÿงช Testing - -### Unit Tests -```bash -# Run all tests -go test ./... - -# Run with verbose output -go test -v ./... - -# Run specific test -go test ./pkg/greet/ -run TestService_Greet -``` - -### CLI Testing -```bash -# Default greeting -go run ./cmd/greet -# Output: Hello world! - -# Personalized greeting -go run ./cmd/greet John -# Output: Hello John! -``` - -### API Testing -```bash -# Health check -curl http://localhost:8080/api/health - -# Greet endpoints -curl http://localhost:8080/api/v1/greet/John -curl http://localhost:8080/api/v1/greet/ -``` - -## ๐Ÿ“ Architecture Decisions - -### Interface-Based Design -```go -type Greeter interface { - Greet(ctx context.Context, name string) string -} -``` - -**Benefits:** -- Easy mocking for tests -- Dependency injection -- Multiple implementations -- Clear contracts - -### Context-Aware Services -```go -func (s *Service) Greet(ctx context.Context, name string) string { - log.Trace().Ctx(ctx).Str("name", name).Msg("Greet function called") - // ... -} -``` - -**Benefits:** -- Request tracing -- Cancellation support -- Deadline propagation -- Metadata passing - -### Server Context Management -```go -// Root context with cancellation -ctx, cancel := context.WithCancel(context.Background()) -defer cancel() - -// Server context with graceful shutdown -serverCtx, serverStop := context.WithCancel(ctx) - -// HTTP server with context-aware shutdown -srv := &http.Server{ - Addr: ":8080", - Handler: server.Router(), -} - -// Graceful shutdown with timeout -shutdownCtx, shutdownCancel := context.WithTimeout( - context.Background(), - 30*time.Second -) -defer shutdownCancel() - -if err := srv.Shutdown(shutdownCtx); err != nil { - log.Error().Err(err).Msg("Server shutdown failed") -} -``` - -**Benefits:** -- Graceful shutdown handling -- Signal-based termination (SIGINT, SIGTERM) -- 30-second timeout for active connections -- Proper resource cleanup -- Context propagation throughout server lifecycle - -### Zerolog Logging -```go -zerolog.SetGlobalLevel(zerolog.TraceLevel) -log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) -``` - -**Benefits:** -- High performance -- Structured logging -- Trace level detail -- Color output - -### Versioned API -```go -router.Route("/api/v1", func(r chi.Router) { - // v1 endpoints -}) -``` - -**Benefits:** -- Backward compatibility -- Clear versioning -- Easy migration -- Parallel versions - -## ๐Ÿ” Troubleshooting - -### Port Already in Use -```bash -# Find and kill process using port 8080 -kill -TERM $(lsof -ti :8080) -``` - -### Server Not Responding -```bash -# Check if running -curl -s http://localhost:8080/api/health - -# Restart server using control script -./scripts/start-server.sh restart -``` - -### Dependency Issues -```bash -# Clean and rebuild -go mod tidy -go build ./... -``` - -### Tests Failing -```bash -# Run with verbose output -go test -v ./... - -# Check specific test -go test ./pkg/greet/ -run TestName -``` - -## ๐Ÿ“š Code Examples - -### Adding New API Endpoint -```go -// 1. Add to interface -func (h *apiV1GreetHandler) RegisterRoutes(router chi.Router) { - router.Get("/", h.handleGreetQuery) - router.Get("/{name}", h.handleGreetPath) - router.Post("/custom", h.handleCustomGreet) // New endpoint -} - -// 2. Implement handler -func (h *apiV1GreetHandler) handleCustomGreet(w http.ResponseWriter, r *http.Request) { - // Parse request - // Call service - // Return JSON response -} -``` - -### Adding Logging -```go -// Trace level logging -log.Trace().Ctx(ctx).Str("key", "value").Msg("message") - -// Info level -log.Info().Msg("Important event") - -// Error level -log.Error().Err(err).Msg("Error occurred") -``` - -### Using Context -```go -// Pass context through calls -func handler(w http.ResponseWriter, r *http.Request) { - result := service.Greet(r.Context(), "John") - // ... -} - -// Create context with values -ctx := context.WithValue(r.Context(), "key", "value") - -// Create context with timeout -ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) -defer cancel() -``` - -## ๐ŸŽ“ Best Practices - -### Code Organization -- Keep handlers thin, move logic to services -- Use interfaces for dependencies -- Separate route registration from handlers -- Group related functionality - -### Error Handling -- Return proper HTTP status codes -- Log errors with context -- Don't expose internal errors to clients -- Use structured error responses - -### Performance -- Use Zerolog's Trace level sparingly in production -- Avoid allocations in hot paths -- Use context timeouts for external calls -- Batch database operations - -### Testing -- Test interfaces, not implementations -- Use table-driven tests -- Test error cases -- Mock dependencies - -## ๐Ÿ“ˆ Future Enhancements - -### Potential Features -- [ ] Database integration -- [ ] Authentication/Authorization -- [ ] Rate limiting -- [ ] Metrics and monitoring -- [ ] Docker containerization -- โœ… CI/CD pipeline ([ADR-0016](adr/0016-ci-cd-pipeline-design.md), [ADR-0017](adr/0017-trunk-based-development-workflow.md)) -- [ ] Configuration hot reload -- [ ] Circuit breakers - -### Architectural Improvements -- [ ] Request validation middleware -- โœ… OpenAPI/Swagger documentation with embedded spec -- [ ] Enhanced OpenTelemetry instrumentation -- [ ] Metrics collection and visualization -- [ ] Health check improvements -- [ ] Configuration validation enhancements - -### Completed Features -- โœ… Graceful shutdown with readiness endpoint -- โœ… OpenTelemetry integration with Jaeger support -- โœ… Configuration management with Viper -- โœ… Comprehensive logging with Zerolog -- โœ… Build system with binary output -- โœ… Complete documentation with commit conventions -- โœ… Version management with runtime info - -## ๐Ÿ“ฆ Version Management - -dance-lessons-coach uses a comprehensive version management system based on Semantic Versioning 2.0.0. - -### Version Information - -**Current Version:** `1.0.0` (see VERSION file) -**Version Format:** `MAJOR.MINOR.PATCH-PRERELEASE` -**SemVer Compliance:** โœ… Yes - -### Version Files - -```bash -# VERSION file - Source of truth -MAJOR=1 -MINOR=0 -PATCH=0 -PRERELEASE="" - -# Auto-generated fields -BUILD_DATE="" -GIT_COMMIT="" -GIT_TAG="" -``` - -### Version Management Commands - -#### Check Version -```bash -# From VERSION file -source VERSION && echo "$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}" - -# From built binary +# Produces: ./bin/server ./bin/greet ./bin/server --version - -# From running server (future) -curl http://localhost:8080/api/version ``` -#### Bump Version +Build injects version, commit, and date via `-ldflags`. + +## Graceful Shutdown + +On `SIGTERM` / `SIGINT`: +1. Readiness context is cancelled โ†’ `/api/ready` returns 503. +2. 1-second propagation window (load balancer drains). +3. `srv.Shutdown()` waits up to `shutdown.timeout` for active requests. +4. Process exits cleanly. + +Health endpoint stays 200 throughout; readiness endpoint goes 503 immediately on signal. + +## OpenTelemetry / Jaeger + +Enable in config or via env: ```bash -# Patch version (bug fixes) -./scripts/version-bump.sh patch # 1.0.0 โ†’ 1.0.1 - -# Minor version (new features) -./scripts/version-bump.sh minor # 1.0.1 โ†’ 1.1.0 - -# Major version (breaking changes) -./scripts/version-bump.sh major # 1.1.0 โ†’ 2.0.0 - -# Pre-release version -./scripts/version-bump.sh pre # 2.0.0 โ†’ 2.0.0-alpha.1 - -# Release from pre-release -./scripts/version-bump.sh release # 2.0.0-alpha.1 โ†’ 2.0.0 +export DLC_TELEMETRY_ENABLED=true +export DLC_TELEMETRY_OTLP_ENDPOINT="localhost:4317" ``` -#### Build with Version +Quick Jaeger setup: ```bash -# Development build -./scripts/build-with-version.sh bin/server-dev - -# Release build -go build -o bin/server \ - -ldflags="\ - -X 'dance-lessons-coach/pkg/version.Version=1.0.0' \ - -X 'dance-lessons-coach/pkg/version.Commit=$(git rev-parse --short HEAD)' \ - -X 'dance-lessons-coach/pkg/version.Date=$(date +%Y-%m-%dT%H:%M:%S%z)' \ - " \ - ./cmd/server +docker run -d --name jaeger \ + -e COLLECTOR_OTLP_ENABLED=true \ + -p 16686:16686 -p 4317:4317 \ + jaegertracing/all-in-one:latest ``` -### Semantic Versioning Rules +## Architecture Decision Records -| Part | When to Increment | Examples | -|------|-------------------|----------| -| **MAJOR** | Breaking changes, major features | Database schema changes, API breaking changes | -| **MINOR** | Backwards-compatible features | New API endpoints, new functionality | -| **PATCH** | Backwards-compatible fixes | Bug fixes, performance improvements | -| **PRERELEASE** | Pre-release versions | alpha.1, beta.2, rc.1 | +| ADR | Decision | +|-----|----------| +| [0001](adr/0001-go-1.26.1-standard.md) | Go 1.26.1 | +| [0002](adr/0002-chi-router.md) | Chi router | +| [0003](adr/0003-zerolog-logging.md) | Zerolog | +| [0004](adr/0004-interface-based-design.md) | Interface-based design | +| [0005](adr/0005-graceful-shutdown.md) | Graceful shutdown | +| [0006](adr/0006-configuration-management.md) | Viper configuration | +| [0007](adr/0007-opentelemetry-integration.md) | OpenTelemetry | +| [0008](adr/0008-bdd-testing.md) | BDD with Godog | +| [0009](adr/0009-hybrid-testing-approach.md) | Hybrid testing strategy | -### Release Lifecycle +Add a new ADR: copy an existing file, edit it, update `adr/README.md`. -#### Development Workflow -```mermaid -graph LR - A[Feature Branch] --> B[PR to main] - B --> C[Auto-build with dev version] - C --> D[Deploy to dev/staging] +## Commit Conventions + +[Conventional Commits](https://www.conventionalcommits.org) with optional [gitmoji](https://gitmoji.dev): + +| Emoji | Type | When | +|-------|------|------| +| โœจ | `feat` | New feature | +| ๐Ÿ› | `fix` | Bug fix | +| ๐Ÿ“ | `docs` | Documentation | +| ๐ŸŽจ | `style` | Formatting only | +| โ™ป๏ธ | `refactor` | Structural change | +| ๐Ÿš€ | `perf` | Performance | +| ๐Ÿ”’ | `security` | Security fix | +| ๐Ÿ“ฆ | `chore` | Dependencies / build | +| ๐Ÿงช | `test` | Tests | +| ๐Ÿค– | `ci` | CI/CD | +| ๐Ÿ”ฅ | `remove` | Delete code/files | + +Examples: ``` - -#### Release Workflow -```mermaid -graph LR - A[Bump version] --> B[Update CHANGELOG] - B --> C[Create git tag] - C --> D[Build release binaries] - D --> E[Push to GitHub Releases] - E --> F[Deploy to production] +feat: add JWT authentication middleware +fix: ensure first log line is JSON when json logging is enabled +docs: rewrite AGENTS.md for clarity ``` - -### Version Package - -The `pkg/version` package provides runtime access to version information: - -```go -package main - -import ( - "dance-lessons-coach/pkg/version" - "fmt" -) - -func main() { - fmt.Println("Version:", version.Short()) - fmt.Println("Full info:", version.Full()) - fmt.Println("Info:", version.Info()) -} -``` - -**Functions:** -- `version.Short()` - Returns version number (e.g., "1.0.0") -- `version.Info()` - Returns short info string -- `version.Full()` - Returns detailed version information - -### Implementation Status - -| Component | Status | Notes | -|-----------|--------|-------| -| Version Package | โœ… Complete | Runtime version access | -| VERSION File | โœ… Complete | Source of truth | -| Build Script | โœ… Complete | Version injection | -| Version Command | โœ… Complete | `--version` flag | -| Version Bump Script | ๐ŸŸก Partial | Basic functionality | -| Git Tag Integration | ๐ŸŸก Planned | Release automation | -| CI/CD Integration | โœ… Complete | Pipeline automation with local testing | -| Release Scripts | ๐ŸŸก Planned | Full release lifecycle | - -### Future Enhancements - -1. **Automated Changelog Generation** -2. **Git Tag Automation** -3. **CI/CD Pipeline Integration** -4. **Version API Endpoint** (`GET /api/version`) -5. **Dependency Version Tracking** -6. **Security Vulnerability Alerts** - -See [ADR 0014](adr/0014-version-management-lifecycle.md) for complete version management architecture. - -## ๐Ÿ“ Architecture Decision Records - -The project maintains comprehensive Architecture Decision Records (ADRs) that document all major architectural choices. See the [adr/](adr/) directory for complete documentation. - -**Key Decisions**: -- **Language**: Go 1.26.1 ([ADR-0001](adr/0001-go-1.26.1-standard.md)) -- **Routing**: Chi router ([ADR-0002](adr/0002-chi-router.md)) -- **Logging**: Zerolog ([ADR-0003](adr/0003-zerolog-logging.md)) -- **Design**: Interface-based ([ADR-0004](adr/0004-interface-based-design.md)) -- **Shutdown**: Graceful with readiness ([ADR-0005](adr/0005-graceful-shutdown.md)) -- **Config**: Viper ([ADR-0006](adr/0006-configuration-management.md)) -- **Observability**: OpenTelemetry ([ADR-0007](adr/0007-opentelemetry-integration.md)) -- **Testing**: BDD with Godog ([ADR-0008](adr/0008-bdd-testing.md)) -- **CI/CD**: Trunk-based development ([ADR-0017](adr/0017-trunk-based-development-workflow.md)) -- **Testing**: BDD with Godog ([ADR-0008](adr/0008-bdd-testing.md)) -- **Strategy**: Hybrid testing ([ADR-0009](adr/0009-hybrid-testing-approach.md)) - -**Adding New ADRs**: -```bash -# 1. Copy template -cp adr/0001-go-1.26.1-standard.md adr/0010-new-decision.md - -# 2. Edit the new ADR -# 3. Update adr/README.md -# 4. Reference in documentation -``` - -## ๐Ÿ“ Changelog - -### 2026-04-05 - Architecture Documentation -- โœ… Added comprehensive ADR directory with 9 decision records -- โœ… Enhanced Zerolog vs Zap analysis in logging ADR -- โœ… Updated README.md and AGENTS.md with ADR references -- โœ… Documented hybrid testing approach -- โœ… Added BDD testing decision record - -### 2026-04-04 - Observability & Testing -- โœ… OpenTelemetry integration with Jaeger -- โœ… Middleware-only tracing approach -- โœ… Comprehensive telemetry configuration -- โœ… BDD testing framework setup -- โœ… Hybrid testing strategy documentation - -### 2026-04-03 - Production Readiness -- โœ… Graceful shutdown with readiness endpoints -- โœ… Configuration management with Viper -- โœ… JSON logging configuration -- โœ… File output logging support -- โœ… Comprehensive error handling - -### 2026-04-02 - Web API Foundation -- โœ… Chi router integration -- โœ… Versioned API endpoints (`/api/v1`) -- โœ… Health and readiness endpoints -- โœ… JSON responses with proper headers -- โœ… Interface-based design patterns - -### 2026-04-01 - Project Foundation -- โœ… Go 1.26.1 environment setup -- โœ… Project structure with `cmd/` and `pkg/` -- โœ… Core Greet service implementation -- โœ… CLI interface -- โœ… Unit tests with table-driven approach - -## ๐Ÿค– AI Agent Information - -**Agent:** Mistral Vibe CLI Agent -**Version:** devstral-2 -**Role:** Development Assistant -**Capabilities:** -- Code generation and refactoring -- Test creation -- Documentation -- Architecture guidance -- Best practices enforcement - -## ๐Ÿ“‹ Quick Reference - -### Common Commands -```bash -# Start server -./scripts/start-server.sh start - -# Test API -curl http://localhost:8080/api/v1/greet/John - -# Run tests -go test ./... - -# Stop server -pkill -f "go" - -# CLI usage -go run ./cmd/greet John -``` - -### Project Structure -``` -cmd/ # Entry points -pkg/ # Core logic - greet/ # Domain services - server/ # HTTP server -go.mod # Dependencies -README.md # User docs -AGENTS.md # This file -``` - -### Key Interfaces -```go -type Greeter interface { - Greet(ctx context.Context, name string) string -} - -type ApiV1Greet interface { - RegisterRoutes(router chi.Router) -} -``` - -## ๐Ÿ“ Commit Conventions - -**Follow Conventional Commits for clear communication:** - -**Core Types:** -- `feat`: New user-facing feature -- `fix`: Bug fix for users -- `docs`: Documentation changes -- `style`: Code formatting (no functional change) -- `refactor`: Code structure changes -- `perf`: Performance improvements -- `test`: Test additions/corrections -- `chore`: Build process, dependencies, maintenance - -**Examples:** -```bash -# New feature -git commit -m "feat: add user authentication" - -# Bug fix -git commit -m "fix: prevent race condition in cache" - -# Documentation -git commit -m "docs: add API endpoint documentation" - -# Maintenance -git commit -m "chore: update dependencies" - -# Refactoring -git commit -m "refactor: extract user service from controller" -``` - -**Optional Emoji Support:** -- Use [gitmoji](https://gitmoji.dev) for visual commit messages -- Example: `git commit -m "โœจ feat: add new API endpoint"` - -**Common Gitmoji Reference:** - -| Emoji | Code | Type | Description | -|-------|------|------|-------------| -| โœจ | `:sparkles:` | feat | New feature | -| ๐Ÿ› | `:bug:` | fix | Bug fix | -| ๐Ÿ“ | `:memo:` | docs | Documentation | -| ๐ŸŽจ | `:art:` | style | Code formatting | -| ๐Ÿ”ง | `:wrench:` | chore | Build/config changes | -| โ™ป๏ธ | `:recycle:` | refactor | Code refactoring | -| ๐Ÿš€ | `:rocket:` | perf | Performance improvements | -| ๐Ÿ”’ | `:lock:` | security | Security fixes | -| ๐Ÿ“ฆ | `:package:` | dependencies | Dependency changes | -| ๐Ÿ”ฅ | `:fire:` | remove | Remove code/files | -| ๐Ÿง | `:penguin:` | linux | Linux-specific changes | -| ๐ŸŽ | `:apple:` | macos | macOS-specific changes | -| ๐ŸชŸ | `:window:` | windows | Windows-specific changes | -| ๐Ÿค– | `:robot:` | ci | CI/CD changes | -| ๐Ÿงช | `:test_tube:` | test | Tests | -| ๐Ÿ“ˆ | `:chart_with_upwards_trend:` | analytics | Analytics/SEO | -| ๐ŸŒ | `:globe_with_meridians:` | i18n | Internationalization | -| โšก | `:zap:` | performance | Performance improvements | - -**Benefits:** -- Clear communication of change types -- Better git history readability -- Tool compatibility (agent-tasks generators, etc.) -- Consistent project history -- Visual scanning of commit history - -## ๐Ÿ“ž Support - -For issues or questions: -1. Check this documentation -2. Review test cases -3. Examine existing implementations -4. Consult Go and Chi documentation -5. Ask the AI agent for guidance - -This documentation provides a complete guide to developing, testing, and maintaining the dance-lessons-coach project using the established patterns and best practices. -## ๐Ÿ“‹ BDD Feature Structure - -All user stories and BDD features follow the structure defined in ADR-0019: - - - -See [ADR-0019](adr/0019-bdd-feature-structure.md) for complete details. - -## ๐Ÿ—‘๏ธ Retention Policy - -### ADRs -- Review quarterly -- Deprecate unused features with `Status: Deprecated` header -- Remove after 6 months of deprecation - -### Documentation -- Archive completed projects to `archive/` directory -- Remove archived documentation after 12 months - -### Scripts -- Move unused scripts to `scripts/deprecated/` -- Remove deprecated scripts after 6 months - -### Skills -- Move unused skills to `.vibe/skills/deprecated/` -- Remove deprecated skills after 6 months diff --git a/README.md b/README.md index a1f03db..fe9c9f4 100644 --- a/README.md +++ b/README.md @@ -1,421 +1,98 @@ # dance-lessons-coach -[![Build Status](https://gitea.arcodange.fr/arcodange/dance-lessons-coach/actions/workflows/ci-cd.yaml/badge.svg)](https://gitea.arcodange.fr/arcodange/dance-lessons-coach/actions/workflows/ci-cd.yaml/badge.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/arcodange/dance-lessons-coach)](https://goreportcard.com/report/github.com/arcodange/dance-lessons-coach) +[![Build Status](https://gitea.arcodange.fr/arcodange/dance-lessons-coach/actions/workflows/ci-cd.yaml/badge.svg)](https://gitea.arcodange.fr/arcodange/dance-lessons-coach/actions/workflows/ci-cd.yaml) [![Version](https://img.shields.io/badge/version-1.4.0-blue.svg)](https://gitea.arcodange.fr/arcodange/dance-lessons-coach/releases) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![BDD Coverage](https://img.shields.io/badge/BDD_Coverage-51.1%%-red?style=flat-square)](https://gitea.arcodange.lab/arcodange/dance-lessons-coach) -[![UNIT Coverage](https://img.shields.io/badge/UNIT_Coverage-8.9%%-red?style=flat-square)](https://gitea.arcodange.lab/arcodange/dance-lessons-coach) -A Go project demonstrating idiomatic package structure, CLI implementation, and JSON API with Chi router. -======= +Go web service demonstrating idiomatic package structure, versioned JSON API, and production-ready features. ## Features -- Greet function with default behavior -- Command-line interface -- JSON API with versioned endpoints -- Chi router integration -- Zerolog for high-performance logging -- Viper for configuration management -- Graceful shutdown with context -- Readiness endpoint for Kubernetes/service mesh integration -- OpenTelemetry integration with Jaeger support -- OpenAPI/Swagger documentation -- Unit tests -- Go 1.26.1 compatible +- Versioned JSON API (`/api/v1`, `/api/v2`) +- Chi router with graceful shutdown +- Zerolog structured logging (console and JSON modes) +- Viper configuration (file + env vars) +- Readiness endpoint for Kubernetes / service mesh +- OpenTelemetry / Jaeger distributed tracing +- OpenAPI / Swagger UI (embedded in binary) +- PostgreSQL user service with JWT auth +- BDD + unit tests -## Installation +## Quick Start ```bash -# Clone the repository git clone https://gitea.arcodange.lab/arcodange/dance-lessons-coach.git cd dance-lessons-coach - -# Build all binaries -./scripts/build.sh - -# Use the new Cobra CLI -./bin/dance-lessons-coach --help - -# Or use the legacy greet CLI -go run ./cmd/greet +./scripts/build.sh # produces ./bin/server and ./bin/greet +./scripts/start-server.sh start ``` -## CI/CD Pipeline - -dance-lessons-coach features an optimized CI/CD pipeline using GitHub Actions with container/services architecture: - -### Key Features -- โœ… **Container-based execution**: All steps run in pre-built Docker cache images -- โœ… **Service-based PostgreSQL**: Automatic database service provisioning -- โœ… **Smart caching**: Dependency-aware cache invalidation -- โœ… **Multi-platform**: Compatible with Gitea, GitHub, and GitLab -- โœ… **Fast execution**: No Docker Compose overhead -- โœ… **Reliable testing**: Full database connectivity with proper environment setup - -### Architecture - -The pipeline uses GitHub Actions' native `container` and `services` directives instead of Docker Compose: - -```yaml -jobs: - ci-pipeline: - container: - image: gitea.arcodange.lab/arcodange/dance-lessons-coach-build-cache:${{ needs.build-cache.outputs.deps_hash }} - - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: dance_lessons_coach_bdd_test -``` - -### Benefits - -1. **Performance**: Direct container execution without compose overhead -2. **Reliability**: Service containers managed by GitHub Actions -3. **Simplicity**: Cleaner workflow definition -4. **Portability**: Works across CI platforms -5. **Caching**: Intelligent dependency-based cache rebuilding - -### Workflow Steps - -1. **Build Cache**: Creates Docker image with Go tools and dependencies -2. **CI Pipeline**: Runs tests, builds binaries, and generates documentation -3. **Database Tests**: Connects to PostgreSQL service container -4. **Coverage Reporting**: Updates coverage badges automatically -5. **Artifact Publishing**: Builds and pushes Docker images (main branch only) - -### Environment Configuration - -The pipeline automatically sets up database environment variables: - ```bash -echo "DLC_DATABASE_HOST=postgres" >> $GITHUB_ENV -echo "DLC_DATABASE_PORT=5432" >> $GITHUB_ENV -echo "DLC_DATABASE_USER=postgres" >> $GITHUB_ENV -echo "DLC_DATABASE_PASSWORD=postgres" >> $GITHUB_ENV -echo "DLC_DATABASE_NAME=dance_lessons_coach_bdd_test" >> $GITHUB_ENV -echo "DLC_DATABASE_SSL_MODE=disable" >> $GITHUB_ENV +curl http://localhost:8080/api/health +curl http://localhost:8080/api/v1/greet/Alice ``` -### Status +Stop: `./scripts/start-server.sh stop` -[![Build Status](https://gitea.arcodange.fr/api/badges/arcodange/dance-lessons-coach/status)](https://gitea.arcodange.fr/arcodange/dance-lessons-coach) +## Greet CLI -======= -- โœ… **Linting**: Code quality checks with `go fmt` and `go vet` -- โœ… **Version Management**: Automatic version detection -- โœ… **Portable**: Uses standard GitHub Actions workflow format - -### Workflow File -```yaml -# .github/workflows/main.yml -jobs: - build-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: '1.26.1' - - run: go build ./... - - run: go test ./... -cover - - lint-format: - runs-on: ubuntu-latest - steps: - - run: go fmt ./... - - run: go vet ./... +```bash +go run ./cmd/greet # Hello world! +go run ./cmd/greet Alice # Hello Alice! ``` -### Setup Instructions -1. **Gitea**: Enable GitHub Actions compatibility in repo settings -2. **GitHub**: Push to mirror repository (workflow runs automatically) -3. **GitLab**: Convert workflow to `.gitlab-ci.yml` or use compatibility mode - -**See [ADR 0016](adr/0016-ci-cd-pipeline-design.md) for complete CI/CD design and [STATUS_BADGES.md](STATUS_BADGES.md) for badge setup.** - ## Configuration -Basic configuration options: +All options are available via `config.yaml` or `DLC_*` environment variables. -```bash -# Start with default configuration -./scripts/start-server.sh start +| Env var | Default | Description | +|---------|---------|-------------| +| `DLC_SERVER_PORT` | `8080` | Listening port | +| `DLC_SERVER_HOST` | `0.0.0.0` | Bind address | +| `DLC_LOGGING_JSON` | `false` | JSON log format | +| `DLC_LOGGING_OUTPUT` | stderr | Log file path | +| `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown window | +| `DLC_API_V2_ENABLED` | `false` | Enable `/api/v2` routes | +| `DLC_CONFIG_FILE` | `./config.yaml` | Override config path | -# Custom port -export DLC_SERVER_PORT=9090 -./scripts/start-server.sh start +See `config.example.yaml` for a full template. -# JSON logging -export DLC_LOGGING_JSON=true -./scripts/start-server.sh start -``` +## API -**See [AGENTS.md](AGENTS.md#configuration-management) for comprehensive configuration guide including:** -- File-based configuration -- Environment variables -- Configuration priority rules -- OpenTelemetry setup -- Advanced scenarios - -## Usage - -### New Cobra CLI (Recommended) - -```bash -# Show help -./bin/dance-lessons-coach --help - -# Show version -./bin/dance-lessons-coach version - -# Greet someone -./bin/dance-lessons-coach greet John - -# Start server -./bin/dance-lessons-coach server -``` - -### Legacy CLI (Deprecated) - -```bash -# Default greeting -go run ./cmd/greet -# Output: Hello world! - -# Custom greeting -go run ./cmd/greet John -# Output: Hello John! -``` - -### Web Server - -**Using the server control script (recommended):** - -```bash -# Start the server -./scripts/start-server.sh start - -# Test API endpoints -./scripts/start-server.sh test - -# Access OpenAPI documentation -# Swagger UI: http://localhost:8080/swagger/ -# OpenAPI spec: http://localhost:8080/swagger/doc.json - -# Stop the server -./scripts/start-server.sh stop -``` - -**Manual server management:** - -```bash -# Start the server -go run ./cmd/server - -# Test API endpoints -curl http://localhost:8080/api/health -# Output: {"status":"healthy"} - -curl http://localhost:8080/api/ready -# Output: {"ready":true} - -curl http://localhost:8080/api/v1/greet -# Output: {"message":"Hello world!"} - -curl http://localhost:8080/api/v1/greet/John -# Output: {"message":"Hello John!"} -``` +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/health` | Liveness check | +| GET | `/api/ready` | Readiness check (503 during shutdown) | +| GET | `/api/version` | Version info (`?format=plain\|full\|json`) | +| GET | `/api/v1/greet/` | Default greeting | +| GET | `/api/v1/greet/{name}` | Named greeting | +| POST | `/api/v2/greet` | V2 greeting with validation | +| GET | `/swagger/` | Swagger UI | ## Testing ```bash -# Run all tests -go test ./... - -# Run specific package tests -go test ./pkg/greet/ +go test ./... # unit + integration tests +./scripts/test-graceful-shutdown.sh # lifecycle + JSON logging validation +./scripts/test-opentelemetry.sh # tracing end-to-end ``` -## CI/CD +## Gitea Client -dance-lessons-coach includes a comprehensive CI/CD pipeline with multiple testing options: +AI agent helper script at `.vibe/skills/gitea-client/scripts/gitea-client.sh`. -### Local Testing (No Gitea Required) +Auth setup: ```bash -# Validate workflow structure -./scripts/cicd.sh validate - -# Test workflow steps locally -./scripts/cicd.sh test-simple +echo "your_token" > ~/.gitea_token +chmod 600 ~/.gitea_token +export GITEA_API_TOKEN_FILE="$HOME/.gitea_token" ``` -### Gitea Integration -```bash -# Test local setup with Gitea configuration -./scripts/cicd.sh test-local - -# Check pipeline status on Gitea -./scripts/cicd.sh check-status -``` - -### Full CI/CD Testing -```bash -# Test with docker compose (requires Gitea runner) -./scripts/cicd.sh test-docker -``` - -**See [adr/0016-ci-cd-pipeline-design.md](adr/0016-ci-cd-pipeline-design.md) for complete CI/CD architecture.** - -## Project Structure - -``` -dance-lessons-coach/ -โ”œโ”€โ”€ adr/ # Architecture Decision Records -โ”œโ”€โ”€ cmd/ # Entry points (greet CLI, server) -โ”œโ”€โ”€ pkg/ # Core packages (config, greet, server, telemetry) -โ”‚ โ””โ”€โ”€ server/docs/ # Generated OpenAPI documentation (gitignored) -โ”œโ”€โ”€ config.yaml # Configuration file -โ”œโ”€โ”€ scripts/ # Management scripts -โ””โ”€โ”€ go.mod # Go module definition -``` - -**See [AGENTS.md](AGENTS.md#project-structure) for detailed structure and component explanations.** -``` - -## Development - -### Generate OpenAPI Documentation - -The project uses [swaggo/swag](https://github.com/swaggo/swag) to generate OpenAPI/Swagger documentation from code annotations: - -```bash -# Generate documentation -go generate ./pkg/server/ - -# This creates: -# - pkg/server/docs/docs.go (swagger template) -# - pkg/server/docs/swagger.json (OpenAPI spec) -# - pkg/server/docs/swagger.yaml (YAML version) -``` - -**Note:** `pkg/server/docs/` is gitignored. Documentation is embedded in the binary at build time. - -### Documentation Annotations - -Add swagger annotations to handlers and models: - -```go -// @Summary Get personalized greeting -// @Description Returns a greeting with the specified name -// @Tags greet -// @Accept json -// @Produce json -// @Param name path string true "Name to greet" -// @Success 200 {object} GreetResponse "Successful response" -// @Failure 400 {object} ErrorResponse "Invalid name parameter" -// @Router /v1/greet/{name} [get] -func (h *apiV1GreetHandler) handleGreetPath(w http.ResponseWriter, r *http.Request) { - // handler implementation -} -``` +Get a token at https://gitea.arcodange.lab โ†’ Profile โ†’ Settings โ†’ Applications. ## Architecture -This project uses Architecture Decision Records (ADRs) to document key technical choices. See [adr/](adr/) for complete documentation including decisions on Go 1.26.1, Chi router, Zerolog, OpenTelemetry, interface-based design, graceful shutdown, configuration management, testing strategies, and OpenAPI documentation. - -**Adding new decisions?** See [adr/README.md](adr/README.md) for guidelines. - -## Gitea Integration - -dance-lessons-coach includes AI agent skills for Gitea integration to monitor CI/CD jobs and interact with pull requests. - -### Gitea Client Skill Setup - -The Gitea client skill enables AI agents to: -- Monitor CI/CD job status -- Fetch job logs for debugging -- Comment on pull requests -- Track PR status - -**Setup Instructions:** - -1. **Create a Personal Access Token:** - - Log in to https://gitea.arcodange.lab - - Go to Profile โ†’ Settings โ†’ Applications - - Generate token with `read:repository`, `write:repository`, and `read:user` scopes - -2. **Configure Authentication:** - ```bash - # Option 1: Environment variable - export GITEA_API_TOKEN="your_token" - - # Option 2: Token file (recommended) - echo "your_token" > ~/.gitea_token - chmod 600 ~/.gitea_token - export GITEA_API_TOKEN_FILE="$HOME/.gitea_token" - ``` - -3. **Add to shell configuration:** - ```bash - echo 'export GITEA_API_TOKEN_FILE="$HOME/.gitea_token"' >> ~/.bashrc - source ~/.bashrc - ``` - -**Usage Examples:** -```bash -# List recent jobs -.vibe/skills/gitea-client/scripts/gitea-client.sh list-jobs owner repo workflow_id 5 - -# Wait for job completion -.vibe/skills/gitea-client/scripts/gitea-client.sh wait-job owner repo job_id 300 - -# Comment on PR -.vibe/skills/gitea-client/scripts/gitea-client.sh comment-pr owner repo 42 "Build completed!" -``` - -**Documentation:** See [.vibe/skills/gitea-client/README.md](.vibe/skills/gitea-client/README.md) for complete setup and usage guide. - -## ๐Ÿค– AI Agent Usage - -### Quick Launch Commands - -**Programmer Agent** (for code implementation, testing, CI/CD): -```bash -vibe start --agent dancelessonscoachprogrammer -``` - -**Product Owner Agent** (for requirements, interviews, documentation): -```bash -vibe start --agent dancelessonscoach-product-owner -``` - -### Full Documentation - -For complete agent usage guide including: -- Agent selection guidance -- Common workflow examples -- Configuration reference -- Best practices -- Troubleshooting tips - -See: [AGENT_USAGE_GUIDE.md](documentation/AGENT_USAGE_GUIDE.md) - -### Gitmoji Cheatsheet - -Quick reference for commit messages: -- **๐Ÿ“ `:memo:` docs** - Documentation -- **โœจ `:sparkles:` feat** - New feature -- **๐Ÿ› `:bug:` fix** - Bug fix -- **โ™ป๏ธ `:recycle:` refactor** - Code refactoring -- **๐Ÿ”ง `:wrench:` chore** - Build/config changes - -Full cheatsheet: [GITMOJI_CHEATSHEET.md](documentation/GITMOJI_CHEATSHEET.md) +Key decisions are documented in [adr/](adr/). See [AGENTS.md](AGENTS.md) for the full development reference (commands, config, ADR index, commit conventions). ## License diff --git a/cmd/server/main.go b/cmd/server/main.go index 6f8cf38..abe8fa1 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -48,8 +48,10 @@ func main() { log.Fatal().Err(err).Msg("Failed to load configuration") } - // Create readiness context to control readiness state - readyCtx, readyCancel := context.WithCancel(context.Background()) + // Create readiness context to control readiness state. + // CancelableContext exposes Cancel() so that Server.Run() can cancel + // readiness at the start of graceful shutdown (before the propagation sleep). + readyCtx, readyCancel := server.NewCancelableContext(context.Background()) defer readyCancel() // Create and run server @@ -57,4 +59,5 @@ func main() { if err := server.Run(); err != nil { log.Fatal().Err(err).Msg("Server failed") } + log.Trace().Msg("Server exited") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 52210f7..f9cbdc1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -118,6 +118,34 @@ type SamplerConfig struct { Ratio float64 `mapstructure:"ratio"` } +// peekJSONLogging determines whether JSON logging should be used before the full +// config is loaded, solving the chicken-and-egg problem where the logger format +// must be known before any log is emitted, yet the format is stored in the config. +// +// Resolution order (mirrors Viper's own priority): +// 1. DLC_LOGGING_JSON env var โ€” checked directly via os.Getenv (zero overhead) +// 2. logging.json key in the config file โ€” read with a minimal throwaway Viper +// instance so we don't parse the whole config twice unnecessarily +func peekJSONLogging() bool { + // 1. Env var takes highest priority โ€” check it first + if env := os.Getenv("DLC_LOGGING_JSON"); env != "" { + return strings.EqualFold(env, "true") || env == "1" + } + + // 2. Try to read logging.json from the config file + preV := viper.New() + preV.SetDefault("logging.json", false) + if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" { + preV.SetConfigFile(configFile) + } else { + preV.SetConfigName("config") + preV.SetConfigType("yaml") + preV.AddConfigPath(".") + } + _ = preV.ReadInConfig() // ignore errors โ€” defaults apply on failure + return preV.GetBool("logging.json") +} + // LoadConfig loads configuration from file, environment variables, and defaults // Configuration priority: file > environment variables > defaults // To specify a custom config file path, set DLC_CONFIG_FILE environment variable @@ -129,9 +157,17 @@ func LoadConfig() (*Config, error) { v := viper.New() - // Set up initial console logging for config loading messages - consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr} - log.Logger = log.Output(consoleWriter) + // Configure the logger format before emitting any log output. + // peekJSONLogging reads the JSON setting early (env var + config file pre-read) + // so that every log line โ€” including those produced during config loading โ€” is + // already in the correct format. + jsonLogging := peekJSONLogging() + if jsonLogging { + log.Logger = log.Output(os.Stderr) + } else { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + } + log.Info().Bool("json", jsonLogging).Msg("Logging configured") // Set default values v.SetDefault("server.host", "0.0.0.0") @@ -227,15 +263,9 @@ func LoadConfig() (*Config, error) { return nil, fmt.Errorf("config unmarshal error: %w", err) } - // Configure log output format (JSON or console) first - if config.Logging.JSON { - log.Logger = log.Output(os.Stderr) - } else { - consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr} - log.Logger = log.Output(consoleWriter) - } - - // Setup logging based on configuration + // Setup logging based on configuration (level, output file, time format). + // The JSON/console format was already applied at the top of LoadConfig via + // peekJSONLogging, so SetupLogging only needs to handle the remaining knobs. config.SetupLogging() log.Info(). diff --git a/pkg/server/server.go b/pkg/server/server.go index 8b7286a..9c20539 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -33,6 +33,28 @@ import ( //go:embed docs/swagger.json var swaggerJSON embed.FS +// CancelableContext wraps a context.Context and exposes a Cancel() method so +// that Server.Run() can cancel readiness during graceful shutdown via the type +// assertion it already performs. Callers that don't need controlled cancellation +// (tests, CLI) can pass a plain context.Background() โ€” the assertion silently +// fails and readiness is never explicitly cancelled, which is harmless. +type CancelableContext struct { + context.Context + cancel context.CancelFunc +} + +// NewCancelableContext creates a CancelableContext whose Cancel() method will +// be invoked by Server.Run() at the start of graceful shutdown, before the +// 1-second readiness propagation window. The returned CancelFunc is a no-op +// after Cancel() has been called, so it is safe to defer in main. +func NewCancelableContext(parent context.Context) (*CancelableContext, context.CancelFunc) { + ctx, cancel := context.WithCancel(parent) + return &CancelableContext{Context: ctx, cancel: cancel}, cancel +} + +// Cancel satisfies the interface checked in Run() and cancels the context. +func (c *CancelableContext) Cancel() { c.cancel() } + type Server struct { router *chi.Mux readyCtx context.Context diff --git a/scripts/start-server.sh b/scripts/start-server.sh index 98229e5..fd7ace8 100755 --- a/scripts/start-server.sh +++ b/scripts/start-server.sh @@ -4,7 +4,8 @@ # This script starts the server in the background and provides control functions # Configuration -PROJECT_DIR="/Users/gabrielradureau/Work/Vibe/dance-lessons-coach" +SCRIPTS_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") +PROJECT_DIR=$(dirname "$SCRIPTS_DIR") SERVER_CMD="go run ./cmd/server" LOG_FILE="server.log" PID_FILE="server.pid" diff --git a/scripts/test-graceful-shutdown.sh b/scripts/test-graceful-shutdown.sh index e3dd437..6b362bc 100755 --- a/scripts/test-graceful-shutdown.sh +++ b/scripts/test-graceful-shutdown.sh @@ -7,7 +7,8 @@ set -e # Configuration -PROJECT_DIR="/Users/gabrielradureau/Work/Vibe/dance-lessons-coach" +SCRIPTS_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") +PROJECT_DIR=$(dirname "$SCRIPTS_DIR") SERVER_CMD="./scripts/start-server.sh" LOG_FILE="server.log" PID_FILE="server.pid" @@ -59,11 +60,40 @@ echo "Response: $GREET_NAME_RESPONSE" echo "" echo "Stopping server gracefully..." -# Test readiness during shutdown (in background) -(curl -s http://localhost:8080/api/ready > /dev/null 2>&1 &) +# Send SIGTERM once and probe /api/ready during the 1-second propagation window +# the server holds open (pkg/server/server.go: time.Sleep(1s) after readiness +# cancel). Previously the curl fired *before* the signal โ€” it always saw "ready". +# We also avoid calling "$SERVER_CMD stop" afterwards because that would send a +# second SIGTERM: after signal.NotifyContext is done, the default handler kicks in +# and the process terminates with a non-JSON "signal: terminated" on stderr. +SERVER_PID=$(cat "$PID_FILE" 2>/dev/null || echo "") +if [[ -z "$SERVER_PID" ]]; then + echo -e "\033[0;31mโŒ FAIL: PID file not found\033[0m" + exit 1 +fi -$SERVER_CMD stop -sleep 3 +kill -TERM "$SERVER_PID" +# Brief yield so the signal handler runs and CancelableContext.Cancel() fires +sleep 0.2 +READY_DURING_SHUTDOWN=$(curl -s -w "\n[HTTP %{http_code}]" http://localhost:8080/api/ready 2>&1 || echo "[connection refused]") +echo "Readiness during shutdown: $READY_DURING_SHUTDOWN" + +# Wait for the process to exit cleanly (up to 30s) without sending another signal +echo "Waiting for server to exit..." +for i in {1..30}; do + if ! ps -p "$SERVER_PID" > /dev/null 2>&1; then + echo "Server stopped successfully" + rm -f "$PID_FILE" + break + fi + sleep 1 +done +if ps -p "$SERVER_PID" > /dev/null 2>&1; then + echo -e "\033[0;31mโŒ FAIL: Server did not stop within 30s\033[0m" + kill -9 "$SERVER_PID" 2>/dev/null || true + exit 1 +fi +sleep 0.5 echo "" echo "Analyzing server logs..." @@ -201,6 +231,12 @@ fi echo "" echo -e "\033[0;32m๐ŸŽ‰ GRACEFUL SHUTDOWN TEST PASSED!\033[0m" echo "All required logs are present and in correct order." + +echo "" +echo "๐Ÿ“‹ Full server log:" +echo "===============================" +cat "$LOG_FILE" | jq -r '"[\(.level | ascii_upcase)] \(.time | tostring) โ€” \(.message)"' +echo "===============================" echo "" # Clean up diff --git a/scripts/test-opentelemetry.sh b/scripts/test-opentelemetry.sh index 750fc15..3d76cff 100755 --- a/scripts/test-opentelemetry.sh +++ b/scripts/test-opentelemetry.sh @@ -9,7 +9,8 @@ echo -e "\033[1;34m=== dance-lessons-coach OpenTelemetry Test ===\033[0m" echo "" # Configuration -PROJECT_DIR="/Users/gabrielradureau/Work/Vibe/dance-lessons-coach" +SCRIPTS_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") +PROJECT_DIR=$(dirname "$SCRIPTS_DIR") SERVER_CMD="./scripts/start-server.sh" LOG_FILE="server.log" PID_FILE="server.pid"