mirror of
https://github.com/btouchard/ackify.git
synced 2026-02-14 18:51:13 -06:00
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.
111 lines
2.6 KiB
Go
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))
|
|
})
|
|
}
|