Files
Nikolai Rodionov ef009e29c6
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Implement test services
Signed-off-by: Nikolai Rodionov <allanger@badhouseplants.net>
2026-04-30 17:34:09 +02:00

225 lines
6.8 KiB
Go

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
}