11 Commits

Author SHA1 Message Date
29272b8fba 🔀 merge: integrate origin/main (PR #16 JSON logging fix) into restructure branch
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 26s
CI/CD Pipeline / CI Pipeline (push) Failing after 5m46s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped
PR #16 (commit c17fb4f) introduced 2 things while this restructure branch
was in flight:
1. A fix in pkg/config/config.go (peekJSONLogging) so the very first log
   line is JSON when DLC_LOGGING_JSON=true.
2. Its own attempt to shorten AGENTS.md (1296 → 191 lines).

Resolution:
- Code/scripts changes from #16 (config.go, server.go, scripts/*,
  gitea-client.sh) accepted as-is via auto-merge.
- AGENTS.md conflict resolved by keeping our version (130 lines,
  fully externalized to documentation/*.md). Our approach goes further
  in the lazy-loading direction (D-004, 128k context constraint),
  externalizing every detail instead of keeping minimal inline content.

Caught up the missing /api/version endpoint in documentation/API.md
(commit acebea3 just before this merge) so we don't regress on the
new endpoint introduced by #16.

Pre-commit hooks already validated each upstream commit. Re-running
on the merge to confirm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:02:19 +02:00
acebea353b 📝 docs(api): add /api/version endpoint reference
Endpoint introduced in PR #16 (commit c17fb4f) was missing from our
restructured documentation/API.md. Catching up before merging origin/main
into this feature branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:01:43 +02:00
732eee7586 🐛 fix(adr): correct ADR 0018-0019 dates (2024 → 2026) — Tâche 6 Phase D
Friction documentaire identifiée pendant l'audit Phase A : les ADRs
0018 (User Management) et 0019 (PostgreSQL Integration) avaient des
dates 2024-04-XX dans leur header, alors que le projet a démarré
le 2026-04-01 (cf. CHANGELOG.md, première entrée).

C'est un typo. Implementation Date était bien à 2026-04-08 dans les
deux fichiers, ce qui confirme le diagnostic.

Fix :
- adr/0018-user-management-auth-system.md : 2024-04-06 → 2026-04-06
- adr/0019-postgresql-integration.md     : 2024-04-07 → 2026-04-07

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:28:34 +02:00
88a934dfd2 📝 docs(restructure): rewrite AGENTS.md as short directive (Tâche 6 Phase C)
AGENTS.md passe de 1296 → 130 lignes, sous la cible 200 fixée en
D-004 (lazy loading 128k). Ne contient plus que :
- Project overview (court)
- Tools & technologies (table)
- Project structure (tree)
- Tableau "Detailed Guides" pointant vers documentation/*.md
  (12 entrées, tous liens vérifiés valides)
- Index des ADR-clés avec liens (13 entrées, tous valides)
- AI agent info (court, pointe vers AGENT_USAGE_GUIDE)
- Commit conventions (court, pointe vers .vibe/skills/commit-message/)
- BDD feature structure (court, pointe vers ADR-0008 + BDD_GUIDE)
- Retention policy (gardée intégralement, directive ARCODANGE)
- Support (procédure escalade en 5 étapes)

Section Version Management (ex-928-1076, ~150 lignes) entièrement
SUPPRIMÉE — totalement redondante avec documentation/version-management-
guide.md (cf. analyse Phase A `~/.vibe/plans/task-6-phase-a-results.md`).

Lien cassé ligne 1277 corrigé : `0019-bdd-feature-structure.md`
(inexistant) remplacé par référence à ADR-0008 (bdd-testing) + ADR-0025
(scenario-isolation) qui sont les vraies sources autoritaires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:28:24 +02:00
41ee8c56ac 📝 docs(restructure): split AGENTS.md into focused guides (Tâche 6 Phase B)
Création de 9 fichiers neufs pour décharger AGENTS.md (1296 lignes →
~130) en documents lazy-loadables, compatibles avec la limite de
contexte 128k de Mistral Vibe (cf. ARCODANGE migration Phase 1,
Tâche 6 du curriculum).

Sept guides ciblés sous documentation/ :
- HISTORY.md            : phases historiques 1-9 du développement
- CLI.md                : commandes CLI, server lifecycle, config DLC_*
- API.md                : endpoints REST, OpenAPI, Greet v1/v2
- OBSERVABILITY.md      : OpenTelemetry + Jaeger, sampler types, test
- TROUBLESHOOTING.md    : issues connues + pointeurs vers guides spé
- CODE_EXAMPLES.md      : snippets endpoint/logging/context, pointeurs ADR
- ROADMAP.md            : potential features, architectural improvements

Deux fichiers racine :
- CHANGELOG.md          : user-facing, format Keep a Changelog
- AGENT_CHANGELOG.md    : décisions structurantes des agents AI
                          (référencé par AGENTS.md, n'existait pas)

Le contenu est extrait fidèlement d'AGENTS.md sans réinterprétation.
Phase C (réécriture AGENTS.md court) suit dans le commit suivant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:28:10 +02:00
c17fb4f9b4 🐛 fix: emit all config-loading logs in correct JSON format from the start (#16)
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 10s
CI/CD Pipeline / CI Pipeline (push) Failing after 4m14s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped
## Summary

Closes #15

When `logging.json: true` (or `DLC_LOGGING_JSON=true`), the logger was unconditionally initialised to console/text format at the top of `LoadConfig()`, so early log lines — most visibly **"Config file loaded"** — were always written as human-readable text regardless of configuration.

## Root cause

Classic chicken-and-egg: the format flag lives inside the config that is being loaded. The format-switch block only ran *after* `v.Unmarshal()`, too late for the config-file log.

## Changes

### `pkg/config/config.go`
- Add `peekJSONLogging()`: resolves the JSON flag **before** any log is emitted by (1) checking `DLC_LOGGING_JSON` directly via `os.Getenv`, then (2) doing a minimal throwaway Viper pre-read of the config file for the `logging.json` key. This mirrors Viper's own priority order without parsing the full config twice.
- Apply the resolved format immediately and emit **"Logging configured"** as the very first log line.
- Remove the now-redundant format-switch block that ran after `Unmarshal()`.

### `scripts/start-server.sh`, `test-graceful-shutdown.sh`, `test-opentelemetry.sh`
- Replace hardcoded `PROJECT_DIR` path with a dynamic `SCRIPTS_DIR=$(dirname $(realpath ${BASH_SOURCE[0]}))` derivation so scripts work from any worktree or clone location.

## Test plan
- [x] `go test ./pkg/...` — all pass
- [x] `scripts/test-graceful-shutdown.sh` — all JSON valid, all startup logs present
- [x] Manual smoke test: first line is `{"level":"info",...,"message":"Logging configured"}`, every line is valid JSON

Reviewed-on: #16
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-04-12 23:28:35 +02:00
73a3af1552 📝 docs: audit and correct all ADR statuses and content
Full pass over all 25 ADRs to align documentation with actual
implementation state. Changes by ADR:

README index: completely rewritten — previous table mapped numbers to
wrong titles from 0010 onward.

0008 (BDD Testing): added note that flat features/ structure and godog
CLI invocation are superseded by ADR-0024; framework decision stands.

0009 (Hybrid Testing): renamed from "Combine BDD and Swagger-based
testing" to "BDD Testing with OpenAPI Documentation"; clarified that
the SDK-testing layer was never built and has no open issue.

0013 (OpenAPI/Swagger): removed leftover merge conflict artifact
(=======) and duplicated 60-line block.

0015 (Cobra CLI): fixed status contradiction — body said "Implemented"
while footer said "Proposed". Now Accepted.

0018 (User Management): status Proposed → Accepted; system is fully
implemented (JWT, bcrypt, GORM repos all present).

0019 (PostgreSQL): status Proposed → Accepted (Partial); added warning
that sqlite_repository.go and gorm/driver/sqlite still present contrary
to ADR intent.

0021 (JWT Retention): fixed wrong cross-reference (previously cited
ADR-0009 "Hybrid Testing" as source of JWT multi-secret support); fixed
title number from "10" to "21"; clarified that base JWT is implemented
but the retention cleanup job is not.

0022 (Rate Limiting/Cache): added warning block linking to open Gitea
issue #13; changed all 20 false  implementation checkboxes to .

0023 (Config Hot Reloading): added note that BDD scenarios exist for
this feature but the feature itself is not yet implemented.

0024 (BDD Organization): status Proposed → Accepted; modular domain
structure is fully built.

0025 (BDD Scenario Isolation): status Proposed → Accepted (Partial);
Phase 1 done, Phase 2 blocked on ADR-0022.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 23:26:09 +02:00
8bae62c28e 📝 docs: add two missing ADR files (0011 validation, 0014 gRPC)
ADR 0011 and 0014 were referenced in the README list but their files
were absent from the repository. Reconstruct them from available context:

- 0011: go-playground/validator selection (already implemented in go.mod)
- 0014: gRPC adoption strategy (evaluated and deferred/rejected)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 23:25:25 +02:00
5eec64e5e8 🧪 test: add JWT secret rotation BDD scenarios and step implementations (#12)
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 9s
CI/CD Pipeline / CI Pipeline (push) Successful in 4m15s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped
 merge: implement JWT secret rotation with BDD scenario isolation

- Implement JWT secret rotation mechanism (closes #8)
- Add per-scenario state isolation for BDD tests (closes #14)
- Validate password reset workflow via BDD tests (closes #7)
- Fix port conflicts in test validation
- Add state tracer for debugging test execution
- Document BDD isolation strategies in ADR 0025
- Fix PostgreSQL configuration environment variables

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-04-11 17:56:45 +02:00
5de703468f Merge pull request 'Move Docker push steps to separate job' (#11) from feature/move-docker-job into main
🤖 ci: separate docker push job
closes #10
2026-04-09 13:08:13 +02:00
be0a31a525 🤖 ci: separate docker push job
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 8s
CI/CD Pipeline / CI Pipeline (push) Successful in 4m17s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped
2026-04-09 13:03:08 +02:00
35 changed files with 1637 additions and 1805 deletions

234
.gitea/workflows/README.md Normal file
View File

@@ -0,0 +1,234 @@
# CI/CD Workflow Architecture
## 🗺️ Overview
The dance-lessons-coach project uses a **multi-workflow architecture** for better separation of concerns, maintainability, and flexibility.
## 📁 Workflow Files
### 1. `ci-cd.yaml` - Main CI/CD Pipeline
**Purpose**: Run tests, build binaries, and generate documentation
**Triggers**:
- Push to `main`, `ci/**`, `feature/**`, `fix/**`, `refactor/**` branches
- Pull requests to `main` branch
- Manual workflow dispatch
**Jobs**:
1. **build-cache** - Build and cache Docker build environment
2. **ci-pipeline** - Run tests, build binaries, generate Swagger docs
3. **trigger-docker-push** - Trigger separate Docker workflow on main branch
**Key Features**:
- Runs in container environment with all build tools
- Generates Swagger documentation
- Runs BDD and unit tests with PostgreSQL
- Updates badges and version information
- Triggers Docker workflow only on main branch
### 2. `docker-push.yaml` - Docker Image Publishing
**Purpose**: Build and push Docker images to registry
**Triggers**:
- Manual workflow dispatch only (no automatic triggers)
- Triggered by `ci-cd.yaml` on main branch
**Jobs**:
1. **docker-push** - Build production Docker image and push to registry
**Key Features**:
- Runs on host environment (access to Docker daemon)
- Uses dependency hash from build-cache
- Builds minimal Alpine-based production image
- Pushes multiple tags (version, latest, commit SHA)
## 🔧 Architecture Benefits
### 1. Clear Separation of Concerns
- **CI/CD Pipeline**: Testing and artifact generation
- **Docker Publishing**: Image building and registry operations
### 2. Proper Environment Isolation
- **CI jobs run in container**: Consistent build environment
- **Docker jobs run on host**: Access to Docker daemon
### 3. Flexible Testing
- Can trigger Docker workflow independently for testing
- No complex conditional logic in main workflow
- Easier to debug and maintain
### 4. Better Security
- Docker operations isolated in separate workflow
- Clear dependency between test success and deployment
- Manual trigger capability for emergency situations
## 🚀 Usage Examples
### Trigger Full CI/CD Pipeline
```bash
# Automatically triggered on push to main branch
# Or manually:
./scripts/gitea-client.sh trigger-workflow arcodange dance-lessons-coach ci-cd.yaml main
```
### Trigger Docker Push Manually
```bash
# Get dependency hash from build-cache job first
DEPS_HASH="abc123def456"
# Trigger Docker workflow manually
./scripts/gitea-client.sh trigger-workflow arcodange dance-lessons-coach docker-push.yaml main --deps_hash $DEPS_HASH
```
### Workflow Dispatch Parameters (docker-push.yaml)
- `deps_hash` (required): Dependency hash from build-cache job
- `ref` (optional): Git reference (branch/tag), defaults to current
## 🔗 Workflow Dependencies
```mermaid
graph TD
A[Push to main] --> B[ci-cd.yaml]
B --> C[build-cache job]
B --> D[ci-pipeline job]
D --> E[trigger-docker-push job]
E --> F[docker-push.yaml]
F --> G[docker-push job]
G --> H[Docker Registry]
```
## 📋 Best Practices
### 1. Always Run CI First
- Docker workflow should only be triggered after CI passes
- Maintains quality gate before deployment
### 2. Use Dependency Hash
- Ensures consistent builds across workflows
- Pass hash from build-cache to docker-push
### 3. Manual Testing
- Use separate Docker workflow for testing image builds
- Avoids polluting main branch with test images
### 4. Monitor Both Workflows
- CI/CD workflow for test results and artifacts
- Docker workflow for image build and push status
## 🎯 Docker Build Strategy Decision
### 🏆 Chosen Approach: Attempt 2 (Standard Dockerfile)
After extensive testing of multiple approaches, we selected **Attempt 2** as the optimal Docker build strategy.
#### ⚡ Why Attempt 2 Won:
**1. Simplicity (60% smaller workflow)**
- 73 lines vs 158 lines in complex approaches
- No inline Dockerfile generation
- Standard `docker build -f docker/Dockerfile .` command
**2. Better Performance**
- No artifact/cache action overhead
- Natural Docker layer caching works optimally
- Faster execution without complex variable substitutions
**3. Superior Reliability**
- Proven standard Docker build process
- Easier to debug and maintain
- Fewer moving parts = fewer failures
**4. Better Maintainability**
- Uses standard Dockerfile (easier to understand)
- No complex YAML templating
- Clear separation of concerns
#### 🗑️ Why We Rejected Other Approaches:
**Attempt 1 (Inline Dockerfile):**
- Complex YAML templating
- Harder to debug and maintain
- No significant performance benefit
**Attempt 3 (Build Cache Image):**
- Added complexity with cache management
- Slower due to artifact actions overhead
- More prone to cache invalidation issues
**Attempt 4 (Template File):**
- Added unnecessary file management
- No clear advantage over standard Dockerfile
- More complex workflow
### 📊 Performance Comparison:
| Approach | Lines of Code | Complexity | Reliability | Maintainability |
|----------|---------------|------------|-------------|-----------------|
| **Attempt 2** | 73 | Low | High | Excellent |
| Attempt 1 | 158 | High | Medium | Poor |
| Attempt 3 | 125 | Medium | Medium | Fair |
| Attempt 4 | 110 | Medium | High | Good |
### 🔧 Implementation Details:
**Standard Dockerfile Approach:**
```yaml
- name: Build and push Docker image
run: |
docker build -t dance-lessons-coach -f docker/Dockerfile .
docker tag dance-lessons-coach "$IMAGE_NAME"
docker push "$IMAGE_NAME"
```
**Key Benefits:**
- Uses multi-stage builds for optimization
- Standard Docker layer caching works naturally
- Easy to understand and modify
- Proven reliability in production
## 🎯 Future Enhancements
### Potential Improvements:
- Add workflow status badges to README
- Implement workflow chaining with outputs
- Add matrix builds for multiple architectures
- Implement canary deployment workflow
- Add rollback capability
### Architecture Considerations:
- Keep workflows focused on single responsibilities
- Maintain clear separation between test and deploy
- Document all workflow triggers and conditions
- Monitor workflow execution times and optimize
## 📝 Maintenance
### Adding New Jobs:
- Add to appropriate workflow based on responsibility
- CI-related jobs → `ci-cd.yaml`
- Docker-related jobs → `docker-push.yaml`
### Modifying Triggers:
- Update trigger conditions in respective workflow files
- Test changes thoroughly before merging
### Debugging:
- Check workflow logs in Gitea Actions
- Use `gitea-client.sh diagnose-job` for detailed analysis
- Monitor workflow dependencies and execution order
## 🔒 Security
### Secrets Management:
- Docker registry credentials stored in Gitea secrets
- Never hardcode credentials in workflow files
- Use GitHub token for workflow dispatch
### Access Control:
- Only authorized users can trigger workflows
- Manual approval required for production deployments
- Audit logs available for all workflow executions
This architecture provides a clean, maintainable, and secure CI/CD pipeline that scales well with project growth while maintaining clear separation of concerns.

View File

@@ -132,7 +132,8 @@ jobs:
name: CI Pipeline
needs: build-cache
runs-on: ubuntu-latest-ca
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.actor != 'ci-bot'"
# Skip conditions: standard skip ci + actor check + respect skip_ci input
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.actor != 'ci-bot' && (!github.event.inputs.skip_ci || github.event.inputs.skip_ci == 'false')"
container:
image: ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}-build-cache:${{ needs.build-cache.outputs.deps_hash }}
@@ -304,47 +305,23 @@ jobs:
echo " No changes to push"
fi
# Docker build and push (main branch only)
- name: Login to Gitea Container Registry
if: github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ${{ env.CI_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Build and push Docker image
if: github.ref == 'refs/heads/main'
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
# Use the template file with proper dependency hash replacement
DEPS_HASH="${{ needs.build-cache.outputs.deps_hash }}"
echo "Using dependency hash: $DEPS_HASH"
# Create Dockerfile.prod from template
sed "s/{{DEPS_HASH}}/$DEPS_HASH/g" docker/Dockerfile.prod.template > docker/Dockerfile.prod
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
echo "Building Docker image with tags: $TAGS"
# Build the production image
docker build -t dance-lessons-coach -f docker/Dockerfile.prod .
for TAG in $TAGS; do
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
echo "Tagging and pushing: $IMAGE_NAME"
docker tag dance-lessons-coach "$IMAGE_NAME"
docker push "$IMAGE_NAME"
done
- name: Show published images
if: github.ref == 'refs/heads/main'
# Trigger Docker push workflow on main branch
trigger-docker-push:
name: Trigger Docker Push
needs: [build-cache, ci-pipeline]
runs-on: ubuntu-latest-ca
if: "!contains(github.event.head_commit.message, '[skip ci]') && github.actor != 'ci-bot' && github.ref == 'refs/heads/main'"
steps:
- name: Trigger Docker Push Workflow
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
echo "📦 Published Docker images:"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$IMAGE_VERSION"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:latest"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:${{ github.sha }}"
echo "🚀 Triggering Docker Push workflow..."
curl -X POST \
-H "Authorization: token ${{ secrets.GITEA_TOKEN || secrets.PACKAGES_TOKEN }}" \
-H "Content-Type: application/json" \
"${{ env.GITEA_INTERNAL }}api/v1/repos/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}/actions/workflows/docker-push.yaml/dispatches" \
-d '{"ref":"${{ github.ref }}"}'
echo "✅ Docker Push workflow triggered successfully!"

View File

@@ -0,0 +1,73 @@
---
# dance-lessons-coach Docker Push Workflow
# Separate workflow for Docker image building and pushing
# Can be triggered manually or by CI/CD workflow
name: Docker Push
on:
# Manual trigger for testing or production
workflow_dispatch:
inputs:
ref:
description: 'Git reference (branch/tag)'
required: false
type: string
default: ''
# Environment variables
env:
GITEA_INTERNAL: "https://gitea.arcodange.lab/"
GITEA_EXTERNAL: "https://gitea.arcodange.fr/"
GITEA_ORG: "arcodange"
GITEA_REPO: "dance-lessons-coach"
CI_REGISTRY: "gitea.arcodange.lab"
jobs:
docker-push:
name: Docker Push
runs-on: ubuntu-latest-ca
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.CI_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Build and push Docker image
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
echo "Building Docker image with tags: $TAGS"
# Build using the standard Dockerfile (Attempt 2 - simplest approach)
docker build -t dance-lessons-coach -f docker/Dockerfile .
for TAG in $TAGS; do
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
echo "Tagging and pushing: $IMAGE_NAME"
docker tag dance-lessons-coach "$IMAGE_NAME"
docker push "$IMAGE_NAME"
done
- name: Show published images
run: |
source VERSION
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
echo "📦 Published Docker images:"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$IMAGE_VERSION"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:latest"
echo " - ${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:${{ github.sha }}"

View File

@@ -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 <owner> <repo> <title> <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

1304
AGENTS.md

File diff suppressed because it is too large Load Diff

32
AGENT_CHANGELOG.md Normal file
View File

@@ -0,0 +1,32 @@
# AGENT_CHANGELOG
Trace ordonnée des décisions et actions structurantes prises par les agents AI (Claude Code, Mistral Vibe, autres) sur le projet `dance-lessons-coach`. Complémentaire au [`CHANGELOG.md`](CHANGELOG.md) qui couvre les changements user-facing du produit.
**Pourquoi ce fichier** : référencé dans la documentation directrice (cf. AGENTS.md), mais initialement absent du repo. Initialisé dans le cadre de la Tâche 6 du curriculum migration Claude → Mistral Vibe (ARCODANGE Phase 1).
## Convention
Une entrée par décision/action structurante prise par un agent AI. Format :
```
## YYYY-MM-DD — <Agent> — <Titre court>
**Contexte** : 1-3 lignes — pourquoi cette action
**Décision/Action** : ce qui a été fait
**Conséquence** : impact sur le projet (fichiers, conventions, workflows)
**Référence** : commit hash, PR Gitea, ADR, issue (le cas échéant)
```
Les entrées qui ne demandent pas de discussion (typo fixes, formatting, dependency bumps mineurs) ne sont **pas** loguées ici — c'est ce que fait le commit Git. Ce fichier garde uniquement les décisions où le **pourquoi** mérite une trace.
---
## 2026-05-02 — Mistral Vibe (intent-router) + Claude Code (Opus 4.7) — Initialisation AGENT_CHANGELOG.md
**Contexte** : Tâche 6 du curriculum migration ARCODANGE Phase 1 (cf. `~/.vibe/plans/migration-claude-vers-mistral-phase-1.md`). Le fichier `AGENT_CHANGELOG.md` était mentionné dans la documentation directrice projet mais n'existait pas — friction identifiée par l'audit Phase A.
**Décision/Action** : initialiser le fichier avec convention claire et pointer depuis `AGENTS.md` (Tâche 6 Phase C).
**Conséquence** : tout agent qui prend une décision structurante sur le projet doit ajouter une entrée datée ici. Permet la traçabilité des choix AI au-delà des commits Git.
**Référence** : Tâche 6 du plan migration. Voir aussi `~/.vibe/plans/task-6-phase-a-results.md` pour le contexte complet de la restructuration en cours.

57
CHANGELOG.md Normal file
View File

@@ -0,0 +1,57 @@
# Changelog
Notable user-facing changes to `dance-lessons-coach`. Format inspired by [Keep a Changelog](https://keepachangelog.com/), versioning follows [Semantic Versioning 2.0.0](https://semver.org/) (see [`documentation/version-management-guide.md`](documentation/version-management-guide.md)).
The historical phases of foundational development (Phase 1 to Phase 9) are documented in [`documentation/HISTORY.md`](documentation/HISTORY.md).
## [Unreleased]
### Added
_(items pending release; move to a versioned section when tagged)_
### Changed
### Fixed
---
## 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

429
README.md
View File

@@ -1,421 +1,98 @@
# dance-lessons-coach
[![Build Status](https://gitea.arcodange.fr/api/badges/arcodange/dance-lessons-coach/status)](https://gitea.arcodange.fr/arcodange/dance-lessons-coach)
[![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-0.0%-red?style=flat-square)](https://gitea.arcodange.lab/arcodange/dance-lessons-coach)
[![Unit Coverage](https://img.shields.io/badge/Unit_Coverage-0.0%-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

View File

@@ -4,6 +4,8 @@
* Deciders: Gabriel Radureau, AI Agent
* Date: 2026-04-05
> **⚠️ Structure superseded by ADR-0024.** The framework decision (Godog, in-process test server) remains valid. However, the flat `features/` layout and single `steps.go` file described here were replaced by a modular per-domain structure. See ADR-0024 for the current organisation: `features/{auth,greet,health,jwt,config}/` with domain-specific step files and per-domain `*_test.go` runners. The `cd features && godog` execution pattern is also outdated — each domain now uses `go test`.
## Context and Problem Statement
We needed to add behavioral testing to dance-lessons-coach that provides:

View File

@@ -1,10 +1,11 @@
# Combine BDD and Swagger-based testing
# BDD Testing with OpenAPI Documentation
* Status: ✅ Partially Implemented (BDD + Documentation only)
* Status: Accepted
* Deciders: Gabriel Radureau, AI Agent
* Date: 2026-04-05
* Last Updated: 2026-04-05
* Implementation Status: BDD testing and OpenAPI documentation completed, SDK generation deferred
* Last Updated: 2026-04-12
> **⚠️ Title corrected.** This ADR was originally named "Combine BDD and Swagger-based testing" with the intent of eventually adding SDK-generated BDD tests as a second layer ("hybrid"). That second layer was deferred and has no concrete plan. The actual architecture is **BDD direct-HTTP testing + OpenAPI documentation via swaggo** — calling it "hybrid" is misleading. SDK generation remains a possible future enhancement but is not tracked by any open issue.
## Context and Problem Statement

View File

@@ -0,0 +1,36 @@
# 11. Validation Library Selection
* Status: Accepted
* Deciders: Gabriel Radureau, AI Agent
* Date: 2026-04-05
* Implementation Date: 2026-04-05
## Context and Problem Statement
The dance-lessons-coach application needs input validation for API request bodies and configuration values. We need a library that integrates well with Go structs and provides clear error messages.
## Decision Drivers
* Struct-tag-based validation to avoid boilerplate
* Good error messages with field-level detail
* Active maintenance and wide adoption
* Compatibility with existing interface-based design
## Considered Options
* `github.com/go-playground/validator/v10` — struct-tag driven, widely adopted
* `github.com/asaskevich/govalidator` — tag-based but less expressive
* Manual validation — full control, no dependency, high boilerplate
## Decision Outcome
Chosen option: **`go-playground/validator/v10`** because it is the de-facto standard in the Go ecosystem, supports struct-tag annotations, provides field-level error detail, and integrates cleanly with our interface-based design.
## Implementation
`github.com/go-playground/validator/v10 v10.30.2` is present in `go.mod`.
The `pkg/validation/` package wraps the validator for reuse across handlers.
## Links
* [go-playground/validator GitHub](https://github.com/go-playground/validator)

View File

@@ -378,68 +378,6 @@ Added to `.gitea/workflows/go-ci-cd.yaml` lint-format job:
# Format swagger comments manually
swag fmt
# Format is automatically run in:
# - pre-commit hook
# - CI/CD lint-format job
```
=======
### Final Implementation
```bash
# 1. Install swaggo
go install github.com/swaggo/swag/cmd/swag@latest
# 2. Add swagger metadata to main.go
// @title dance-lessons-coach API
// @version 1.0
// @description API for dance-lessons-coach service
// @host localhost:8080
// @BasePath /api
package main
```
### Swag Formatting Integration
To ensure consistent swagger comment formatting, we've integrated `swag fmt` into our workflow:
#### Git Hooks
Added to `.git/hooks/pre-commit`:
```bash
# Run swag fmt to format swagger comments
echo "Running swag fmt..."
if command -v swag >/dev/null 2>&1; then
swag fmt
if [ $? -ne 0 ]; then
echo "ERROR: swag fmt failed"
exit 1
fi
else
echo "swag not installed, skipping swag fmt"
fi
```
#### CI/CD Integration
Added to `.gitea/workflows/go-ci-cd.yaml` lint-format job:
```yaml
- name: Install swag
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Run swag fmt
run: swag fmt
```
#### Benefits
- **Consistent Formatting**: Automatic formatting of swagger comments
- **Pre-Commit Validation**: Catches issues before commit
- **CI/CD Enforcement**: Ensures formatting in all pull requests
- **Team Consistency**: Everyone follows the same rules
- **Automatic Fixes**: Issues are fixed automatically
#### Usage
```bash
# Format swagger comments manually
swag fmt
# Format is automatically run in:
# - pre-commit hook
# - CI/CD lint-format job

View File

@@ -0,0 +1,44 @@
# 14. gRPC Adoption Strategy
* Status: Rejected / Deferred
* Deciders: Gabriel Radureau, AI Agent
* Date: 2026-04-05
## Context and Problem Statement
As the API grows, gRPC was evaluated as an alternative or complement to REST for internal service communication. The question was whether to adopt gRPC alongside the existing Chi REST API.
## Decision Drivers
* Performance of inter-service communication
* Type safety via Protocol Buffers
* Streaming support
* Team familiarity and operational overhead
## Considered Options
* **Hybrid REST/gRPC** — add gRPC endpoints alongside existing REST endpoints
* **REST only** — maintain current Chi router approach
* **gRPC-first with transcoding** — use bufbuild/connect for unified REST+gRPC
## Decision Outcome
Chosen option: **REST only (deferred)**. gRPC adoption is not warranted at the current scale. The application has a small number of endpoints, a single-binary deployment model, and no internal service mesh that would benefit from gRPC's efficiency.
### Reasons for deferral
1. **No inter-service communication today** — the application is a single binary; gRPC's main benefit (efficient binary RPC between services) does not apply
2. **Complexity cost** — adding Protobuf toolchain, code generation, and a second transport layer would significantly increase cognitive overhead
3. **Chi router commitment** — the REST API is well-designed with OpenAPI documentation; introducing gRPC in parallel creates dual-maintenance burden
4. **Team capacity** — limited bandwidth for large architectural changes
## When to reconsider
* Application evolves into multiple services that need efficient internal RPC
* Streaming use cases emerge (real-time lesson progress, etc.)
* External consumers explicitly require gRPC endpoints
## Links
* [ADR-0002: Chi Router](0002-chi-router.md)
* [ADR-0013: OpenAPI/Swagger Toolchain](0013-openapi-swagger-toolchain.md)

View File

@@ -222,7 +222,7 @@ dance-lessons-coach config validate
---
**Status:** Proposed
**Next Review:** 2026-04-12
**Status:** Accepted
**Implementation Date:** 2026-04-05
**Implementation Owner:** Arcodange Team
**Approvers Needed:** @gabrielradureau
**Approved by:** @gabrielradureau

View File

@@ -1,7 +1,8 @@
# 18. User Management and Authentication System
**Date:** 2024-04-06
**Status:** Proposed
**Date:** 2026-04-06
**Status:** Accepted
**Implementation Date:** 2026-04-08
**Authors:** Product Owner
**Decision Drivers:** Security, User Personalization, Admin Functionality

View File

@@ -1,10 +1,13 @@
# 19. PostgreSQL Database Integration
**Date:** 2024-04-07
**Status:** Proposed
**Date:** 2026-04-07
**Status:** Accepted (Partial)
**Implementation Date:** 2026-04-08
**Authors:** Product Owner
**Decision Drivers:** Data Persistence, Scalability, Production Readiness
> **⚠️ Pending cleanup:** `pkg/user/sqlite_repository.go` and `gorm.io/driver/sqlite` still present in the codebase. The ADR requires their removal, but no Gitea issue tracks this yet. The PostgreSQL implementation (`pkg/user/postgres_repository.go`) is complete and in use.
## Context
The dance-lessons-coach application currently uses SQLite with GORM for the user management system (ADR 0018), but since there are no existing users or production data, we can implement PostgreSQL directly as our primary database without migration concerns.

View File

@@ -1,11 +1,13 @@
# 10. JWT Secret Retention Policy
# 21. JWT Secret Retention Policy
## Status
**Proposed** 🟡
> **Note:** Basic JWT multi-secret support and graceful rotation are implemented in `pkg/jwt/jwt_secret_manager.go`. The retention cleanup policy (background job, configurable TTL factor) proposed in this ADR is **not yet implemented**.
## Context
The dance-lessons-coach application requires a robust JWT secret management system that balances security and user experience. As implemented in [ADR-0009](0009-hybrid-testing-approach.md), the system supports multiple JWT secrets for graceful rotation. However, the current implementation lacks a clear policy for secret retention and cleanup.
The dance-lessons-coach application requires a robust JWT secret management system that balances security and user experience. The system supports multiple JWT secrets for graceful rotation. However, the current implementation lacks a clear policy for secret retention and cleanup.
### Current State
@@ -386,8 +388,8 @@ func maskSecret(secret string) string {
## References
- [ADR-0009: Hybrid Testing Approach](0009-hybrid-testing-approach.md)
- [ADR-0008: BDD Testing](0008-bdd-testing.md)
- [ADR-0018: User Management and Auth System](0018-user-management-auth-system.md)
- [RFC 7519: JSON Web Tokens](https://tools.ietf.org/html/rfc7519)
- [OWASP Key Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html)

View File

@@ -3,6 +3,8 @@
## Status
**Proposed** 🟡
> **⚠️ Not yet implemented.** Gitea issue #13 ("feat: Implement Rate Limiting and Caching Strategy") is open and tracks this work. `go-cache`, `redis`, and `ulule/limiter` are absent from `go.mod`. The phase checkboxes below are corrected to reflect actual status.
## Context
As the dance-lessons-coach application grows and potentially serves multiple users simultaneously, we need to implement rate limiting to:
@@ -284,38 +286,38 @@ func GetCacheKey(prefix, entityType, entityID string) string {
## Implementation Phases
### Phase 1: In-Memory Cache (Current Sprint)
- Research and select in-memory cache library
- Implement cache interface and in-memory service
- Add cache configuration to config package
- Implement basic cache operations (set, get, delete)
- Add TTL support and automatic cleanup
- Cache JWT validation results
- Add cache metrics and monitoring
- Research and select in-memory cache library
- Implement cache interface and in-memory service
- Add cache configuration to config package
- Implement basic cache operations (set, get, delete)
- Add TTL support and automatic cleanup
- Cache JWT validation results
- Add cache metrics and monitoring
### Phase 2: Redis-Compatible Cache (Next Sprint)
- Set up Dragonfly/KeyDB in development environment
- Implement Redis cache service
- Add configuration for Redis connection
- Implement cache fallback strategy (Redis → in-memory)
- Add health checks for Redis connection
- Implement distributed cache invalidation
- Set up Dragonfly/KeyDB in development environment
- Implement Redis cache service
- Add configuration for Redis connection
- Implement cache fallback strategy (Redis → in-memory)
- Add health checks for Redis connection
- Implement distributed cache invalidation
### Phase 3: Rate Limiting (Following Sprint)
- Research and select rate limiting library
- Implement rate limiter service
- Add rate limit configuration
- Implement Chi middleware for rate limiting
- Add rate limit headers to responses
- Implement IP whitelisting
- Add endpoint-specific rate limits
- Research and select rate limiting library
- Implement rate limiter service
- Add rate limit configuration
- Implement Chi middleware for rate limiting
- Add rate limit headers to responses
- Implement IP whitelisting
- Add endpoint-specific rate limits
### Phase 4: Advanced Features (Future)
- Cache warming for critical data
- Two-level caching (Redis + in-memory)
- Cache compression for large objects
- Rate limit exemptions for admin users
- Dynamic rate limit adjustment
- Cache analytics and usage patterns
- Cache warming for critical data
- Two-level caching (Redis + in-memory)
- Cache compression for large objects
- Rate limit exemptions for admin users
- Dynamic rate limit adjustment
- Cache analytics and usage patterns
## Configuration

View File

@@ -4,6 +4,8 @@
* Deciders: Gabriel Radureau, AI Agent
* Date: 2026-04-05
> **⚠️ Not yet implemented.** No `ConfigManager` exists in `pkg/config/` and Viper's `WatchConfig()` is not wired up. However, `features/config/config_hot_reloading.feature` has been written — BDD scenarios exist for a feature that is not yet built. Those tests are expected to fail until implementation begins.
## Context and Problem Statement
The dance-lessons-coach application currently loads configuration once at startup using Viper, which supports file-based configuration, environment variables, and defaults. However, the current implementation does not support runtime configuration changes without restarting the application.

View File

@@ -1,7 +1,7 @@
# ADR 0024: BDD Test Organization and Isolation Strategy
## Status
**Proposed** 🟡
**Accepted**
## Context

View File

@@ -1,7 +1,10 @@
# ADR 0025: BDD Scenario Isolation Strategies
## Status
**Proposed** 🟡
**Accepted (Partial)** 🟡
Phase 1 (schema-per-scenario DB isolation + `ScenarioState` manager in `pkg/bdd/steps/scenario_state.go`) is implemented.
Phase 2 (cache key prefix strategy, in-memory store `Reset()` methods) is pending — blocked on ADR-0022 (rate limiting/cache) not yet implemented.
## Context

View File

@@ -13,24 +13,24 @@ This directory contains Architecture Decision Records (ADRs) for the dance-lesso
| 0005 | Graceful Shutdown | ✅ Accepted |
| 0006 | Configuration Management | ✅ Accepted |
| 0007 | OpenTelemetry Integration | ✅ Accepted |
| 0008 | BDD Testing | ✅ Accepted |
| 0009 | Hybrid Testing Approach | ✅ Accepted |
| 0010 | CI/CD Pipeline Design | ✅ Accepted |
| 0011 | Trunk-Based Development | ✅ Accepted |
| 0012 | Commit Message Conventions | ✅ Accepted |
| 0013 | Version Management Lifecycle | ✅ Accepted |
| 0014 | Swagger Documentation | ✅ Accepted |
| 0015 | Rate Limiting Strategy | ✅ Accepted |
| 0016 | Cache Invalidation Strategy | ✅ Accepted |
| 0017 | JWT Secret Rotation | ✅ Accepted |
| 0018 | Configuration Hot Reloading | ✅ Accepted |
| 0019 | BDD Feature Structure | ✅ Accepted |
| 0020 | Database Migration Strategy | ✅ Accepted |
| 0021 | API Versioning Strategy | ✅ Accepted |
| 0022 | Rate Limiting and Cache Strategy | ✅ Accepted |
| 0023 | Config Hot Reloading | 🟡 Proposed |
| 0024 | BDD Test Organization and Isolation | 🟡 Proposed |
| 0025 | BDD Scenario Isolation Strategies | 🟡 Proposed |
| 0008 | BDD Testing with Godog | ✅ Accepted (structure superseded by 0024) |
| 0009 | BDD Testing with OpenAPI Documentation | ✅ Accepted |
| 0010 | API v2 Feature Flag | ✅ Accepted |
| 0011 | Validation Library (go-playground/validator) | ✅ Accepted |
| 0012 | Git Hooks: Staged-Only Formatting | ✅ Accepted |
| 0013 | OpenAPI/Swagger Toolchain (swaggo/swag) | ✅ Accepted |
| 0014 | gRPC Adoption Strategy | ❌ Rejected / Deferred |
| 0015 | CLI Subcommands with Cobra | ✅ Accepted |
| 0016 | CI/CD Pipeline Design | ✅ Accepted |
| 0017 | Trunk-Based Development Workflow | ✅ Accepted |
| 0018 | User Management and Auth System | ✅ Accepted |
| 0019 | PostgreSQL Integration | ✅ Accepted (SQLite cleanup pending) |
| 0020 | Docker Build Strategy | ✅ Accepted |
| 0021 | JWT Secret Retention Policy | 🟡 Proposed (base JWT done; cleanup job not implemented) |
| 0022 | Rate Limiting and Cache Strategy | 🟡 Proposed (not implemented — Gitea issue #13) |
| 0023 | Config Hot Reloading | 🟡 Proposed (not implemented) |
| 0024 | BDD Test Organization and Isolation | ✅ Accepted |
| 0025 | BDD Scenario Isolation Strategies | ✅ Accepted (Partial — Phase 2 pending ADR-0022) |
## What is an ADR?
@@ -96,23 +96,24 @@ Chosen option: "[Option 1]" because [justification]
* [0005-graceful-shutdown.md](0005-graceful-shutdown.md) - Implement graceful shutdown with readiness endpoints
* [0006-configuration-management.md](0006-configuration-management.md) - Use Viper for configuration management
* [0007-opentelemetry-integration.md](0007-opentelemetry-integration.md) - Integrate OpenTelemetry for distributed tracing
* [0008-bdd-testing.md](0008-bdd-testing.md) - Adopt BDD with Godog for behavioral testing
* [0009-hybrid-testing-approach.md](0009-hybrid-testing-approach.md) - Combine BDD and Swagger-based testing
* [0008-bdd-testing.md](0008-bdd-testing.md) - Adopt BDD with Godog for behavioral testing (structure superseded by 0024)
* [0009-hybrid-testing-approach.md](0009-hybrid-testing-approach.md) - BDD testing with OpenAPI documentation (SDK layer deferred)
* [0010-api-v2-feature-flag.md](0010-api-v2-feature-flag.md) - API v2 implementation with feature flag control
* [0011-validation-library-selection.md](0011-validation-library-selection.md) - Selection of go-playground/validator for input validation
* [0012-git-hooks-staged-only-formatting.md](0012-git-hooks-staged-only-formatting.md) - Git hooks format only staged Go files
* [0013-openapi-swagger-toolchain.md](0013-openapi-swagger-toolchain.md) - OpenAPI/Swagger documentation with swaggo/swag (Implemented)
* [0014-grpc-adoption-strategy.md](0014-grpc-adoption-strategy.md) - Hybrid REST/gRPC adoption strategy
* [0013-openapi-swagger-toolchain.md](0013-openapi-swagger-toolchain.md) - OpenAPI/Swagger documentation with swaggo/swag
* [0014-grpc-adoption-strategy.md](0014-grpc-adoption-strategy.md) - gRPC adoption strategy (rejected/deferred)
* [0015-cli-subcommands-cobra.md](0015-cli-subcommands-cobra.md) - Cobra CLI framework adoption
* [0016-ci-cd-pipeline-design.md](0016-ci-cd-pipeline-design.md) - CI/CD pipeline architecture
* [0017-trunk-based-development-workflow.md](0017-trunk-based-development-workflow.md) - Trunk-based development workflow
* [0018-user-management-auth-system.md](0018-user-management-auth-system.md) - User management and authentication system
* [0019-postgresql-integration.md](0019-postgresql-integration.md) - PostgreSQL database integration
* [0020-docker-build-strategy.md](0020-docker-build-strategy.md) - Docker Build Strategy: Traditional vs Buildx
* [0021-jwt-secret-retention-policy.md](0021-jwt-secret-retention-policy.md) - JWT Secret Retention Policy with Configurable TTL and Retention
* [0022-rate-limiting-cache-strategy.md](0022-rate-limiting-cache-strategy.md) - Rate Limiting and Cache Strategy with Multi-Phase Implementation
* [0023-config-hot-reloading.md](0023-config-hot-reloading.md) - Config Hot Reloading Strategy
* [0025-bdd-scenario-isolation-strategies.md](0025-bdd-scenario-isolation-strategies.md) - Schema-per-scenario isolation for BDD tests
* [0021-jwt-secret-retention-policy.md](0021-jwt-secret-retention-policy.md) - JWT Secret Retention Policy (base JWT done; cleanup job proposed)
* [0022-rate-limiting-cache-strategy.md](0022-rate-limiting-cache-strategy.md) - Rate Limiting and Cache Strategy (not yet implemented — issue #13)
* [0023-config-hot-reloading.md](0023-config-hot-reloading.md) - Config Hot Reloading Strategy (not yet implemented)
* [0024-bdd-test-organization-and-isolation.md](0024-bdd-test-organization-and-isolation.md) - BDD test modular organisation by domain
* [0025-bdd-scenario-isolation-strategies.md](0025-bdd-scenario-isolation-strategies.md) - Schema-per-scenario isolation for BDD tests (partial)
## How to Add a New ADR

View File

@@ -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")
}

158
documentation/API.md Normal file
View File

@@ -0,0 +1,158 @@
# API Endpoints
REST API reference for `dance-lessons-coach`. Extracted from the original `AGENTS.md` (Tâche 6 restructure) for lazy-loading compatibility with Mistral Vibe.
## 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
**Embedded Documentation:** The OpenAPI spec is embedded in the binary using Go's `//go:embed` directive for single-binary deployment.
---
## Health Check
```http
GET /api/health
```
**Response:**
```json
{"status":"healthy"}
```
## Version Info
```http
GET /api/version
GET /api/version?format=plain
GET /api/version?format=full
GET /api/version?format=json
```
Returns the running binary version (injected at build time via `-ldflags`). The `format` query parameter controls the response shape:
- `format=plain` (or `?format=short`): plain text version (e.g. `1.0.0`)
- `format=full`: detailed multi-line text (Version, Commit, Built date, Go version)
- `format=json` (default): structured JSON `{"version": "1.0.0", "commit": "abc1234", "built": "...", "go_version": "go1.26.1"}`
## 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 v1
```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/v1/greet/Alice
# Response: {"message":"Hello Alice!"}
```
## Greet Service v2 (Feature-flagged)
```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)"}]}
```
**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`.

251
documentation/CLI.md Normal file
View File

@@ -0,0 +1,251 @@
# CLI Management Guide
Complete reference for the `dance-lessons-coach` CLI, server lifecycle, and configuration. Extracted from the original `AGENTS.md` (Tâche 6 restructure) for lazy-loading compatibility with Mistral Vibe.
## Cobra CLI (Recommended)
`dance-lessons-coach` includes a modern CLI built with Cobra:
```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
```
For full version management workflow (bump, release, build with version), see [`version-management-guide.md`](version-management-guide.md).
## Server Control Script
A shell script manages the server lifecycle:
```bash
cd /Users/gabrielradureau/Work/Vibe/DanceLessonsCoach
./scripts/start-server.sh start # Start the server
./scripts/start-server.sh status # Check server status
./scripts/start-server.sh test # Test API endpoints
./scripts/start-server.sh logs # View server logs
./scripts/start-server.sh stop # Stop the server
./scripts/start-server.sh restart # Restart
```
**Available subcommands:**
- `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
## Manual Server Management
For direct control:
```bash
cd /Users/gabrielradureau/Work/Vibe/DanceLessonsCoach
./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
Configuration via environment variables with `DLC_` prefix:
| Option | Environment Variable | Default | 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) |
**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
# Enable JSON logging
export DLC_LOGGING_JSON=true
# Log to file
export DLC_LOGGING_OUTPUT="server.log"
# Combined: JSON logging to file
export DLC_LOGGING_JSON=true
export DLC_LOGGING_OUTPUT="server.json.log"
```
**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`:
```yaml
server:
host: "0.0.0.0"
port: 8080
shutdown:
timeout: 30s
logging:
json: false
```
**Configuration Loading Precedence:**
1. **File-based configuration** (highest precedence)
2. **Environment variables** (override defaults, overridden by config file)
3. **Default values** (fallback)
All configuration is validated on startup. Invalid configurations cause server startup failure. Configuration values and source are logged at startup.
**Verification:**
```bash
DLC_SERVER_PORT=9090 DLC_SERVER_HOST="127.0.0.1" ./scripts/start-server.sh start
curl http://127.0.0.1:9090/api/health
# Expected: {"status":"healthy"}
```
## Server Status
```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}` (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
```

View File

@@ -0,0 +1,59 @@
# Code Examples
Snippets and patterns used across the `dance-lessons-coach` codebase. Extracted from the original `AGENTS.md` (Tâche 6 restructure).
## Adding a 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
}
```
## Logging with Zerolog
```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")
```
For the full logging strategy (when to use Trace vs Info, performance considerations), see [ADR-0003 — Zerolog Logging](../adr/0003-zerolog-logging.md).
## Using `context.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()
```
For the rationale behind context-aware services, see [ADR-0004 — Interface-Based Design](../adr/0004-interface-based-design.md).
## Best Practices Reminders
For higher-level guidance on code organization, error handling, performance, and testing, see [`AGENT_USAGE_GUIDE.md`](AGENT_USAGE_GUIDE.md#best-practices) section "Best Practices".

83
documentation/HISTORY.md Normal file
View File

@@ -0,0 +1,83 @@
# Development History
This document records the historical development phases of `dance-lessons-coach`. Extracted from the original `AGENTS.md` (Tâche 6 restructure) for lazy-loading compatibility with Mistral Vibe (128k context).
All phases below are **completed** ✅. They are kept here for traceability and onboarding context — refer to ADRs (`adr/`) for the technical decisions behind each phase.
## Phase 1: Foundation
- 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
- Chi router integration
- Versioned API endpoints (`/api/v1`)
- Health endpoint (`/api/health`)
- JSON responses with proper headers
## Phase 3: Logging & Architecture
- Zerolog integration with Trace level
- Context-aware logging
- Interface-based design patterns
- Dependency injection
## Phase 4: Documentation & Testing
- Comprehensive `AGENTS.md`
- `README.md` with usage instructions
- Server management guide
- API endpoint documentation
## Phase 5: Configuration Management
- 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
- 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
- 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
- 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
- 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
## Beyond Phase 9
Subsequent work (CI/CD, BDD scenarios, ADR audit, JWT, config hot-reloading) is tracked in the [Changelog](../CHANGELOG.md) and the corresponding [ADRs](../adr/).

View File

@@ -0,0 +1,94 @@
# Observability — OpenTelemetry & Jaeger Integration
Tracing setup for `dance-lessons-coach`. Extracted from the original `AGENTS.md` (Tâche 6 restructure) for lazy-loading compatibility with Mistral Vibe.
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:
```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
```
## Testing with Jaeger
**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
| Sampler | Behavior |
|---|---|
| `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
A convenience script is provided:
```bash
./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
## ADR Reference
See [ADR-0007 — OpenTelemetry Integration](../adr/0007-opentelemetry-integration.md) for the full architectural decision and rationale (middleware-only approach, sampling strategy, OTLP/gRPC choice).

40
documentation/ROADMAP.md Normal file
View File

@@ -0,0 +1,40 @@
# Roadmap & Future Enhancements
Tracking pending features and architectural improvements. Extracted from the original `AGENTS.md` (Tâche 6 restructure). Status updated continuously — items move to "Completed Features" section once shipped.
## 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
## How to Propose a New Feature
1. Open a Gitea issue describing the use case and acceptance criteria
2. If the feature implies an architectural decision, draft an ADR (`adr/<NNNN>-<slug>.md`) following the template
3. Reference the ADR + issue in any PR introducing the feature
4. Update this roadmap (move from "Potential" to "Completed" when shipped)

View File

@@ -0,0 +1,107 @@
# Troubleshooting
Common issues and their resolution. Extracted from the original `AGENTS.md` and merged with relevant sections from `AGENT_USAGE_GUIDE.md` and `BDD_GUIDE.md`. Refer back to those guides for context-specific troubleshooting (agent workflows, BDD test failures).
## Port Already in Use
```bash
# Find and kill process using port 8080
kill -TERM $(lsof -ti :8080)
# Force kill if graceful does not work
kill -9 $(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
# View recent logs
./scripts/start-server.sh logs
```
If health endpoint returns connection refused, the server may have crashed. Check logs in `./scripts/start-server.sh logs` for stack traces.
## Dependency Issues
```bash
# Clean and rebuild
go mod tidy
go build ./...
# If dependency version conflicts persist
go mod download
go mod verify
```
## Tests Failing
### Unit tests
```bash
# Run with verbose output
go test -v ./...
# Check specific test
go test ./pkg/greet/ -run TestName
```
### BDD tests
See [`BDD_GUIDE.md`](BDD_GUIDE.md) for the full BDD troubleshooting workflow (Godog setup, scenario isolation, step matching). Common BDD issues:
- **Step not found** → check `pkg/bdd/steps/` for the step definition file
- **Scenario state leaking** → review [ADR-0025](../adr/0025-bdd-scenario-isolation-strategies.md) for the isolation pattern
- **Database not reset** → ensure the test fixtures cleanup runs (BDD scenario After hooks)
## Configuration Not Loading
The application logs the configuration source at startup. Check logs for:
```
[INF] Configuration loaded from: file:config.yaml
# or
[INF] Configuration loaded from: env
# or
[INF] Configuration loaded from: defaults
```
If config is not loading as expected:
1. Verify file exists and is readable: `ls -la config.yaml`
2. Verify env vars are exported: `env | grep DLC_`
3. Check for typos in keys (case-sensitive)
4. Review [`AGENT_USAGE_GUIDE.md`](AGENT_USAGE_GUIDE.md) section "Configuration troubleshooting"
## OpenTelemetry Not Tracing
1. Verify Jaeger is running: `docker ps | grep jaeger`
2. Check `DLC_TELEMETRY_ENABLED=true` in environment or `telemetry.enabled: true` in config
3. Verify OTLP endpoint reachable: `nc -zv localhost 4317`
4. Check sampler is not `always_off`
5. See [`OBSERVABILITY.md`](OBSERVABILITY.md) for full setup
## Build Failures
```bash
# Clear caches
go clean -cache -modcache
go mod download
# Rebuild
go build ./...
```
If errors persist, see [`local-ci-cd-testing.md`](local-ci-cd-testing.md) for the CI/CD pipeline that mirrors the production build.
## Where to Look Next
- **Agent-specific issues** (vibe, mistral, programmer agent) → [`AGENT_USAGE_GUIDE.md`](AGENT_USAGE_GUIDE.md)
- **BDD-specific issues** → [`BDD_GUIDE.md`](BDD_GUIDE.md)
- **Version/release issues** → [`version-management-guide.md`](version-management-guide.md)
- **CI/CD issues** → [`local-ci-cd-testing.md`](local-ci-cd-testing.md)

View File

@@ -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().

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"