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 support@simplebank.com // @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 }}