refactor: wire centralized job scheduler and custom request logging in main.go

Replace inline cleanup goroutines and default chi logger with centralized jobs scheduler and custom colored request logger.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-07-07 00:10:09 +01:00
co-authored by Sisyphus
parent e86248b27c
commit 27be7cea91
+84 -29
View File
@@ -4,6 +4,8 @@ import (
"context" "context"
"crussell/auth" "crussell/auth"
"crussell/internal/dav" "crussell/internal/dav"
"crussell/internal/jobs"
"crussell/internal/logutil"
"crussell/internal/s3" "crussell/internal/s3"
"crussell/internal/square" "crussell/internal/square"
"encoding/json" "encoding/json"
@@ -13,6 +15,7 @@ import (
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"sync/atomic"
"syscall" "syscall"
"time" "time"
@@ -65,13 +68,35 @@ func limitBody(limit int64) func(http.Handler) http.Handler {
} }
const ( const (
defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB
uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB
portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source) portfolioBodyLimit int64 = 40 * 1024 * 1024 // 40MB (7 variants from 20MB source)
reservationCleanupInterval = 5 * time.Minute
reservationCleanupTimeout = 30 * time.Second
) )
// nColor / bColor — Chi-style ANSI colors for request logging.
type nColor string
type bColor string
var (
reset = nColor(logutil.Reset)
nGreen = nColor(logutil.Green)
nYellow = nColor(logutil.Yellow)
nCyan = nColor(logutil.Cyan)
nRed = nColor(logutil.Red)
bGreen = bColor(logutil.BoldGreen)
bYellow = bColor(logutil.BoldYellow)
bRed = bColor(logutil.BoldRed)
bBlue = bColor(logutil.BoldBlue)
bMagenta = bColor(logutil.BoldMagenta)
debugLvl = nColor(logutil.DebugLvl)
warnLvl = nColor(logutil.WarnLvl)
errorLvl = nColor(logutil.ErrorLvl)
)
var apiLog = log.New(os.Stdout, "", log.LstdFlags)
func initDB() { func initDB() {
if err := db.Connect(); err != nil { if err := db.Connect(); err != nil {
log.Fatal("Failed to connect to DB:", err) log.Fatal("Failed to connect to DB:", err)
@@ -139,14 +164,63 @@ func main() {
initDav() initDav()
initS3() initS3()
initSquare() initSquare()
auth.StartJTICleanup()
sched := jobs.New()
jobs.RegisterAll(sched)
sched.Start()
r := chi.NewRouter() r := chi.NewRouter()
// --- Global Middleware --- // --- Global Middleware ---
r.Use(middleware.RequestID) // Custom RequestID using shared random prefix (matches jobs scheduler)
var reqCounter atomic.Uint64
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
myid := reqCounter.Add(1)
requestID := fmt.Sprintf("%s/%s-%06d", jobs.Hostname(), jobs.RandomPrefix(), myid)
ctx := context.WithValue(r.Context(), middleware.RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
})
r.Use(middleware.ClientIPFromHeader("X-Real-IP")) r.Use(middleware.ClientIPFromHeader("X-Real-IP"))
r.Use(middleware.Logger) r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
t1 := time.Now()
defer func() {
status := ww.Status()
bytes := ww.BytesWritten()
reqID := middleware.GetReqID(r.Context())
var level nColor
var statusColor bColor
switch {
case status >= 500:
level, statusColor = errorLvl, bRed
case status >= 400:
level, statusColor = warnLvl, bYellow
default:
level, statusColor = debugLvl, bGreen
}
clientIP := middleware.GetClientIP(r.Context())
if clientIP == "" {
clientIP = r.RemoteAddr
}
apiLog.Printf("%s %s%s%s \"%s%s %s%s %s%s\" from %s - %s %s%03d%s %s%dB%s in %s",
level,
nYellow, reqID, reset,
bMagenta, r.Method, nCyan, r.URL.String(), nCyan, r.Proto, reset,
clientIP,
statusColor, status, reset,
bBlue, bytes, reset,
logutil.ColoredDuration(time.Since(t1)),
)
}()
next.ServeHTTP(ww, r)
})
})
r.Use(middleware.Recoverer) r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(15 * time.Second)) r.Use(middleware.Timeout(15 * time.Second))
r.Use(func(next http.Handler) http.Handler { r.Use(func(next http.Handler) http.Handler {
@@ -429,26 +503,6 @@ r.Route("/admin/users", func(r chi.Router) {
// Webhooks (no auth - Square sends to base path) // Webhooks (no auth - Square sends to base path)
r.Post("/webhooks/square", webhooks.HandleSquareWebhook) r.Post("/webhooks/square", webhooks.HandleSquareWebhook)
// Background cleanup of expired reservations
cleanupCtx, cleanupStop := context.WithCancel(context.Background())
go func() {
ticker := time.NewTicker(reservationCleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
ctx, cancel := context.WithTimeout(context.Background(), reservationCleanupTimeout)
if err := scheduling.CleanupOldReservations(ctx); err != nil {
log.Printf("reservation cleanup error: %v", err)
}
cancel()
case <-cleanupCtx.Done():
log.Println("reservation cleanup goroutine stopped")
return
}
}
}()
srv := &http.Server{ srv := &http.Server{
Addr: ":8080", Addr: ":8080",
Handler: r, Handler: r,
@@ -464,7 +518,8 @@ r.Route("/admin/users", func(r chi.Router) {
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Printf("Server forced to shutdown: %v", err) log.Printf("Server forced to shutdown: %v", err)
} }
cleanupStop() <-sched.Shutdown()
log.Println("Background jobs stopped")
}() }()
fmt.Println("Server is listening on :8080") fmt.Println("Server is listening on :8080")