From 1c3df23bf970fd38947c25c3dffc2bd40325bea2 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Tue, 19 May 2026 10:55:53 +0200 Subject: [PATCH] Restructure the projec and start adding projects Signed-off-by: Nikolai Rodionov --- api/v1/accounts.go | 42 --------- cmd/server.go | 10 +-- internal/api/v1/accounts.go | 89 +++++++++++++++++++ {api => internal/api}/v1/projects.go | 0 {api => internal/api}/v1/public_accounts.go | 40 ++++----- {api => internal/api}/v1/public_tokens.go | 20 ++--- {api => internal/api}/v1/refresh_session.go | 0 {api => internal/api}/v1/test.go | 0 {api => internal/api}/v1/tokens.go | 46 +++++----- internal/repository/projects.go | 6 +- internal/repository/projects_test.go | 26 ++++++ internal/repository/transaction.go | 22 +++++ .../{controllers => services}/accounts.go | 2 +- .../accounts_test.go | 24 ++--- .../authorization.go | 2 +- .../authorization_test.go | 26 +++--- .../{controllers => services}/projects.go | 12 ++- internal/{controllers => services}/tokens.go | 2 +- .../{controllers => services}/tokens_test.go | 68 +++++++------- 19 files changed, 270 insertions(+), 167 deletions(-) delete mode 100644 api/v1/accounts.go create mode 100644 internal/api/v1/accounts.go rename {api => internal/api}/v1/projects.go (100%) rename {api => internal/api}/v1/public_accounts.go (73%) rename {api => internal/api}/v1/public_tokens.go (79%) rename {api => internal/api}/v1/refresh_session.go (100%) rename {api => internal/api}/v1/test.go (100%) rename {api => internal/api}/v1/tokens.go (88%) create mode 100644 internal/repository/projects_test.go create mode 100644 internal/repository/transaction.go rename internal/{controllers => services}/accounts.go (99%) rename internal/{controllers => services}/accounts_test.go (86%) rename internal/{controllers => services}/authorization.go (99%) rename internal/{controllers => services}/authorization_test.go (68%) rename internal/{controllers => services}/projects.go (72%) rename internal/{controllers => services}/tokens.go (99%) rename internal/{controllers => services}/tokens_test.go (88%) diff --git a/api/v1/accounts.go b/api/v1/accounts.go deleted file mode 100644 index 70b0437..0000000 --- a/api/v1/accounts.go +++ /dev/null @@ -1,42 +0,0 @@ -package v1 - -import ( - "context" - - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" - "google.golang.org/protobuf/types/known/emptypb" -) - -func NewAccountServer( - accountsCtrl *controllers.AccountController, - authorizationCtrl *controllers.AuthController, -) *AccountsServer { - return &AccountsServer{ - accountsCtrl: accountsCtrl, - authorizationCtrl: authorizationCtrl, - } -} - -//var _ accounts.AccountsServiceServer = (*AccountsServer)(nil) - -type AccountsServer struct { - accounts.UnimplementedAccountsServiceServer - accountsCtrl *controllers.AccountController - authorizationCtrl *controllers.AuthController -} - -// IsEmailVerified implements [v1.AccountsServiceServer]. -func (a *AccountsServer) IsEmailVerified(context.Context, *accounts.IsEmailVerifiedRequest) (*accounts.IsEmailVerifiedResponse, error) { - panic("unimplemented") -} - -// RemoveSession implements [v1.AccountsServiceServer]. -func (a *AccountsServer) RemoveSession(context.Context, *accounts.RemoveSessionRequest) (*emptypb.Empty, error) { - panic("unimplemented") -} - -// TokenAuthorization implements [v1.AccountsServiceServer]. -func (a *AccountsServer) TokenAuthorization(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - panic("unimplemented") -} diff --git a/cmd/server.go b/cmd/server.go index 6037ed2..82bde3c 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -7,10 +7,10 @@ import ( "strings" "time" - v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/api/v1" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/postgres" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1" @@ -68,7 +68,7 @@ func (cmd *Server) Run(ctx context.Context) error { Addr: cmd.RedisHost, }) - authController := controllers.NewAuthController( + authController := services.NewAuthController( []byte(cmd.JWTSecret), cmd.AccessTokenTTL, cmd.RefreshTokenTTL, @@ -97,12 +97,12 @@ func (cmd *Server) Run(ctx context.Context) error { reflection.Register(grpcServer) } - tokenCtrl := &controllers.TokenController{ + tokenCtrl := &services.TokenController{ DB: db, Redis: rdb, } - accountCtrl := &controllers.AccountController{ + accountCtrl := &services.AccountController{ HashCost: cmd.HashCost, DB: db, DevMode: cmd.DevMode, diff --git a/internal/api/v1/accounts.go b/internal/api/v1/accounts.go new file mode 100644 index 0000000..c97f88a --- /dev/null +++ b/internal/api/v1/accounts.go @@ -0,0 +1,89 @@ +package v1 + +import ( + "context" + "errors" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" + accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +func NewAccountServer( + accountsCtrl *services.AccountController, + authorizationCtrl *services.AuthController, +) *AccountsServer { + return &AccountsServer{ + accountsCtrl: accountsCtrl, + authorizationCtrl: authorizationCtrl, + } +} + +//var _ accounts.AccountsServiceServer = (*AccountsServer)(nil) + +type AccountsServer struct { + accounts.UnimplementedAccountsServiceServer + accountsCtrl *services.AccountController + authorizationCtrl *services.AuthController +} + +func (srv *AccountsServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { + claims, err := services.ClaimsFromContext(ctx) + if err != nil { + return nil, status.Error(codes.Aborted, "Context is invalid") + } + + if claims.TokenType != services.TokenTypeRefresh { + return nil, status.Error(codes.Unauthenticated, "Invalid token") + } + + session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID) + if err != nil { + if errors.Is(err, services.ErrSessionNotFound) { + return nil, status.Error(codes.Unauthenticated, "Session doesn't exists") + } + return nil, status.Error(codes.Internal, "Somethings is broken on our side") + } + + if session.UserID != claims.UserID { + return nil, status.Error(codes.Unauthenticated, "Invalid session") + } + + accessToken, _, err := srv.authorizationCtrl.GenerateToken(&services.JWTData{ + UserID: claims.UserID, + TokenType: services.TokenTypeAccess, + TokenAud: services.TokenAudWeb, + }) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't generate an access token") + } + + refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&services.JWTData{ + UserID: claims.UserID, + TokenType: services.TokenTypeRefresh, + TokenAud: services.TokenAudWeb, + }) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't generate an access token") + } + newSession := &services.Session{UserID: session.UserID} + + if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil { + return nil, status.Error(codes.Aborted, "Couldn't store session") + } + + header := metadata.New(map[string]string{ + "X-Access-Token": accessToken, + "X-Refresh-Token": refreshToken, + }) + if err := grpc.SetHeader(ctx, header); err != nil { + return nil, status.Error(codes.Aborted, "Couldn't set metadata") + } + + return &emptypb.Empty{}, nil +} diff --git a/api/v1/projects.go b/internal/api/v1/projects.go similarity index 100% rename from api/v1/projects.go rename to internal/api/v1/projects.go diff --git a/api/v1/public_accounts.go b/internal/api/v1/public_accounts.go similarity index 73% rename from api/v1/public_accounts.go rename to internal/api/v1/public_accounts.go index fe9495c..a59c669 100644 --- a/api/v1/public_accounts.go +++ b/internal/api/v1/public_accounts.go @@ -3,15 +3,15 @@ package v1 import ( "context" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func NewPublicAccountServer( - accountsCtrl *controllers.AccountController, - authorizationCtrl *controllers.AuthController, + accountsCtrl *services.AccountController, + authorizationCtrl *services.AuthController, ) *PublicAccountService { return &PublicAccountService{ accountsCtrl: accountsCtrl, @@ -21,8 +21,8 @@ func NewPublicAccountServer( type PublicAccountService struct { accounts.UnimplementedPublicAccountsServiceServer - accountsCtrl *controllers.AccountController - authorizationCtrl *controllers.AuthController + accountsCtrl *services.AccountController + authorizationCtrl *services.AuthController } func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRequest) (*accounts.SignInResponse, error) { @@ -30,25 +30,25 @@ func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRe if err != nil { return nil, status.Error(codes.Aborted, "Couldn't create a user") } - accessToken, _, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{ + accessToken, _, err := a.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: id, - TokenType: controllers.TokenTypeAccess, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeAccess, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{ + refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: id, - TokenType: controllers.TokenTypeRefresh, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeRefresh, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - session := &controllers.Session{UserID: id} + session := &services.Session{UserID: id} if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil { return nil, status.Error(codes.Aborted, "Couldn't store session") @@ -63,7 +63,7 @@ func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRe // Create a new account in Softplayer func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*accounts.SignUpResponse, error) { - data := &controllers.AccountData{ + data := &services.AccountData{ Password: in.GetPassword(), Email: in.GetEmail(), Name: in.PersonalData.GetName(), @@ -74,25 +74,25 @@ func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRe return nil, status.Error(codes.Aborted, "Couldn't create a user") } - accessToken, _, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{ + accessToken, _, err := a.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: id, - TokenType: controllers.TokenTypeAccess, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeAccess, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{ + refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: id, - TokenType: controllers.TokenTypeRefresh, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeRefresh, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - session := &controllers.Session{UserID: id} + session := &services.Session{UserID: id} if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil { return nil, status.Error(codes.Aborted, "Couldn't store session") diff --git a/api/v1/public_tokens.go b/internal/api/v1/public_tokens.go similarity index 79% rename from api/v1/public_tokens.go rename to internal/api/v1/public_tokens.go index 30e777f..da267cd 100644 --- a/api/v1/public_tokens.go +++ b/internal/api/v1/public_tokens.go @@ -4,7 +4,7 @@ import ( "context" "errors" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -17,13 +17,13 @@ import ( type PublicTokensServer struct { tokens.UnimplementedPublicTokensServiceServer - tokenCtrl *controllers.TokenController - authorizationCtrl *controllers.AuthController + tokenCtrl *services.TokenController + authorizationCtrl *services.AuthController } func NewPublicTokensServer( - tokenCtrl *controllers.TokenController, - authorizationCtrl *controllers.AuthController, + tokenCtrl *services.TokenController, + authorizationCtrl *services.AuthController, ) *PublicTokensServer { return &PublicTokensServer{ tokenCtrl: tokenCtrl, @@ -34,19 +34,19 @@ func NewPublicTokensServer( func (srv *PublicTokensServer) AuthenticateWithToken(ctx context.Context, in *tokens.AuthenticateWithTokenRequest) (*emptypb.Empty, error) { tokenAuthRes, err := srv.tokenCtrl.AuthenticateWithToken(ctx, in.TokenValue.Token) if err != nil { - if errors.Is(err, controllers.ErrBadToken) { + if errors.Is(err, services.ErrBadToken) { return nil, status.Error(codes.Unauthenticated, "Token is not valid") } - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't authorize") } - jwtData := &controllers.JWTData{ + jwtData := &services.JWTData{ UserID: tokenAuthRes.UserID, - TokenType: controllers.TokenTypeAccess, - TokenAud: controllers.TokenAudToken, + TokenType: services.TokenTypeAccess, + TokenAud: services.TokenAudToken, Scope: tokenAuthRes.Scope, } accessToken, _, err := srv.authorizationCtrl.GenerateToken(jwtData) diff --git a/api/v1/refresh_session.go b/internal/api/v1/refresh_session.go similarity index 100% rename from api/v1/refresh_session.go rename to internal/api/v1/refresh_session.go diff --git a/api/v1/test.go b/internal/api/v1/test.go similarity index 100% rename from api/v1/test.go rename to internal/api/v1/test.go diff --git a/api/v1/tokens.go b/internal/api/v1/tokens.go similarity index 88% rename from api/v1/tokens.go rename to internal/api/v1/tokens.go index 812abd9..7a75dfd 100644 --- a/api/v1/tokens.go +++ b/internal/api/v1/tokens.go @@ -4,7 +4,7 @@ import ( "context" "errors" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -18,13 +18,13 @@ import ( // TokensServer implements the Token Service type TokensServer struct { tokens.UnimplementedTokensServiceServer - tokenCtrl *controllers.TokenController - authorizationCtrl *controllers.AuthController + tokenCtrl *services.TokenController + authorizationCtrl *services.AuthController } func NewTokensServer( - tokenCtrl *controllers.TokenController, - authorizationCtrl *controllers.AuthController, + tokenCtrl *services.TokenController, + authorizationCtrl *services.AuthController, ) *TokensServer { return &TokensServer{ tokenCtrl: tokenCtrl, @@ -34,7 +34,7 @@ func NewTokensServer( // CreateToken implements [v1.TokensServiceServer]. func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateTokenRequest) (*tokens.CreateTokenResponse, error) { - claims, err := controllers.ClaimsFromContext(ctx) + claims, err := services.ClaimsFromContext(ctx) if err != nil { return nil, status.Error(codes.Aborted, "Context is invalid") } @@ -50,7 +50,7 @@ func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateToken permissions[service] = methods.GetMethods() } - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: in.TokenMetadata.GetName(), UserID: claims.UserID, ExpiresAt: in.TokenMetadata.ExpiresAt.AsTime(), @@ -59,7 +59,7 @@ func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateToken token, tokenID, err := srv.tokenCtrl.Create(ctx, tokenData) if err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't create a token") @@ -75,7 +75,7 @@ func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateToken // ForceTokenExpiration implements [v1.TokensServiceServer]. func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.ForceTokenExpirationRequest) (*emptypb.Empty, error) { - claims, err := controllers.ClaimsFromContext(ctx) + claims, err := services.ClaimsFromContext(ctx) if err != nil { return nil, status.Error(codes.Aborted, "Context is invalid") } @@ -84,14 +84,14 @@ func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.Fo } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") } if err := srv.tokenCtrl.ForceExpiration(ctx, in.TokenUuid.GetUuid()); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't create a token") @@ -101,7 +101,7 @@ func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.Fo // GetToken implements [v1.TokensServiceServer]. func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenRequest) (*tokens.GetTokenResponse, error) { - claims, err := controllers.ClaimsFromContext(ctx) + claims, err := services.ClaimsFromContext(ctx) if err != nil { return nil, status.Error(codes.Aborted, "Context is invalid") } @@ -109,7 +109,7 @@ func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenReques return nil, status.Error(codes.Aborted, "Context is invalid") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") @@ -117,7 +117,7 @@ func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenReques token, err := srv.tokenCtrl.Get(ctx, in.TokenUuid.Uuid, claims.UserID) if err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") @@ -141,7 +141,7 @@ func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenReques // ListTokens implements [v1.TokensServiceServer]. func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreamingServer[tokens.ListTokensResponse]) error { - claims, err := controllers.ClaimsFromContext(stream.Context()) + claims, err := services.ClaimsFromContext(stream.Context()) if err != nil { return status.Error(codes.Aborted, "Context is invalid") } @@ -151,7 +151,7 @@ func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreami tokensRes, err := srv.tokenCtrl.List(stream.Context(), claims.UserID) if err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return status.Error(codes.Internal, "Something is broken on our side") } return status.Error(codes.Aborted, "Couldn't list tokens") @@ -179,7 +179,7 @@ func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreami // RegenerateToken implements [v1.TokensServiceServer]. func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.RegenerateTokenRequest) (*tokens.RegenerateTokenResponse, error) { - claims, err := controllers.ClaimsFromContext(ctx) + claims, err := services.ClaimsFromContext(ctx) if err != nil { return nil, status.Error(codes.Aborted, "Context is invalid") } @@ -187,7 +187,7 @@ func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.Regener return nil, status.Error(codes.Aborted, "Context is invalid") } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") @@ -195,7 +195,7 @@ func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.Regener tokenVal, err := srv.tokenCtrl.Regenerate(ctx, in.TokenUuid.GetUuid()) if err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") @@ -209,7 +209,7 @@ func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.Regener // UpdateToken implements [v1.TokensServiceServer]. func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateTokenRequest) (*tokens.UpdateTokenResponse, error) { - claims, err := controllers.ClaimsFromContext(ctx) + claims, err := services.ClaimsFromContext(ctx) if err != nil { return nil, status.Error(codes.Aborted, "Context is invalid") } @@ -218,7 +218,7 @@ func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateToken } if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "User is now allowed to manipulate this token") @@ -231,12 +231,12 @@ func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateToken for service, methods := range in.TokenPermissions.Permissions { permissions[service] = methods.GetMethods() } - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: in.TokenMetadata.Name, Scopes: permissions, } if err := srv.tokenCtrl.Update(ctx, tokenData); err != nil { - if errors.Is(err, controllers.ErrServerError) { + if errors.Is(err, services.ErrServerError) { return nil, status.Error(codes.Internal, "Something is broken on our side") } return nil, status.Error(codes.Aborted, "Couldn't list tokens") diff --git a/internal/repository/projects.go b/internal/repository/projects.go index 85bac95..35563fd 100644 --- a/internal/repository/projects.go +++ b/internal/repository/projects.go @@ -11,9 +11,9 @@ type ProjectData struct { Name string Slug string Description string - OwnerID string - CreatedAt string - ArchivedAt time.Time + CreatedBy string + CreatedAt time.Time + ArchivedAt sql.NullTime Blocked bool UpdatedAt time.Time UpdatedBy string diff --git a/internal/repository/projects_test.go b/internal/repository/projects_test.go new file mode 100644 index 0000000..534f39f --- /dev/null +++ b/internal/repository/projects_test.go @@ -0,0 +1,26 @@ +package repository_test + +import ( + "database/sql" + "testing" + "time" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository" + "github.com/google/uuid" +) + +func TestProjectsRepository_Success(t *testing.T) { + createdBy := uuid.NewString() + _ = &repository.ProjectData{ + UUID: uuid.NewString(), + Name: "test-1", + Slug: "test_1", + Description: "Test Project Number 1", + CreatedBy: createdBy, + CreatedAt: time.Now(), + ArchivedAt: sql.NullTime{Time: time.Now()}, + Blocked: false, + UpdatedAt: time.Now(), + UpdatedBy: createdBy, + } +} diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go new file mode 100644 index 0000000..8e5e3d4 --- /dev/null +++ b/internal/repository/transaction.go @@ -0,0 +1,22 @@ +package repository + +import ( + "context" + "database/sql" +) + +func StartTransaction(ctx context.Context, db *sql.DB) (*sql.Tx, error) { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + return tx, nil +} + +func CommitTransaction(ctx context.Context, tx *sql.Tx) error { + return tx.Commit() +} + +func RollBackTransaction(ctx context.Context, tx *sql.Tx) error { + return tx.Rollback() +} diff --git a/internal/controllers/accounts.go b/internal/services/accounts.go similarity index 99% rename from internal/controllers/accounts.go rename to internal/services/accounts.go index acb5010..fc814c2 100644 --- a/internal/controllers/accounts.go +++ b/internal/services/accounts.go @@ -1,4 +1,4 @@ -package controllers +package services import ( "context" diff --git a/internal/controllers/accounts_test.go b/internal/services/accounts_test.go similarity index 86% rename from internal/controllers/accounts_test.go rename to internal/services/accounts_test.go index 22e3f58..f9764c2 100644 --- a/internal/controllers/accounts_test.go +++ b/internal/services/accounts_test.go @@ -1,4 +1,4 @@ -package controllers_test +package services_test import ( "context" @@ -8,8 +8,8 @@ import ( "testing" "time" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/postgres" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" "github.com/google/uuid" _ "github.com/jackc/pgx/v5/stdlib" "github.com/redis/go-redis/v9" @@ -40,8 +40,8 @@ func newTestRedisConnection() *redis.Client { }) } -func newTestAccountController(ctx context.Context) *controllers.AccountController { - return &controllers.AccountController{ +func newTestAccountController(ctx context.Context) *services.AccountController { + return &services.AccountController{ DB: newTestDBConnection(ctx), Redis: newTestRedisConnection(), DevMode: true, @@ -67,7 +67,7 @@ func newTestUniqueEmail(prefix string) string { func TestIntegrationAccountCreate_Success(t *testing.T) { ctrl := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), Surname: "Doe", @@ -81,7 +81,7 @@ func TestIntegrationAccountCreate_Success(t *testing.T) { func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) { ctrl := newTestAccountController(t.Context()) email := newTestUniqueEmail("accounts") - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: email, Surname: "Doe", @@ -94,13 +94,13 @@ func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) { id, err = ctrl.Create(t.Context(), accountData) assert.Empty(t, id) assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrEmailUsed) + assert.ErrorIs(t, err, services.ErrEmailUsed) } func TestIntegrationAccountLogin_Success(t *testing.T) { ctrl := newTestAccountController(t.Context()) email := newTestUniqueEmail("accounts") - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: email, } @@ -117,7 +117,7 @@ func TestIntegrationAccountLogin_Success(t *testing.T) { func TestIntegrationAccountLogin_WrongPassword(t *testing.T) { ctrl := newTestAccountController(t.Context()) email := newTestUniqueEmail("accounts") - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: email, } @@ -129,13 +129,13 @@ func TestIntegrationAccountLogin_WrongPassword(t *testing.T) { id, err = ctrl.Login(t.Context(), accountData.Email, "Wrong Password") assert.Empty(t, id) assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrWrongPassword) + assert.ErrorIs(t, err, services.ErrWrongPassword) } func TestIntegrationAccountLogin_WrongEmail(t *testing.T) { ctrl := newTestAccountController(t.Context()) email := newTestUniqueEmail("accounts") - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: email, } @@ -147,5 +147,5 @@ func TestIntegrationAccountLogin_WrongEmail(t *testing.T) { id, err = ctrl.Login(t.Context(), "some@email.com", "Wrong Password") assert.Empty(t, id) assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrUserNotFound) + assert.ErrorIs(t, err, services.ErrUserNotFound) } diff --git a/internal/controllers/authorization.go b/internal/services/authorization.go similarity index 99% rename from internal/controllers/authorization.go rename to internal/services/authorization.go index 31da861..b0f94d0 100644 --- a/internal/controllers/authorization.go +++ b/internal/services/authorization.go @@ -1,4 +1,4 @@ -package controllers +package services import ( "context" diff --git a/internal/controllers/authorization_test.go b/internal/services/authorization_test.go similarity index 68% rename from internal/controllers/authorization_test.go rename to internal/services/authorization_test.go index 567014a..4673c0f 100644 --- a/internal/controllers/authorization_test.go +++ b/internal/services/authorization_test.go @@ -1,10 +1,10 @@ -package controllers_test +package services_test import ( "testing" "time" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) @@ -16,24 +16,24 @@ var ( ) func TestGenerateInvalidTokenType(t *testing.T) { - data := &controllers.JWTData{ + data := &services.JWTData{ UserID: testUserID, TokenType: "invalid_type", } - authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) + authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) token, _, err := authCtrl.GenerateToken(data) assert.Equal(t, "", token) - assert.ErrorIs(t, controllers.ErrUnknownTokenType, err) + assert.ErrorIs(t, services.ErrUnknownTokenType, err) } func TestGenerateValidateAccessToken(t *testing.T) { - data := &controllers.JWTData{ + data := &services.JWTData{ UserID: testUserID, - TokenType: controllers.TokenTypeAccess, + TokenType: services.TokenTypeAccess, } - authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) + authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) now := time.Now() token, _, err := authCtrl.GenerateToken(data) assert.NoError(t, err) @@ -43,18 +43,18 @@ func TestGenerateValidateAccessToken(t *testing.T) { assert.NoError(t, err) assert.Equal(t, testUserID, claims.UserID) assert.NotEmpty(t, claims.TokenID) - assert.Equal(t, controllers.TokenTypeAccess, claims.TokenType) + assert.Equal(t, services.TokenTypeAccess, claims.TokenType) assert.Equal(t, now.Add(testAccessTTL).Unix(), claims.ExpiresAt.Unix()) assert.Equal(t, now.Unix(), claims.IssuedAt.Unix()) assert.Equal(t, now.Unix(), claims.NotBefore.Unix()) } func TestGenerateValidateRefreshToken(t *testing.T) { - data := &controllers.JWTData{ + data := &services.JWTData{ UserID: testUserID, - TokenType: controllers.TokenTypeRefresh, + TokenType: services.TokenTypeRefresh, } - authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) + authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil) now := time.Now() token, _, err := authCtrl.GenerateToken(data) assert.NoError(t, err) @@ -64,7 +64,7 @@ func TestGenerateValidateRefreshToken(t *testing.T) { assert.NoError(t, err) assert.Equal(t, testUserID, claims.UserID) assert.NotEmpty(t, claims.TokenID) - assert.Equal(t, controllers.TokenTypeRefresh, claims.TokenType) + assert.Equal(t, services.TokenTypeRefresh, claims.TokenType) assert.Equal(t, now.Add(testRefreshTTL).Unix(), claims.ExpiresAt.Unix()) assert.Equal(t, now.Unix(), claims.IssuedAt.Unix()) assert.Equal(t, now.Unix(), claims.NotBefore.Unix()) diff --git a/internal/controllers/projects.go b/internal/services/projects.go similarity index 72% rename from internal/controllers/projects.go rename to internal/services/projects.go index 95fce54..9d56803 100644 --- a/internal/controllers/projects.go +++ b/internal/services/projects.go @@ -1,13 +1,21 @@ -package controllers +package services -import "context" +import ( + "context" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger" +) type ProjectsController struct{} type ProjectData struct{} // Create a new project +// It should create a project and set an owner to it func (ctrl *ProjectsController) Create(ctx context.Context, data *ProjectData) (id string, err error) { + log := logger.FromContext(ctx) + log.V(2).Info("Creating a project") + return "", nil } diff --git a/internal/controllers/tokens.go b/internal/services/tokens.go similarity index 99% rename from internal/controllers/tokens.go rename to internal/services/tokens.go index 85a675a..a34b94f 100644 --- a/internal/controllers/tokens.go +++ b/internal/services/tokens.go @@ -1,6 +1,6 @@ // Package controllers for token management // This a token controller, that implements the logic around tokens -package controllers +package services import ( "context" diff --git a/internal/controllers/tokens_test.go b/internal/services/tokens_test.go similarity index 88% rename from internal/controllers/tokens_test.go rename to internal/services/tokens_test.go index 6a6d5a7..0a5eaae 100644 --- a/internal/controllers/tokens_test.go +++ b/internal/services/tokens_test.go @@ -1,4 +1,4 @@ -package controllers_test +package services_test import ( "context" @@ -6,13 +6,13 @@ import ( "testing" "time" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) -func newTestTokensController(ctx context.Context) *controllers.TokenController { - return &controllers.TokenController{ +func newTestTokensController(ctx context.Context) *services.TokenController { + return &services.TokenController{ DB: newTestDBConnection(ctx), Redis: newTestRedisConnection(), } @@ -21,14 +21,14 @@ func newTestTokensController(ctx context.Context) *controllers.TokenController { func TestIntegrationCreateToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), @@ -45,7 +45,7 @@ func TestIntegrationCreateToken_Success(t *testing.T) { } func TestIntegrationCreateToken_UserNotExist(t *testing.T) { - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: uuid.NewString(), ExpiresAt: time.Now().Add(time.Second * 5), @@ -58,7 +58,7 @@ func TestIntegrationCreateToken_UserNotExist(t *testing.T) { tokenVal, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrUserNotFound) + assert.ErrorIs(t, err, services.ErrUserNotFound) assert.Empty(t, tokenID) assert.Empty(t, tokenVal) } @@ -66,7 +66,7 @@ func TestIntegrationCreateToken_UserNotExist(t *testing.T) { func TestIntegrationGetToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -75,7 +75,7 @@ func TestIntegrationGetToken_Success(t *testing.T) { userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -101,7 +101,7 @@ func TestIntegrationGetToken_NotExists(t *testing.T) { ctrl := newTestTokensController(t.Context()) token, err := ctrl.Get(t.Context(), uuid.NewString(), uuid.NewString()) assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrTokenNotFound) + assert.ErrorIs(t, err, services.ErrTokenNotFound) assert.Empty(t, token) } @@ -109,14 +109,14 @@ func TestIntegrationGetToken_WrongRequest(t *testing.T) { ctrl := newTestTokensController(t.Context()) token, err := ctrl.Get(t.Context(), "test", "test") assert.Error(t, err) - assert.ErrorIs(t, err, controllers.ErrServerError) + assert.ErrorIs(t, err, services.ErrServerError) assert.Empty(t, token) } func TestIntegrationVerifyTokenOwner_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -124,7 +124,7 @@ func TestIntegrationVerifyTokenOwner_Success(t *testing.T) { userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -142,12 +142,12 @@ func TestIntegrationVerifyTokenOwner_Success(t *testing.T) { func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } - secondAccountData := &controllers.AccountData{ + secondAccountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -157,7 +157,7 @@ func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) { secondUserID, err := ctrlAccount.Create(t.Context(), secondAccountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -169,13 +169,13 @@ func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) { ctrl := newTestTokensController(t.Context()) _, tokenID, err := ctrl.Create(t.Context(), tokenData) assert.NoError(t, err) - assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), controllers.ErrUserTokenMismatch) + assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), services.ErrUserTokenMismatch) } func TestIntegrationForceExpiration_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -183,7 +183,7 @@ func TestIntegrationForceExpiration_Success(t *testing.T) { userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -205,7 +205,7 @@ func TestIntegrationForceExpiration_Success(t *testing.T) { func TestIntegrationRegenerateToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -214,7 +214,7 @@ func TestIntegrationRegenerateToken_Success(t *testing.T) { userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -255,7 +255,7 @@ func TestIntegrationRegenerateToken_Success(t *testing.T) { func TestIntegrationListTokens_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } @@ -263,7 +263,7 @@ func TestIntegrationListTokens_Success(t *testing.T) { userID, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenDataOne := &controllers.TokenData{ + tokenDataOne := &services.TokenData{ Name: "Test Token", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -271,7 +271,7 @@ func TestIntegrationListTokens_Success(t *testing.T) { "Test": {"test", "test2"}, }, } - tokenDataTwo := &controllers.TokenData{ + tokenDataTwo := &services.TokenData{ Name: "Test Token again", UserID: userID, ExpiresAt: time.Now().Add(time.Second * 5), @@ -293,14 +293,14 @@ func TestIntegrationListTokens_Success(t *testing.T) { func TestIntegrationAuthenticateWithToken_Success(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), @@ -330,19 +330,19 @@ func TestIntegrationAuthenticateWithToken_UnknownToken(t *testing.T) { auth, err := ctrl.AuthenticateWithToken(t.Context(), "dummy") assert.Error(t, err) assert.Nil(t, auth) - assert.ErrorIs(t, err, controllers.ErrTokenNotFound) + assert.ErrorIs(t, err, services.ErrTokenNotFound) } func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), @@ -365,19 +365,19 @@ func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) { auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.Error(t, err) assert.Nil(t, auth) - assert.ErrorIs(t, err, controllers.ErrBadToken) + assert.ErrorIs(t, err, services.ErrBadToken) } func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) { ctrlAccount := newTestAccountController(t.Context()) - accountData := &controllers.AccountData{ + accountData := &services.AccountData{ Password: "qwertyu9", Email: newTestUniqueEmail("accounts"), } id, err := ctrlAccount.Create(t.Context(), accountData) assert.NoError(t, err) - tokenData := &controllers.TokenData{ + tokenData := &services.TokenData{ Name: "Test Token", UserID: id, ExpiresAt: time.Now().Add(time.Second * 5), @@ -399,5 +399,5 @@ func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) { auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal) assert.Error(t, err) assert.Nil(t, auth) - assert.ErrorIs(t, err, controllers.ErrBadToken) + assert.ErrorIs(t, err, services.ErrBadToken) }