All checks were successful
ci/woodpecker/push/build Pipeline was successful
Signed-off-by: Nikolai Rodionov <allanger@badhouseplants.net>
225 lines
6.8 KiB
Go
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
|
|
}
|