Public Access
feat: basalt 0.0.1
This commit is contained in:
22 files changed
+1364
-1
No files matched your search
@@ -0,0 +1,323 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
{{ if .UseGRPC }}
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
{{ end }}
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
{{ if .UseGRPC }}
|
||||
"github.com/rs/cors"
|
||||
{{ end }}
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"{{ .ModuleName }}/internal/auth"
|
||||
"{{ .ModuleName }}/internal/config"
|
||||
db "{{ .ModuleName }}/internal/db/sqlc"
|
||||
{{ if .UseGRPC }}
|
||||
gapi "{{ .ModuleName }}/internal/handlers/grpc"
|
||||
{{ end }}
|
||||
{{ if not .UseGRPC }}
|
||||
api "{{ .ModuleName }}/internal/handlers/http"
|
||||
{{ end }}
|
||||
"{{ .ModuleName }}/internal/mailer"
|
||||
"{{ .ModuleName }}/internal/service"
|
||||
"{{ .ModuleName }}/internal/worker"
|
||||
{{ if .UseGRPC }}
|
||||
"{{ .ModuleName }}/pb"
|
||||
{{ end }}
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
{{ if .UseGRPC }}
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
_ "{{ .ModuleName }}/docs/statik"
|
||||
{{ end }}
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
|
||||
|
||||
)
|
||||
|
||||
var interruptSignals = []os.Signal{
|
||||
os.Interrupt,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGINT,
|
||||
}
|
||||
|
||||
// @title Simple Bank API
|
||||
// @version 1.0
|
||||
// @description API for Simple Bank application
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
|
||||
// @contact.name API Support
|
||||
// @contact.email [email protected]
|
||||
|
||||
// @license.name MIT
|
||||
// @license.url https://opensource.org/licenses/MIT
|
||||
|
||||
// @host localhost:8080
|
||||
// @BasePath /v1
|
||||
// @schemes http https
|
||||
|
||||
// @securityDefinitions.apikey BearerAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @description Type "Bearer" followed by a space and JWT token.
|
||||
func main() {
|
||||
config, err := config.LoadConfig(".")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot load config")
|
||||
}
|
||||
|
||||
if config.Environment == "development" {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), interruptSignals...)
|
||||
defer cancel()
|
||||
|
||||
connPool, err := pgxpool.New(ctx, config.DBAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot connect to db")
|
||||
}
|
||||
|
||||
runDBMigration(config.MigrationURL, config.DBAddr)
|
||||
|
||||
redisOpt := asynq.RedisClientOpt{
|
||||
Addr: config.RedisAddr,
|
||||
Password: config.RedisPassword,
|
||||
DB: config.RedisDB,
|
||||
}
|
||||
|
||||
taskDist := worker.NewRedisTaskDistributor(redisOpt)
|
||||
store := db.NewPostgresStore(connPool)
|
||||
|
||||
tokenMaker, err := auth.NewPasetoMaker(config)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create token maker")
|
||||
}
|
||||
|
||||
userService := service.NewUserService(store, taskDist)
|
||||
authService := service.NewAuthService(store, tokenMaker, config)
|
||||
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
{{ if not .UseGRPC }}
|
||||
runGinServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
{{ else }}
|
||||
// Assuming you want Gateway AND gRPC when gRPC is selected
|
||||
runGatewayServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
runGrpcServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
{{ end }}
|
||||
|
||||
runTaskProcessor(ctx, wg, redisOpt, store, config)
|
||||
|
||||
err = wg.Wait()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("application terminated with error")
|
||||
}
|
||||
}
|
||||
|
||||
func runTaskProcessor(ctx context.Context, wg *errgroup.Group, redisOpt asynq.RedisClientOpt, store db.Store, config config.Config) {
|
||||
mailer := mailer.NewGmailSender(config.EmailSenderName, config.EmailSender, config.EmailPassword)
|
||||
taskProcessor := worker.NewRedisTaskProcessor(redisOpt, store, mailer, config)
|
||||
log.Info().Msg("start task processor")
|
||||
if err := taskProcessor.Start(); err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot start task processor")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down task processor")
|
||||
taskProcessor.Shutdown()
|
||||
log.Info().Msg("task processor shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runDBMigration(migrationURL, dbSource string) {
|
||||
migration, err := migrate.New(migrationURL, dbSource)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create new migrate instance")
|
||||
}
|
||||
|
||||
if err = migration.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
log.Fatal().Err(err).Msg("failed to run migrate up")
|
||||
}
|
||||
|
||||
log.Info().Msg("db migrated successfully")
|
||||
}
|
||||
|
||||
{{ if not .UseGRPC }}
|
||||
func runGinServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, td worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
s, err := api.NewServer(config, store, td, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
err = s.Start(config.HTTPServerAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot start server:")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("shutting down server")
|
||||
err := s.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot shutdown server")
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("server shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ if .UseGRPC }}
|
||||
func runGrpcServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, taskDistributor worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
server, err := gapi.NewServer(config, store, taskDistributor, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server:")
|
||||
}
|
||||
|
||||
grpcLogger := grpc.UnaryInterceptor(gapi.GrpcLogger)
|
||||
|
||||
grpcServer := grpc.NewServer(grpcLogger)
|
||||
pb.RegisterSimpleBankServer(grpcServer, server)
|
||||
reflection.Register(grpcServer)
|
||||
|
||||
listener, err := net.Listen("tcp", config.GrpcServerAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create listener")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
log.Info().Msgf("start gRPC server at %s", listener.Addr().String())
|
||||
err = grpcServer.Serve(listener)
|
||||
if err != nil {
|
||||
if errors.Is(err, grpc.ErrServerStopped) {
|
||||
return nil
|
||||
}
|
||||
log.Error().Err(err).Msg("gRPC server failed to serve")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down gRPC server")
|
||||
grpcServer.GracefulStop()
|
||||
log.Info().Msg("gRPC server shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runGatewayServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, taskDistributor worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
server, err := gapi.NewServer(config, store, taskDistributor, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server:")
|
||||
}
|
||||
|
||||
opts := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
},
|
||||
UnmarshalOptions: protojson.UnmarshalOptions{
|
||||
DiscardUnknown: true,
|
||||
},
|
||||
})
|
||||
|
||||
grpcMux := runtime.NewServeMux(opts)
|
||||
|
||||
err = pb.RegisterSimpleBankHandlerServer(ctx, grpcMux, server)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot register handler server")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", grpcMux)
|
||||
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create statik fs")
|
||||
}
|
||||
|
||||
swaggerHandler := http.StripPrefix("/swagger/", http.FileServer(statikFS))
|
||||
mux.Handle("/swagger/", swaggerHandler)
|
||||
|
||||
c := cors.New(
|
||||
cors.Options{
|
||||
AllowedOrigins: config.AllowedOrigins,
|
||||
AllowedMethods: []string{
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodOptions,
|
||||
},
|
||||
AllowedHeaders: []string{
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"X-CSRF-Token",
|
||||
},
|
||||
AllowCredentials: true,
|
||||
Debug: config.Environment == "development",
|
||||
},
|
||||
)
|
||||
handler := c.Handler(gapi.HTTPLogger(mux))
|
||||
httpServer := &http.Server{
|
||||
Handler: handler,
|
||||
Addr: config.HTTPServerAddr,
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
log.Info().Msgf("start HTTP gateway server at %s", httpServer.Addr)
|
||||
|
||||
err = httpServer.ListenAndServe()
|
||||
if err != nil {
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
log.Error().Err(err).Msg("cannot start http gateway server")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down HTTP gateway server")
|
||||
err := httpServer.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot shut down http gateway server")
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("HTTP gateway server shut down")
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
{{ end }}
|
||||
Reference in new issue
Block a user