Files
ackify/backend/internal/presentation/api/shared/logging.go
Benjamin e95185f9c7 feat: migrate to Vue.js SPA with API-first architecture
Major refactoring to modernize the application architecture:

Backend changes:
- Restructure API with v1 versioning and modular handlers
- Add comprehensive OpenAPI specification
- Implement RESTful endpoints for documents, signatures, admin
- Add checksum verification system for document integrity
- Add server-side runtime injection of ACKIFY_BASE_URL and meta tags
- Generate dynamic Open Graph/Twitter Card meta tags for unfurling
- Remove legacy HTML template handlers
- Isolate backend source on dedicated folder
- Improve tests suite

Frontend changes:
- Migrate from Go templates to Vue.js 3 SPA with TypeScript
- Add Tailwind CSS with shadcn/vue components
- Implement i18n support (fr, en, es, de, it)
- Add admin dashboard for document and signer management
- Add signature tracking with file checksum verification
- Add embed page with sign button linking to main app
- Implement dark mode and accessibility features
- Auto load file to compute checksum

Infrastructure:
- Update Dockerfile for SPA build process
- Simplify deployment with embedded frontend assets
- Add migration for checksum_verifications table

This enables better UX, proper link previews on social platforms,
and provides a foundation for future enhancements.
2025-10-26 02:32:10 +02:00

111 lines
2.6 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-or-later
package shared
import (
"context"
"net/http"
"time"
"github.com/go-chi/chi/v5/middleware"
"github.com/btouchard/ackify-ce/backend/pkg/logger"
)
// responseWriter is a wrapper around http.ResponseWriter that captures the status code
type responseWriter struct {
http.ResponseWriter
status int
wroteHeader bool
}
func wrapResponseWriter(w http.ResponseWriter) *responseWriter {
return &responseWriter{ResponseWriter: w}
}
func (rw *responseWriter) Status() int {
return rw.status
}
func (rw *responseWriter) WriteHeader(code int) {
if rw.wroteHeader {
return
}
rw.status = code
rw.ResponseWriter.WriteHeader(code)
rw.wroteHeader = true
}
// RequestLogger middleware logs all API requests with structured logging
func RequestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := getRequestID(r.Context())
// Log request start in DEBUG
logger.Logger.Debug("api_request_start",
"request_id", requestID,
"method", r.Method,
"path", r.URL.Path,
"remote_addr", r.RemoteAddr,
"user_agent", r.UserAgent())
wrapped := wrapResponseWriter(w)
next.ServeHTTP(wrapped, r)
// Log request completion
duration := time.Since(start)
status := wrapped.status
if status == 0 {
status = 200
}
fields := []interface{}{
"request_id", requestID,
"method", r.Method,
"path", r.URL.Path,
"status", status,
"duration_ms", duration.Milliseconds(),
}
// Add user email if available
if user, ok := GetUserFromContext(r.Context()); ok {
fields = append(fields, "user_email", user.Email)
}
// Log at appropriate level based on status
if status >= 500 {
logger.Logger.Error("api_request_error", fields...)
} else if status >= 400 {
logger.Logger.Warn("api_request_client_error", fields...)
} else {
logger.Logger.Info("api_request_complete", fields...)
}
})
}
// Helper functions
func getRequestID(ctx context.Context) string {
if requestID, ok := ctx.Value(ContextKeyRequestID).(string); ok {
return requestID
}
return ""
}
func errToString(err error) string {
if err == nil {
return ""
}
return err.Error()
}
// AddRequestIDToContext middleware adds the request ID from chi middleware to our context
func AddRequestIDToContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := middleware.GetReqID(r.Context())
ctx := context.WithValue(r.Context(), ContextKeyRequestID, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}