all repos

onasty @ b650519

a one-time notes service

onasty/cmd/api/main.go (view raw)

Olexandr Smirnov Olexandr Smirnov
ss2316544@gmail.com
fix: use separate ratelimiter for some endpoints (#166)..., 10 months ago
1
package main
2
3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
	"log/slog"
8
	"net/http"
9
	"os"
10
	"os/signal"
11
12
	"github.com/gin-gonic/gin"
13
	"github.com/nats-io/nats.go"
14
	"github.com/olexsmir/onasty/internal/config"
15
	"github.com/olexsmir/onasty/internal/events/mailermq"
16
	"github.com/olexsmir/onasty/internal/hasher"
17
	"github.com/olexsmir/onasty/internal/jwtutil"
18
	"github.com/olexsmir/onasty/internal/logger"
19
	"github.com/olexsmir/onasty/internal/metrics"
20
	"github.com/olexsmir/onasty/internal/oauth"
21
	"github.com/olexsmir/onasty/internal/service/notesrv"
22
	"github.com/olexsmir/onasty/internal/service/usersrv"
23
	"github.com/olexsmir/onasty/internal/store/psql/noterepo"
24
	"github.com/olexsmir/onasty/internal/store/psql/passwordtokrepo"
25
	"github.com/olexsmir/onasty/internal/store/psql/sessionrepo"
26
	"github.com/olexsmir/onasty/internal/store/psql/userepo"
27
	"github.com/olexsmir/onasty/internal/store/psql/vertokrepo"
28
	"github.com/olexsmir/onasty/internal/store/psqlutil"
29
	"github.com/olexsmir/onasty/internal/store/rdb"
30
	"github.com/olexsmir/onasty/internal/store/rdb/notecache"
31
	"github.com/olexsmir/onasty/internal/store/rdb/usercache"
32
	httptransport "github.com/olexsmir/onasty/internal/transport/http"
33
	"github.com/olexsmir/onasty/internal/transport/http/httpserver"
34
	"github.com/olexsmir/onasty/internal/transport/http/ratelimit"
35
)
36
37
func main() {
38
	if err := run(context.Background()); err != nil {
39
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
40
		os.Exit(1)
41
	}
42
}
43
44
//nolint:err113,funlen
45
func run(ctx context.Context) error {
46
	ctx, cancel := context.WithCancel(ctx)
47
	defer cancel()
48
49
	cfg := config.NewConfig()
50
51
	// logger
52
	if err := logger.SetDefault(cfg.LogLevel, cfg.LogFormat, cfg.LogShowLine); err != nil {
53
		return err
54
	}
55
56
	// semi dev mode
57
	if !cfg.AppEnv.IsDevMode() {
58
		gin.SetMode(gin.ReleaseMode)
59
	}
60
61
	// app deps
62
	nc, err := nats.Connect(cfg.NatsURL)
63
	if err != nil {
64
		return err
65
	}
66
67
	psqlDB, err := psqlutil.Connect(ctx, cfg.PostgresDSN)
68
	if err != nil {
69
		return err
70
	}
71
72
	redisDB, err := rdb.Connect(ctx, cfg.RedisAddr, cfg.RedisPassword, cfg.RedisDB)
73
	if err != nil {
74
		return err
75
	}
76
77
	userPasswordHasher := hasher.NewSHA256Hasher(cfg.PasswordSalt)
78
	notePasswordHasher := hasher.NewSHA256Hasher(cfg.NotePasswordSalt)
79
	jwtTokenizer := jwtutil.NewJWTUtil(cfg.JwtSigningKey, cfg.JwtAccessTokenTTL)
80
81
	googleOauth := oauth.NewGoogleProvider(
82
		cfg.GoogleClientID,
83
		cfg.GoogleSecret,
84
		cfg.GoogleRedirectURL,
85
	)
86
	githubOauth := oauth.NewGithubProvider(
87
		cfg.GitHubClientID,
88
		cfg.GitHubSecret,
89
		cfg.GitHubRedirectURL,
90
	)
91
92
	mailermq := mailermq.New(nc)
93
94
	sessionrepo := sessionrepo.New(psqlDB)
95
	vertokrepo := vertokrepo.New(psqlDB)
96
	pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(psqlDB)
97
98
	notecache := notecache.New(redisDB, cfg.CacheNoteTTL)
99
	noterepo := noterepo.New(psqlDB)
100
	notesrv := notesrv.New(noterepo, notePasswordHasher, notecache)
101
102
	userepo := userepo.New(psqlDB)
103
	usercache := usercache.New(redisDB, cfg.CacheUsersTTL)
104
	usersrv := usersrv.New(
105
		userepo,
106
		sessionrepo,
107
		vertokrepo,
108
		pwdtokrepo,
109
		noterepo,
110
		userPasswordHasher,
111
		jwtTokenizer,
112
		mailermq,
113
		usercache,
114
		googleOauth,
115
		githubOauth,
116
		cfg.JwtRefreshTokenTTL,
117
		cfg.VerificationTokenTTL,
118
		cfg.ResetPasswordTokenTTL,
119
	)
120
121
	rateLimiterConfig := ratelimit.Config{
122
		RPS:   cfg.RateLimiterRPS,
123
		TTL:   cfg.RateLimiterTTL,
124
		Burst: cfg.RateLimiterBurst,
125
	}
126
127
	slowRateLimiterConfig := ratelimit.Config{
128
		RPS:   cfg.SlowRateLimiterRPS,
129
		TTL:   cfg.SlowRateLimiterTTL,
130
		Burst: cfg.SlowRateLimiterBurst,
131
	}
132
133
	handler := httptransport.NewTransport(
134
		usersrv,
135
		notesrv,
136
		cfg.AppEnv,
137
		cfg.AppURL,
138
		cfg.CORSAllowedOrigins,
139
		cfg.CORSMaxAge,
140
		rateLimiterConfig,
141
		slowRateLimiterConfig,
142
	)
143
144
	// http server
145
	srv := httpserver.NewServer(handler.Handler(), httpserver.Config{
146
		Port:            cfg.HTTPPort,
147
		ReadTimeout:     cfg.HTTPReadTimeout,
148
		WriteTimeout:    cfg.HTTPWriteTimeout,
149
		MaxHeaderSizeMb: cfg.HTTPHeaderMaxSizeMb,
150
	})
151
	go func() {
152
		slog.Info("starting http server", "port", cfg.HTTPPort)
153
		if err := srv.Start(); !errors.Is(err, http.ErrServerClosed) {
154
			slog.Error("failed to start http server", "error", err)
155
		}
156
	}()
157
158
	// metrics
159
	if cfg.MetricsEnabled {
160
		mSrv := httpserver.NewDefaultServer(metrics.Handler(), cfg.MetricsPort)
161
		go func() {
162
			slog.Info("starting metrics server", "port", cfg.MetricsPort)
163
			if err := mSrv.Start(); !errors.Is(err, http.ErrServerClosed) {
164
				slog.Error("failed to start metrics server", "error", err)
165
			}
166
		}()
167
	}
168
169
	// graceful shutdown
170
	quitCh := make(chan os.Signal, 1)
171
	signal.Notify(quitCh, os.Interrupt)
172
	<-quitCh
173
174
	if err := srv.Stop(ctx); err != nil {
175
		return errors.Join(errors.New("failed to stop http server"), err)
176
	}
177
178
	if err := psqlDB.Close(); err != nil {
179
		return errors.Join(errors.New("failed to close postgres connection"), err)
180
	}
181
182
	if err := redisDB.Close(); err != nil {
183
		return errors.Join(errors.New("failed to close redis connection"), err)
184
	}
185
186
	return nil
187
}