all repos

onasty @ 8ecd31e7e2391c4a6bba88d5818481ba8133f5b1

a one-time notes service

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

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