-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
157 lines (127 loc) · 4.21 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package main
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/guerzon/gobudget-api/pkg/api"
"github.com/guerzon/gobudget-api/pkg/db"
"github.com/guerzon/gobudget-api/pkg/util"
"github.com/guerzon/gobudget-api/pkg/worker"
"github.com/hibiken/asynq"
"github.com/jackc/pgx/v5/pgxpool"
)
// @title gobudget API
// @version beta
// @description gobudget API
// @host localhost:8080
// @BasePath /beta
// @accept json
// @produce json
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
// Load config
dir, _ := os.Getwd()
config, err := util.LoadConfig(dir)
if err != nil {
slog.Error("startup failed", "action", "load configuration", "errmsg", err)
os.Exit(1)
}
// Create a DB connection
conn, err := pgxpool.New(context.Background(), config.DBConnString)
if err != nil {
slog.Error("startup failed", "action", "database connection", "error", err)
os.Exit(2)
}
dbStore := db.NewStore(conn)
// Create a Redis client
redisOpts := asynq.RedisClientOpt{
Addr: config.RedisAddress,
}
taskDistributor := worker.NewRedisTaskDistributor(redisOpts)
// Create an email sender
var emailSender util.EmailSender
if config.Environment == "local" {
emailSender = util.NewLocalSender(config.EmailSenderName, config.MailhogSenderAddress, config.MailhogHost)
} else {
emailSender = util.NewGmailSender(config.EmailSenderName, config.GmailSenderAddress, config.GmailSenderPassword)
}
// Run the db migration
dbMigration(config.DBMigrationFiles, config.DBConnString)
// Create the server
apiServer, err := api.NewServer(config, dbStore, taskDistributor)
if err != nil {
slog.Error("startup failed", "action", "create server instance", "errmsg", err)
os.Exit(5)
}
// Start the task processor in a go routine
go startTaskProcessor(redisOpts, dbStore, emailSender)
// Use http from the std library
srv := &http.Server{
Addr: fmt.Sprintf("%s:%s", config.ListenAddr, config.ListenPort),
Handler: apiServer.Router,
ReadHeaderTimeout: 2 * time.Second,
}
// Setup routine to shut down the server gracefully
idleConnsClosed := make(chan struct{})
go func() {
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGINT, syscall.SIGTERM)
<-s
slog.Info("interrupt received ...")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
slog.Error("cannot shut down server", "shutdown", "shutdown API server", "errmsg", err)
}
close(idleConnsClosed)
}()
// Start the server
if err = srv.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
slog.Error("startup failed", "action", "start API server", "errmsg", err)
}
}
<-idleConnsClosed
slog.Info("API server has been shut down.")
}
func dbMigration(migrationFiles string, dbConnString string) {
slog.Info("starting db migration")
mig, err := migrate.New(migrationFiles, dbConnString)
if err != nil {
slog.Error("startup failed", "action", "create db migration", "error", err)
os.Exit(2)
}
if err = mig.Up(); err != nil {
if err != migrate.ErrNoChange {
slog.Error("startup failed", "action", "migrate database", "error", err)
os.Exit(3)
}
slog.Info("no change detected during db migration")
}
slog.Info("db migration completed successfully")
}
// Starts the task processor for picking up tasks from Redis. It receives a db.Store and a util.EmailSender object for any DB and email task it requires.
func startTaskProcessor(redisOpts asynq.RedisClientOpt, store db.Store, mailer util.EmailSender) {
processor := worker.NewRedisTaskProcessor(redisOpts, store, mailer)
slog.Info("Starting task processor ...")
err := processor.Start()
if err != nil {
slog.Error("startup failed", "action", "start task processor", "error", err)
os.Exit(7)
}
slog.Info("Task processor has started.")
}