@@ -2,67 +2,173 @@ package main
22
33import (
44 "context"
5- "flag "
5+ "fmt "
66 "log/slog"
77 "net/http"
88 "os"
9- "time"
9+ "os/signal"
10+ "syscall"
1011
1112 mem "gamifykit/adapters/memory"
13+ redisAdapter "gamifykit/adapters/redis"
14+ sqlxAdapter "gamifykit/adapters/sqlx"
1215 "gamifykit/api/httpapi"
16+ "gamifykit/config"
1317 "gamifykit/engine"
1418 "gamifykit/gamify"
1519 "gamifykit/realtime"
1620)
1721
1822func main () {
19- var (
20- addr = flag . String ( "addr" , ":8080" , "listen address" )
21- prefix = flag . String ( "prefix" , "/api" , "HTTP API path prefix" )
22- cors = flag . String ( "cors" , "*" , "Access-Control-Allow-Origin value (empty to disable)" )
23- )
24- flag . Parse ()
23+ // Load configuration
24+ cfg , err := config . Load ( )
25+ if err != nil {
26+ fmt . Fprintf ( os . Stderr , "Failed to load configuration: %v \n " , err )
27+ os . Exit ( 1 )
28+ }
2529
26- // Configure JSON logging for production use
27- logHandler := slog .NewJSONHandler (os .Stdout , & slog.HandlerOptions {
28- Level : slog .LevelInfo ,
29- })
30- slog .SetDefault (slog .New (logHandler ))
30+ // Setup logging based on configuration
31+ setupLogging (cfg )
32+
33+ // Load secrets if in production
34+ ctx := context .Background ()
35+ if cfg .Environment == config .EnvProduction {
36+ if err := cfg .LoadSecretsFromEnv (ctx ); err != nil {
37+ slog .Error ("Failed to load secrets" , "error" , err )
38+ os .Exit (1 )
39+ }
40+ }
41+
42+ slog .Info ("starting gamifykit server" ,
43+ "environment" , cfg .Environment ,
44+ "profile" , cfg .Profile ,
45+ "address" , cfg .Server .Address ,
46+ "storage_adapter" , cfg .Storage .Adapter )
47+
48+ // Setup storage adapter
49+ storage , err := setupStorage (ctx , cfg )
50+ if err != nil {
51+ slog .Error ("Failed to setup storage" , "error" , err )
52+ os .Exit (1 )
53+ }
3154
32- // Build service with sensible defaults.
55+ // Build service
3356 hub := realtime .NewHub ()
3457 svc := gamify .New (
3558 gamify .WithRealtime (hub ),
36- gamify .WithStorage (mem . New () ),
59+ gamify .WithStorage (storage ),
3760 gamify .WithDispatchMode (engine .DispatchAsync ),
3861 )
3962
40- // HTTP API
41- handler := httpapi .NewMux (svc , hub , httpapi.Options {PathPrefix : * prefix , AllowCORSOrigin : * cors })
63+ // Setup HTTP API
64+ handler := httpapi .NewMux (svc , hub , httpapi.Options {
65+ PathPrefix : cfg .Server .PathPrefix ,
66+ AllowCORSOrigin : cfg .Server .CORSOrigin ,
67+ })
4268
69+ // Create HTTP server
4370 srv := & http.Server {
44- Addr : * addr ,
71+ Addr : cfg . Server . Address ,
4572 Handler : handler ,
46- ReadHeaderTimeout : 5 * time . Second ,
47- ReadTimeout : 10 * time . Second ,
48- WriteTimeout : 10 * time . Second ,
49- IdleTimeout : 60 * time . Second ,
73+ ReadHeaderTimeout : cfg . Server . ReadHeaderTimeout ,
74+ ReadTimeout : cfg . Server . ReadTimeout ,
75+ WriteTimeout : cfg . Server . WriteTimeout ,
76+ IdleTimeout : cfg . Server . IdleTimeout ,
5077 }
5178
52- slog .Info ("starting gamifykit server" ,
53- "address" , * addr ,
54- "api_prefix" , * prefix ,
55- "cors_origin" , * cors )
79+ // Start server in a goroutine
80+ go func () {
81+ slog .Info ("server listening" , "address" , cfg .Server .Address )
82+ if err := srv .ListenAndServe (); err != nil && err != http .ErrServerClosed {
83+ slog .Error ("failed to start server" , "error" , err )
84+ os .Exit (1 )
85+ }
86+ }()
5687
57- if err := srv . ListenAndServe (); err != nil && err != http . ErrServerClosed {
58- slog . Error ( "failed to start server" , "error" , err )
59- os . Exit ( 1 )
60- }
88+ // Setup graceful shutdown
89+ quit := make ( chan os. Signal , 1 )
90+ signal . Notify ( quit , syscall . SIGINT , syscall . SIGTERM )
91+ <- quit
6192
62- // Graceful shutdown - this will only be reached if the server is stopped externally
63- slog .Info ("server shutting down" )
64- if err := srv .Shutdown (context .Background ()); err != nil {
93+ slog .Info ("shutting down server" , "timeout" , cfg .Server .ShutdownTimeout )
94+
95+ shutdownCtx , cancel := context .WithTimeout (context .Background (), cfg .Server .ShutdownTimeout )
96+ defer cancel ()
97+
98+ if err := srv .Shutdown (shutdownCtx ); err != nil {
6599 slog .Error ("error during server shutdown" , "error" , err )
66100 os .Exit (1 )
67101 }
102+
103+ slog .Info ("server stopped" )
104+ }
105+
106+ // setupLogging configures the logger based on configuration
107+ func setupLogging (cfg * config.Config ) {
108+ var handler slog.Handler
109+
110+ opts := & slog.HandlerOptions {
111+ Level : parseLogLevel (cfg .Logging .Level ),
112+ }
113+
114+ switch cfg .Logging .Format {
115+ case "text" :
116+ handler = slog .NewTextHandler (os .Stdout , opts )
117+ case "json" :
118+ handler = slog .NewJSONHandler (os .Stdout , opts )
119+ default :
120+ handler = slog .NewJSONHandler (os .Stdout , opts )
121+ }
122+
123+ // Add attributes if specified
124+ if len (cfg .Logging .Attributes ) > 0 {
125+ handler = handler .WithAttrs (convertAttributes (cfg .Logging .Attributes ))
126+ }
127+
128+ slog .SetDefault (slog .New (handler ))
129+ }
130+
131+ // parseLogLevel converts string log level to slog.Level
132+ func parseLogLevel (level string ) slog.Level {
133+ switch level {
134+ case "debug" :
135+ return slog .LevelDebug
136+ case "info" :
137+ return slog .LevelInfo
138+ case "warn" :
139+ return slog .LevelWarn
140+ case "error" :
141+ return slog .LevelError
142+ default :
143+ return slog .LevelInfo
144+ }
145+ }
146+
147+ // convertAttributes converts map[string]string to []slog.Attr
148+ func convertAttributes (attrs map [string ]string ) []slog.Attr {
149+ var result []slog.Attr
150+ for k , v := range attrs {
151+ result = append (result , slog .String (k , v ))
152+ }
153+ return result
154+ }
155+
156+ // setupStorage creates the appropriate storage adapter based on configuration
157+ func setupStorage (ctx context.Context , cfg * config.Config ) (engine.Storage , error ) {
158+ switch cfg .Storage .Adapter {
159+ case "memory" :
160+ return mem .New (), nil
161+
162+ case "redis" :
163+ return redisAdapter .New (cfg .Storage .Redis )
164+
165+ case "sql" :
166+ return sqlxAdapter .New (cfg .Storage .SQL )
167+
168+ case "file" :
169+ return mem .New (), fmt .Errorf ("file storage not yet implemented, using memory fallback" )
170+
171+ default :
172+ return mem .New (), fmt .Errorf ("unknown storage adapter: %s" , cfg .Storage .Adapter )
173+ }
68174}
0 commit comments