Files
draft/assets/templates/main.go.tpl
T
2026-01-27 17:33:14 +00:00

324 lines
8.5 KiB
Smarty

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 }}