@@ -21,21 +21,26 @@ import (
21
21
)
22
22
23
23
type 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 {
28
29
dsn string
29
30
redis string
30
31
}
31
32
}
32
33
33
34
func main () {
34
35
cfg := loadConfig ()
35
- logger := setupLogger ()
36
+ logger := setupLogger (cfg . logLevel )
36
37
37
38
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
+ }
39
44
return
40
45
}
41
46
@@ -45,11 +50,50 @@ func main() {
45
50
}
46
51
47
52
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
+ }
49
59
defer dbpool .Close ()
50
60
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 )))
53
97
}
54
98
55
99
func loadConfig () config {
@@ -76,82 +120,75 @@ Flags:
76
120
flag .StringVar (& cfg .db .dsn , "db-dsn" , defaultDSN , "PostgreSQL DSN (or set DATABASE_URL env)" )
77
121
flag .StringVar (& cfg .db .redis , "redis" , defaultRedisURL , "Redis URL (optional, or set REDIS_URL env)" )
78
122
flag .BoolVar (& cfg .migrate , "migrate" , false , "Run DB migrations and exit" )
123
+ flag .StringVar (& cfg .logLevel , "loglevel" , "info" , "Log level (debug|info|warn|error)" )
79
124
80
125
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
+
81
137
return cfg
82
138
}
83
139
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 )
88
160
}
89
161
90
- func runMigrations (cfg config , logger * slog.Logger ) {
162
+ func runMigrations (cfg config , logger * slog.Logger ) error {
91
163
if err := db .RunMigrations (cfg .db .dsn , logger ); err != nil {
92
- logger .Error ("Migration failed" , "error" , err )
93
- os .Exit (1 )
164
+ return err
94
165
}
95
166
logger .Info ("Migrations applied successfully" )
167
+ return nil
96
168
}
97
169
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 ) {
99
171
dbpool , err := db .NewPool (ctx , cfg .db .dsn , logger )
100
172
if err != nil {
101
- logger .Error ("Failed to connect to DB" , "error" , err )
102
- os .Exit (1 )
173
+ return nil , err
103
174
}
104
175
logger .Info ("Connected to PostgreSQL" )
105
- return dbpool
176
+ return dbpool , nil
106
177
}
107
178
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 )
110
181
if err != nil {
111
- logger .Error ("Failed to initialize embedder" , "error" , err )
112
- os .Exit (1 )
182
+ return nil , err
113
183
}
114
184
115
185
if cfg .db .redis != "" {
116
186
redisClient := db .NewRedisClient (cfg .db .redis , logger )
117
187
return & embed.CachedEmbedder {
118
- Base : baseEmbedder ,
188
+ Base : base ,
119
189
Redis : redisClient ,
120
190
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
135
192
}
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
157
194
}
0 commit comments