package main import ( "context" "database/sql" "errors" "fmt" "net" "strings" "time" v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" "github.com/alecthomas/kong" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" _ "github.com/lib/pq" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "github.com/redis/go-redis/v9" ) var CLI struct { Serve Serve `cmd:"" help:"Start the grpc server"` Migrate Migrate `cmd:"" help:"Run the database migrations"` } type Serve struct { // Service related Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"` Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"` HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"` // SMTP Config SMTPHost string `env:"SOFTPLAYER_SMTP_HOST"` SMTPPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"` SMTPFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"` SMTPPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"` // Database and redis DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` RedisHost string `env:"SOFTPLAYER_REDIS_HOST"` // JWT parameters RefrestTokenTTL time.Duration `default:"8h"` AccessTokenTTL time.Duration `default:"15m"` JWTSecret string `default:"qwertyu9"` // Dev and logging Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"` DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"` } type Migrate struct { DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` MigrationsPath string `env:"SOFTPLAYER_DB_MIGRATIOON_PATH" default:"file://migrations"` } func main() { kongCtx := kong.Parse(&CLI) ctx := logger.NewLogger(context.Background(), "info") switch kongCtx.Command() { case "serve": if err := server(ctx, CLI.Serve); err != nil { panic(err) } case "migrate": if err := migrateDB(ctx, CLI.Migrate); err != nil { panic(err) } default: panic(kongCtx.Command()) } } // Migrate the database to the latest version func migrateDB(ctx context.Context, params Migrate) error { log := logger.FromContext(ctx) log.Info("Starting a database migration driver") db, err := sql.Open("postgres", params.DBConnectionString) if err != nil { log.Error(err, "Couldn't start a database driver") return err } driver, err := postgres.WithInstance(db, &postgres.Config{}) if err != nil { log.Error(err, "Couldn't start a database migration driver") return err } log.Info("Preparing database migrations") m, err := migrate.NewWithDatabaseInstance( params.MigrationsPath, "postgres", driver) if err != nil { log.Error(err, "Couldn't perform database migrations") return err } log.Info("Starting database migrations") err = m.Up() if err != nil { if errors.Is(err, migrate.ErrNoChange) { log.Info("Database is already up-to-date") } else { log.Error(err, "Couldn't migrate the database") return err } } return nil } // Run the grpc backend server func server(ctx context.Context, params Serve) error { // Make sure the download dir exists log := logger.FromContext(ctx) // Init a logger //log.Info("Starting a kubernetes manager") //controller, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) //if err != nil { // log.Error(err, "Coulnd't start a kube manager") // return err //} // TODO: Handle the error //go func() { // if err := controller.Start(ctx); err != nil { // panic(err) // } //}() log.Info("Opening a database connection") db, err := sql.Open("postgres", params.DBConnectionString) if err != nil { log.Error(err, "Couldn't start a database driver") return err } //emailConfig := email.EmailConf{ // From: params.SmtpFrom, // Password: params.SmtpPassword, // SmtpHost: params.SmtpHost, // SmtpPort: params.SmtpPort, //} address := fmt.Sprintf("%s:%d", params.Host, params.Port) lis, err := net.Listen("tcp", address) if err != nil { return err } authReqServices := func(ctx context.Context, callMeta interceptors.CallMeta) bool { serviceParts := strings.Split(callMeta.Service, ".") if len(serviceParts) == 0 { return false } serviceName := serviceParts[len(serviceParts)-1] fmt.Println(serviceName) return !strings.HasPrefix(serviceName, "Public") } rdb := redis.NewClient(&redis.Options{ Addr: params.RedisHost, }) authInterceptor := controllers.NewAuthController( []byte(params.JWTSecret), params.AccessTokenTTL, params.RefrestTokenTTL, rdb, ) grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")), // jwtVerifier.JWTAuthInterceptor, selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authInterceptor.AuthInterceptorFN), selector.MatchFunc(authReqServices)), ), grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))), ) if params.Reflection { reflection.Register(grpcServer) } accountCtrl := &controllers.AccountController{ HashCost: params.HashCost, DB: db, DevMode: params.DevMode, RefreshTokenTTL: params.RefrestTokenTTL, AccessTokenTTL: params.AccessTokenTTL, JWTSecret: []byte(params.JWTSecret), Redis: rdb, } accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewAccountNoAuthRPCImpl(accountCtrl, authInterceptor)) accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountAuthRPCImpl(accountCtrl, authInterceptor)) test.RegisterTestAuthServiceServer(grpcServer, v1.NewTestAuthRPCImpl()) test.RegisterTestNoAuthServiceServer(grpcServer, v1.NewTestNoAuthRPCImpl()) if err := grpcServer.Serve(lis); err != nil { return err } return nil }