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/tools/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-jwt/jwt/v5" "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/codes" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "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 } // jwtVerifier := interceptors.NewJWTVerifier(ctx, []byte(params.JWTSecret)) authFn := func(ctx context.Context) (context.Context, error) { tokenString, err := auth.AuthFromMD(ctx, "bearer") if err != nil { return nil, err } token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { return []byte(params.JWTSecret), nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) if err != nil { return nil, status.Error(codes.Unauthenticated, "User is not authorized") } if claims, ok := token.Claims.(jwt.MapClaims); ok { ctx = context.WithValue(ctx, "token_id", claims["token_id"].(string)) ctx = context.WithValue(ctx, "user_id", claims["user_id"].(string)) } else { return ctx, errors.New("claims are missing int the token") } return ctx, nil } authReqServices := func(ctx context.Context, callMeta interceptors.CallMeta) bool { return !strings.Contains(callMeta.Service, "NoAuth") } grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")), // jwtVerifier.JWTAuthInterceptor, selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(authReqServices)), ), grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))), ) rdb := redis.NewClient(&redis.Options{ Addr: params.RedisHost, }) 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.RegisterAccountsNoAuthServiceServer(grpcServer, v1.NewAccountNoAuthRPCImpl(accountCtrl)) accounts.RegisterAccountsAuthServiceServer(grpcServer, v1.NewAccountAuthRPCImpl(accountCtrl)) test.RegisterTestAuthServiceServer(grpcServer, v1.NewTestAuthRPCImpl()) test.RegisterTestNoAuthServiceServer(grpcServer, v1.NewTestNoAuthRPCImpl()) if err := grpcServer.Serve(lis); err != nil { return err } return nil }