📝 docs: update comprehensive documentation and project infrastructure

Documentation Updates:
- Enhanced AGENTS.md with user authentication details
- Updated README.md with authentication API documentation
- Added CONTRIBUTING.md guidelines for BDD testing
- Version management guide improvements
- Local CI/CD testing documentation

Project Infrastructure:
- Updated .gitignore for new file patterns
- Enhanced git hooks documentation
- YAML linting configuration
- Script improvements and organization
- Configuration management updates

API Enhancements:
- Greet service integration with authentication
- Server middleware for JWT validation
- Telemetry improvements
- Version management utilities

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-04-09 00:26:15 +02:00
parent 30af706590
commit c1e628f339
39 changed files with 1230 additions and 1187 deletions

View File

@@ -20,8 +20,11 @@ import (
"dance-lessons-coach/pkg/config"
"dance-lessons-coach/pkg/greet"
"dance-lessons-coach/pkg/telemetry"
"dance-lessons-coach/pkg/user"
userapi "dance-lessons-coach/pkg/user/api"
"dance-lessons-coach/pkg/validation"
"dance-lessons-coach/pkg/version"
"encoding/json"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
@@ -37,6 +40,8 @@ type Server struct {
config *config.Config
tracerProvider *sdktrace.TracerProvider
validator *validation.Validator
userRepo user.UserRepository
userService user.UserService
}
func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
@@ -48,17 +53,46 @@ func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
log.Trace().Msg("Validator created successfully")
}
// Initialize user repository and services
userRepo, userService, err := initializeUserServices(cfg)
if err != nil {
log.Warn().Err(err).Msg("Failed to initialize user services, user functionality will be disabled")
}
s := &Server{
router: chi.NewRouter(),
readyCtx: readyCtx,
withOTEL: cfg.GetTelemetryEnabled(),
config: cfg,
validator: validator,
router: chi.NewRouter(),
readyCtx: readyCtx,
withOTEL: cfg.GetTelemetryEnabled(),
config: cfg,
validator: validator,
userRepo: userRepo,
userService: userService,
}
s.setupRoutes()
return s
}
// initializeUserServices initializes the user repository and unified user service
func initializeUserServices(cfg *config.Config) (user.UserRepository, user.UserService, error) {
// Create user repository using PostgreSQL
repo, err := user.NewPostgresRepository(cfg)
if err != nil {
return nil, nil, fmt.Errorf("failed to create PostgreSQL user repository: %w", err)
}
// Create JWT config
jwtConfig := user.JWTConfig{
Secret: cfg.GetJWTSecret(),
ExpirationTime: time.Hour * 24, // 24 hours
Issuer: "dance-lessons-coach",
}
// Create unified user service
userService := user.NewUserService(repo, jwtConfig, cfg.GetAdminMasterPassword())
return repo, userService, nil
}
func (s *Server) setupRoutes() {
// Use Zerolog middleware instead of Chi's default logger
s.router.Use(middleware.RequestLogger(&middleware.DefaultLogFormatter{
@@ -109,9 +143,31 @@ func (s *Server) setupRoutes() {
func (s *Server) registerApiV1Routes(r chi.Router) {
greetService := greet.NewService()
greetHandler := greet.NewApiV1GreetHandler(greetService)
// Create auth middleware if available
var authMiddleware *AuthMiddleware
if s.userService != nil {
authMiddleware = NewAuthMiddleware(s.userService)
}
r.Route("/greet", func(r chi.Router) {
// Add optional authentication middleware
if authMiddleware != nil {
r.Use(authMiddleware.Middleware)
}
greetHandler.RegisterRoutes(r)
})
// Register user authentication routes
if s.userService != nil && s.userRepo != nil {
// Use unified user service - much simpler!
if s.userService != nil {
handler := userapi.NewAuthHandler(s.userService, s.userService, s.validator)
r.Route("/auth", func(r chi.Router) {
handler.RegisterRoutes(r)
})
}
}
}
func (s *Server) registerApiV2Routes(r chi.Router) {
@@ -155,24 +211,75 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
// handleReadiness godoc
//
// @Summary Readiness check
// @Description Check if the service is ready to accept traffic
// @Description Check if the service is ready to accept traffic including detailed connection status
// @Tags System/Health
// @Accept json
// @Produce json
// @Success 200 {object} map[string]bool "Service is ready"
// @Failure 503 {object} map[string]bool "Service is not ready"
// @Success 200 {object} object "Service is ready with connection details"
// @Failure 503 {object} object "Service is not ready with failure details"
// @Router /ready [get]
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
log.Trace().Msg("Readiness check requested")
// Check if server is shutting down
select {
case <-s.readyCtx.Done():
log.Trace().Msg("Readiness check: not ready (shutting down)")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte(`{"ready":false}`))
json.NewEncoder(w).Encode(map[string]interface{}{
"ready": false,
"reason": "server_shutting_down",
"connections": map[string]interface{}{
"database": "not_checked",
},
})
return
default:
log.Trace().Msg("Readiness check: ready")
w.Write([]byte(`{"ready":true}`))
// Server is not shutting down, check all connections
connectionStatus := make(map[string]interface{})
allHealthy := true
var failureReason string
// Check database if available
if s.userRepo != nil {
if err := s.userRepo.CheckDatabaseHealth(r.Context()); err != nil {
log.Warn().Err(err).Msg("Database health check failed")
connectionStatus["database"] = map[string]interface{}{
"status": "unhealthy",
"error": err.Error(),
}
allHealthy = false
failureReason = "database_unhealthy"
} else {
connectionStatus["database"] = map[string]interface{}{
"status": "healthy",
}
}
} else {
connectionStatus["database"] = map[string]interface{}{
"status": "not_configured",
}
}
if allHealthy {
log.Trace().Msg("Readiness check: ready")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"ready": true,
"connections": connectionStatus,
})
} else {
log.Warn().Str("reason", failureReason).Msg("Readiness check: not ready")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"ready": false,
"reason": failureReason,
"connections": connectionStatus,
})
}
}
}