all repos

onasty @ bf8dc57251dbc6827d93ef4bb0e5461f6084a099

a one-time notes service

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

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