@@ -21,21 +21,26 @@ import (
2121)
2222
2323type config struct {
24- port int
25- apiKey string
26- migrate bool
27- db struct {
24+ port int
25+ apiKey string
26+ migrate bool
27+ logLevel string
28+ db struct {
2829 dsn string
2930 redis string
3031 }
3132}
3233
3334func main () {
3435 cfg := loadConfig ()
35- logger := setupLogger ()
36+ logger := setupLogger (cfg . logLevel )
3637
3738 if cfg .migrate {
38- runMigrations (cfg , logger )
39+ err := runMigrations (cfg , logger )
40+ if err != nil {
41+ logger .Error ("Migration failed" , "error" , err )
42+ os .Exit (1 )
43+ }
3944 return
4045 }
4146
@@ -45,11 +50,50 @@ func main() {
4550 }
4651
4752 ctx := context .Background ()
48- dbpool := connectToPostgres (ctx , cfg , logger )
53+
54+ dbpool , err := connectToPostgres (ctx , cfg , logger )
55+ if err != nil {
56+ logger .Error ("Database connection failed" , "error" , err )
57+ os .Exit (1 )
58+ }
4959 defer dbpool .Close ()
5060
51- embedder := setupEmbedder (ctx , cfg , logger )
52- startServer (cfg , logger , dbpool , embedder )
61+ embedder , err := newEmbedder (ctx , cfg , logger )
62+ if err != nil {
63+ logger .Error ("Failed to setup embedder" , "error" , err )
64+ os .Exit (1 )
65+ }
66+
67+ repo := repository .New (dbpool )
68+ bookService := & service.BookService {
69+ Embedder : embedder ,
70+ Repository : repo ,
71+ Logger : logger ,
72+ }
73+ bookHandler := & api.BookHandler {
74+ Service : bookService ,
75+ }
76+
77+ e := echo .New ()
78+ e .HideBanner = true
79+
80+ e .Use (middleware .Logger ())
81+ e .Use (middleware .Recover ())
82+ e .Use (middleware .TimeoutWithConfig (middleware.TimeoutConfig {
83+ Timeout : 5 * time .Second ,
84+ ErrorMessage : "Request timed out." ,
85+ OnTimeoutRouteErrorHandler : func (err error , c echo.Context ) {
86+ logger .Warn ("Timeout on route" , "path" , c .Path (), "error" , err )
87+ },
88+ }))
89+
90+ e .GET ("/ping" , func (c echo.Context ) error {
91+ return c .String (200 , "pong" )
92+ })
93+ e .GET ("/search" , bookHandler .SearchBooks )
94+ e .POST ("/books" , bookHandler .AddBook )
95+
96+ e .Logger .Fatal (e .Start (fmt .Sprintf (":%d" , cfg .port )))
5397}
5498
5599func loadConfig () config {
@@ -76,82 +120,75 @@ Flags:
76120 flag .StringVar (& cfg .db .dsn , "db-dsn" , defaultDSN , "PostgreSQL DSN (or set DATABASE_URL env)" )
77121 flag .StringVar (& cfg .db .redis , "redis" , defaultRedisURL , "Redis URL (optional, or set REDIS_URL env)" )
78122 flag .BoolVar (& cfg .migrate , "migrate" , false , "Run DB migrations and exit" )
123+ flag .StringVar (& cfg .logLevel , "loglevel" , "info" , "Log level (debug|info|warn|error)" )
79124
80125 flag .Parse ()
126+
127+ if cfg .db .dsn == "" {
128+ fmt .Fprintln (os .Stderr , "Error: --db-dsn is required" )
129+ os .Exit (1 )
130+ }
131+
132+ if cfg .apiKey == "" {
133+ fmt .Fprintln (os .Stderr , "Error: --apikey is required" )
134+ os .Exit (1 )
135+ }
136+
81137 return cfg
82138}
83139
84- func setupLogger () * slog.Logger {
85- return slog .New (tint .NewHandler (os .Stdout , & tint.Options {
86- Level : slog .LevelInfo ,
87- }))
140+ func setupLogger (levelStr string ) * slog.Logger {
141+ level := slog .LevelInfo
142+
143+ switch levelStr {
144+ case "debug" :
145+ level = slog .LevelDebug
146+ case "info" :
147+ level = slog .LevelInfo
148+ case "warn" , "warning" :
149+ level = slog .LevelWarn
150+ case "error" :
151+ level = slog .LevelError
152+ default :
153+ fmt .Fprintf (os .Stderr , "invalid log level %q, defaulting to info\n " , levelStr )
154+ }
155+
156+ handler := tint .NewHandler (os .Stdout , & tint.Options {
157+ Level : level ,
158+ })
159+ return slog .New (handler )
88160}
89161
90- func runMigrations (cfg config , logger * slog.Logger ) {
162+ func runMigrations (cfg config , logger * slog.Logger ) error {
91163 if err := db .RunMigrations (cfg .db .dsn , logger ); err != nil {
92- logger .Error ("Migration failed" , "error" , err )
93- os .Exit (1 )
164+ return err
94165 }
95166 logger .Info ("Migrations applied successfully" )
167+ return nil
96168}
97169
98- func connectToPostgres (ctx context.Context , cfg config , logger * slog.Logger ) * pgxpool.Pool {
170+ func connectToPostgres (ctx context.Context , cfg config , logger * slog.Logger ) ( * pgxpool.Pool , error ) {
99171 dbpool , err := db .NewPool (ctx , cfg .db .dsn , logger )
100172 if err != nil {
101- logger .Error ("Failed to connect to DB" , "error" , err )
102- os .Exit (1 )
173+ return nil , err
103174 }
104175 logger .Info ("Connected to PostgreSQL" )
105- return dbpool
176+ return dbpool , nil
106177}
107178
108- func setupEmbedder (ctx context.Context , cfg config , logger * slog.Logger ) embed.Embedder {
109- baseEmbedder , err := embed .NewGeminiEmbedder (ctx , logger , cfg .apiKey )
179+ func newEmbedder (ctx context.Context , cfg config , logger * slog.Logger ) ( embed.Embedder , error ) {
180+ base , err := embed .NewGeminiEmbedder (ctx , logger , cfg .apiKey )
110181 if err != nil {
111- logger .Error ("Failed to initialize embedder" , "error" , err )
112- os .Exit (1 )
182+ return nil , err
113183 }
114184
115185 if cfg .db .redis != "" {
116186 redisClient := db .NewRedisClient (cfg .db .redis , logger )
117187 return & embed.CachedEmbedder {
118- Base : baseEmbedder ,
188+ Base : base ,
119189 Redis : redisClient ,
120190 Logger : logger ,
121- }
122- }
123- return baseEmbedder
124- }
125-
126- func startServer (cfg config , logger * slog.Logger , dbpool * pgxpool.Pool , embedder embed.Embedder ) {
127- repo := repository .New (dbpool )
128- bookService := & service.BookService {
129- Embedder : embedder ,
130- Repository : repo ,
131- Logger : logger ,
132- }
133- bookHandler := & api.BookHandler {
134- Service : bookService ,
191+ }, nil
135192 }
136-
137- e := echo .New ()
138- e .HideBanner = true
139-
140- e .Use (middleware .Logger ())
141- e .Use (middleware .Recover ())
142- e .Use (middleware .TimeoutWithConfig (middleware.TimeoutConfig {
143- Timeout : 5 * time .Second ,
144- ErrorMessage : "Request timed out." ,
145- OnTimeoutRouteErrorHandler : func (err error , c echo.Context ) {
146- logger .Warn ("Timeout on route" , "path" , c .Path (), "error" , err )
147- },
148- }))
149-
150- e .GET ("/ping" , func (c echo.Context ) error {
151- return c .String (200 , "pong" )
152- })
153- e .GET ("/search" , bookHandler .SearchBooks )
154- e .POST ("/books" , bookHandler .AddBook )
155-
156- e .Logger .Fatal (e .Start (fmt .Sprintf (":%d" , cfg .port )))
193+ return base , nil
157194}
0 commit comments