Compare commits
4 Commits
add-projec
...
renovate/g
| Author | SHA1 | Date | |
|---|---|---|---|
| ef6747d0a6 | |||
|
34d44cf1de
|
|||
|
08087d453f
|
|||
|
a9784c3436
|
35
Taskfile.yml
35
Taskfile.yml
@@ -61,30 +61,6 @@ tasks:
|
|||||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
||||||
cmd: go run main.go migrate --migrations-path=file://migrations
|
cmd: go run main.go migrate --migrations-path=file://migrations
|
||||||
|
|
||||||
force-migration:
|
|
||||||
desc: Force migrate to a desired version
|
|
||||||
vars:
|
|
||||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
|
||||||
cmd: "{{ .MIGRATE }} -path=./migrations -database={{ .SOFTPLAYER_DB_CONNECTION_STRING }} force {{ .CLI_ARGS }}"
|
|
||||||
deps:
|
|
||||||
- migrate
|
|
||||||
|
|
||||||
down-migrations:
|
|
||||||
desc: Roll back all migrations
|
|
||||||
vars:
|
|
||||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
|
||||||
cmd: "{{ .MIGRATE }} -path=./migrations -database={{ .SOFTPLAYER_DB_CONNECTION_STRING }} down -all"
|
|
||||||
deps:
|
|
||||||
- migrate
|
|
||||||
|
|
||||||
drop-migrations:
|
|
||||||
desc: Drop migrations
|
|
||||||
vars:
|
|
||||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
|
||||||
cmd: "{{ .MIGRATE }} -path=./migrations -database={{ .SOFTPLAYER_DB_CONNECTION_STRING }} drop"
|
|
||||||
deps:
|
|
||||||
- migrate
|
|
||||||
|
|
||||||
run-server-dev:
|
run-server-dev:
|
||||||
desc: Run the local dev server
|
desc: Run the local dev server
|
||||||
deps:
|
deps:
|
||||||
@@ -137,8 +113,6 @@ tasks:
|
|||||||
desc: Add a new database migration
|
desc: Add a new database migration
|
||||||
silent: true
|
silent: true
|
||||||
cmd: "{{.MIGRATE}} create -dir migrations -ext sql {{.CLI_ARGS}}"
|
cmd: "{{.MIGRATE}} create -dir migrations -ext sql {{.CLI_ARGS}}"
|
||||||
deps:
|
|
||||||
- migrate
|
|
||||||
|
|
||||||
# Install required tools
|
# Install required tools
|
||||||
localbin:
|
localbin:
|
||||||
@@ -158,7 +132,6 @@ tasks:
|
|||||||
TARGET: "{{.MIGRATE}}"
|
TARGET: "{{.MIGRATE}}"
|
||||||
PACKAGE: github.com/golang-migrate/migrate/v4/cmd/migrate
|
PACKAGE: github.com/golang-migrate/migrate/v4/cmd/migrate
|
||||||
VERSION: latest
|
VERSION: latest
|
||||||
TAGS: "postgres"
|
|
||||||
|
|
||||||
go-install-tool:
|
go-install-tool:
|
||||||
internal: true
|
internal: true
|
||||||
@@ -177,13 +150,7 @@ tasks:
|
|||||||
echo "Downloading $PACKAGE"
|
echo "Downloading $PACKAGE"
|
||||||
rm -f "$TARGET"
|
rm -f "$TARGET"
|
||||||
|
|
||||||
TAGS="{{.TAGS}}"
|
GOBIN="{{.LOCALBIN}}" go install "$PACKAGE"
|
||||||
if [ -n "$TAGS" ]; then
|
|
||||||
echo "Using build tags: $TAGS"
|
|
||||||
GOBIN="{{.LOCALBIN}}" go install -tags "$TAGS" "$PACKAGE"
|
|
||||||
else
|
|
||||||
GOBIN="{{.LOCALBIN}}" go install "$PACKAGE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "{{.LOCALBIN}}/$(basename "$TARGET")" "$VERSIONED"
|
mv "{{.LOCALBIN}}/$(basename "$TARGET")" "$VERSIONED"
|
||||||
ln -sf "$(realpath "$VERSIONED")" "$TARGET"
|
ln -sf "$(realpath "$VERSIONED")" "$TARGET"
|
||||||
|
|||||||
42
api/v1/accounts.go
Normal file
42
api/v1/accounts.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
@@ -3,19 +3,15 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
|
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/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPublicAccountServer(
|
func NewPublicAccountServer(
|
||||||
accountsCtrl *services.AccountController,
|
accountsCtrl *controllers.AccountController,
|
||||||
authorizationCtrl *services.AuthController,
|
authorizationCtrl *controllers.AuthController,
|
||||||
) *PublicAccountService {
|
) *PublicAccountService {
|
||||||
return &PublicAccountService{
|
return &PublicAccountService{
|
||||||
accountsCtrl: accountsCtrl,
|
accountsCtrl: accountsCtrl,
|
||||||
@@ -25,91 +21,85 @@ func NewPublicAccountServer(
|
|||||||
|
|
||||||
type PublicAccountService struct {
|
type PublicAccountService struct {
|
||||||
accounts.UnimplementedPublicAccountsServiceServer
|
accounts.UnimplementedPublicAccountsServiceServer
|
||||||
accountsCtrl *services.AccountController
|
accountsCtrl *controllers.AccountController
|
||||||
authorizationCtrl *services.AuthController
|
authorizationCtrl *controllers.AuthController
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRequest) (*empty.Empty, error) {
|
func (a *PublicAccountService) SignIn(ctx context.Context, in *accounts.SignInRequest) (*accounts.SignInResponse, error) {
|
||||||
id, err := a.accountsCtrl.Login(ctx, in.GetEmail(), in.GetPassword())
|
id, err := a.accountsCtrl.Login(ctx, in.GetEmail(), in.GetPassword())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't create a user")
|
return nil, status.Error(codes.Aborted, "Couldn't create a user")
|
||||||
}
|
}
|
||||||
accessToken, _, err := a.authorizationCtrl.GenerateToken(&services.JWTData{
|
accessToken, _, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{
|
||||||
UserID: id,
|
UserID: id,
|
||||||
TokenType: services.TokenTypeAccess,
|
TokenType: controllers.TokenTypeAccess,
|
||||||
TokenAud: services.TokenAudWeb,
|
TokenAud: controllers.TokenAudWeb,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&services.JWTData{
|
refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{
|
||||||
UserID: id,
|
UserID: id,
|
||||||
TokenType: services.TokenTypeRefresh,
|
TokenType: controllers.TokenTypeRefresh,
|
||||||
TokenAud: services.TokenAudWeb,
|
TokenAud: controllers.TokenAudWeb,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
session := &services.Session{UserID: id}
|
session := &controllers.Session{UserID: id}
|
||||||
|
|
||||||
if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil {
|
if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't store session")
|
return nil, status.Error(codes.Aborted, "Couldn't store session")
|
||||||
}
|
}
|
||||||
header := metadata.New(map[string]string{
|
return &accounts.SignInResponse{
|
||||||
"X-Access-Token": accessToken,
|
TokenPair: &accounts.TokenPair{
|
||||||
"X-Refresh-Token": refreshToken,
|
AccessToken: accessToken,
|
||||||
})
|
RefreshToken: refreshToken,
|
||||||
if err := grpc.SetHeader(ctx, header); err != nil {
|
},
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't set metadata")
|
}, nil
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new account in Softplayer
|
// Create a new account in Softplayer
|
||||||
func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*empty.Empty, error) {
|
func (a *PublicAccountService) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*accounts.SignUpResponse, error) {
|
||||||
data := &services.AccountData{
|
data := &controllers.AccountData{
|
||||||
Password: in.GetPassword(),
|
Password: in.GetPassword(),
|
||||||
Email: in.GetEmail(),
|
Email: in.GetEmail(),
|
||||||
Name: in.PersonalData.GetName(),
|
|
||||||
Surname: in.PersonalData.GetSurname(),
|
|
||||||
}
|
}
|
||||||
id, err := a.accountsCtrl.Create(ctx, data)
|
id, err := a.accountsCtrl.Create(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't create a user")
|
return nil, status.Error(codes.Aborted, "Couldn't create a user")
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, _, err := a.authorizationCtrl.GenerateToken(&services.JWTData{
|
accessToken, _, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{
|
||||||
UserID: id,
|
UserID: id,
|
||||||
TokenType: services.TokenTypeAccess,
|
TokenType: controllers.TokenTypeAccess,
|
||||||
TokenAud: services.TokenAudWeb,
|
TokenAud: controllers.TokenAudWeb,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&services.JWTData{
|
refreshToken, tokenID, err := a.authorizationCtrl.GenerateToken(&controllers.JWTData{
|
||||||
UserID: id,
|
UserID: id,
|
||||||
TokenType: services.TokenTypeRefresh,
|
TokenType: controllers.TokenTypeRefresh,
|
||||||
TokenAud: services.TokenAudWeb,
|
TokenAud: controllers.TokenAudWeb,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
session := &services.Session{UserID: id}
|
session := &controllers.Session{UserID: id}
|
||||||
|
|
||||||
if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil {
|
if err := a.authorizationCtrl.SaveSession(ctx, tokenID, session); err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't store session")
|
return nil, status.Error(codes.Aborted, "Couldn't store session")
|
||||||
}
|
}
|
||||||
|
|
||||||
header := metadata.New(map[string]string{
|
return &accounts.SignUpResponse{
|
||||||
"X-Access-Token": accessToken,
|
TokenPair: &accounts.TokenPair{
|
||||||
"X-Refresh-Token": refreshToken,
|
AccessToken: accessToken,
|
||||||
})
|
RefreshToken: refreshToken,
|
||||||
if err := grpc.SetHeader(ctx, header); err != nil {
|
},
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't set metadata")
|
}, nil
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -17,13 +17,13 @@ import (
|
|||||||
|
|
||||||
type PublicTokensServer struct {
|
type PublicTokensServer struct {
|
||||||
tokens.UnimplementedPublicTokensServiceServer
|
tokens.UnimplementedPublicTokensServiceServer
|
||||||
tokenCtrl *services.TokenController
|
tokenCtrl *controllers.TokenController
|
||||||
authorizationCtrl *services.AuthController
|
authorizationCtrl *controllers.AuthController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPublicTokensServer(
|
func NewPublicTokensServer(
|
||||||
tokenCtrl *services.TokenController,
|
tokenCtrl *controllers.TokenController,
|
||||||
authorizationCtrl *services.AuthController,
|
authorizationCtrl *controllers.AuthController,
|
||||||
) *PublicTokensServer {
|
) *PublicTokensServer {
|
||||||
return &PublicTokensServer{
|
return &PublicTokensServer{
|
||||||
tokenCtrl: tokenCtrl,
|
tokenCtrl: tokenCtrl,
|
||||||
@@ -34,19 +34,19 @@ func NewPublicTokensServer(
|
|||||||
func (srv *PublicTokensServer) AuthenticateWithToken(ctx context.Context, in *tokens.AuthenticateWithTokenRequest) (*emptypb.Empty, error) {
|
func (srv *PublicTokensServer) AuthenticateWithToken(ctx context.Context, in *tokens.AuthenticateWithTokenRequest) (*emptypb.Empty, error) {
|
||||||
tokenAuthRes, err := srv.tokenCtrl.AuthenticateWithToken(ctx, in.TokenValue.Token)
|
tokenAuthRes, err := srv.tokenCtrl.AuthenticateWithToken(ctx, in.TokenValue.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, services.ErrBadToken) {
|
if errors.Is(err, controllers.ErrBadToken) {
|
||||||
return nil, status.Error(codes.Unauthenticated, "Token is not valid")
|
return nil, status.Error(codes.Unauthenticated, "Token is not valid")
|
||||||
}
|
}
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't authorize")
|
return nil, status.Error(codes.Aborted, "Couldn't authorize")
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtData := &services.JWTData{
|
jwtData := &controllers.JWTData{
|
||||||
UserID: tokenAuthRes.UserID,
|
UserID: tokenAuthRes.UserID,
|
||||||
TokenType: services.TokenTypeAccess,
|
TokenType: controllers.TokenTypeAccess,
|
||||||
TokenAud: services.TokenAudToken,
|
TokenAud: controllers.TokenAudToken,
|
||||||
Scope: tokenAuthRes.Scope,
|
Scope: tokenAuthRes.Scope,
|
||||||
}
|
}
|
||||||
accessToken, _, err := srv.authorizationCtrl.GenerateToken(jwtData)
|
accessToken, _, err := srv.authorizationCtrl.GenerateToken(jwtData)
|
||||||
78
api/v1/refresh_session.go
Normal file
78
api/v1/refresh_session.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
|
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRefreshSessionServer(
|
||||||
|
authorizationCtrl *controllers.AuthController,
|
||||||
|
) *RefreshSessionService {
|
||||||
|
return &RefreshSessionService{
|
||||||
|
authorizationCtrl: authorizationCtrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshSessionService struct {
|
||||||
|
accounts.UnimplementedRefreshSessionServiceServer
|
||||||
|
authorizationCtrl *controllers.AuthController
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accounts.RefreshSessionRequest) (*accounts.RefreshSessionResponse, error) {
|
||||||
|
fmt.Println(in.GetRefreshToken())
|
||||||
|
claims, err := srv.authorizationCtrl.ParseToken(in.GetRefreshToken())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil, status.Error(codes.Aborted, "Invalid token is sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims.TokenType != controllers.TokenTypeRefresh {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "Invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, controllers.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(&controllers.JWTData{
|
||||||
|
UserID: claims.UserID,
|
||||||
|
TokenType: controllers.TokenTypeAccess,
|
||||||
|
TokenAud: controllers.TokenAudWeb,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{
|
||||||
|
UserID: claims.UserID,
|
||||||
|
TokenType: controllers.TokenTypeRefresh,
|
||||||
|
TokenAud: controllers.TokenAudWeb,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Aborted, "Couldn't generate an access token")
|
||||||
|
}
|
||||||
|
newSession := &controllers.Session{UserID: session.UserID}
|
||||||
|
|
||||||
|
if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil {
|
||||||
|
return nil, status.Error(codes.Aborted, "Couldn't store session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &accounts.RefreshSessionResponse{TokenPair: &accounts.TokenPair{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -18,13 +18,13 @@ import (
|
|||||||
// TokensServer implements the Token Service
|
// TokensServer implements the Token Service
|
||||||
type TokensServer struct {
|
type TokensServer struct {
|
||||||
tokens.UnimplementedTokensServiceServer
|
tokens.UnimplementedTokensServiceServer
|
||||||
tokenCtrl *services.TokenController
|
tokenCtrl *controllers.TokenController
|
||||||
authorizationCtrl *services.AuthController
|
authorizationCtrl *controllers.AuthController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokensServer(
|
func NewTokensServer(
|
||||||
tokenCtrl *services.TokenController,
|
tokenCtrl *controllers.TokenController,
|
||||||
authorizationCtrl *services.AuthController,
|
authorizationCtrl *controllers.AuthController,
|
||||||
) *TokensServer {
|
) *TokensServer {
|
||||||
return &TokensServer{
|
return &TokensServer{
|
||||||
tokenCtrl: tokenCtrl,
|
tokenCtrl: tokenCtrl,
|
||||||
@@ -34,7 +34,7 @@ func NewTokensServer(
|
|||||||
|
|
||||||
// CreateToken implements [v1.TokensServiceServer].
|
// CreateToken implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateTokenRequest) (*tokens.CreateTokenResponse, error) {
|
func (srv *TokensServer) CreateToken(ctx context.Context, in *tokens.CreateTokenRequest) (*tokens.CreateTokenResponse, error) {
|
||||||
claims, err := services.ClaimsFromContext(ctx)
|
claims, err := controllers.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Context is invalid")
|
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()
|
permissions[service] = methods.GetMethods()
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: in.TokenMetadata.GetName(),
|
Name: in.TokenMetadata.GetName(),
|
||||||
UserID: claims.UserID,
|
UserID: claims.UserID,
|
||||||
ExpiresAt: in.TokenMetadata.ExpiresAt.AsTime(),
|
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)
|
token, tokenID, err := srv.tokenCtrl.Create(ctx, tokenData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't create a token")
|
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].
|
// ForceTokenExpiration implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.ForceTokenExpirationRequest) (*emptypb.Empty, error) {
|
func (srv *TokensServer) ForceTokenExpiration(ctx context.Context, in *tokens.ForceTokenExpirationRequest) (*emptypb.Empty, error) {
|
||||||
claims, err := services.ClaimsFromContext(ctx)
|
claims, err := controllers.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Context is invalid")
|
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 err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
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")
|
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 err := srv.tokenCtrl.ForceExpiration(ctx, in.TokenUuid.GetUuid()); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't create a token")
|
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].
|
// GetToken implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenRequest) (*tokens.GetTokenResponse, error) {
|
func (srv *TokensServer) GetToken(ctx context.Context, in *tokens.GetTokenRequest) (*tokens.GetTokenResponse, error) {
|
||||||
claims, err := services.ClaimsFromContext(ctx)
|
claims, err := controllers.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Context is invalid")
|
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")
|
return nil, status.Error(codes.Aborted, "Context is invalid")
|
||||||
}
|
}
|
||||||
if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
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")
|
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)
|
token, err := srv.tokenCtrl.Get(ctx, in.TokenUuid.Uuid, claims.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't list tokens")
|
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].
|
// ListTokens implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreamingServer[tokens.ListTokensResponse]) error {
|
func (srv *TokensServer) ListTokens(in *emptypb.Empty, stream grpc.ServerStreamingServer[tokens.ListTokensResponse]) error {
|
||||||
claims, err := services.ClaimsFromContext(stream.Context())
|
claims, err := controllers.ClaimsFromContext(stream.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Error(codes.Aborted, "Context is invalid")
|
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)
|
tokensRes, err := srv.tokenCtrl.List(stream.Context(), claims.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return status.Error(codes.Internal, "Something is broken on our side")
|
return status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return status.Error(codes.Aborted, "Couldn't list tokens")
|
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].
|
// RegenerateToken implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.RegenerateTokenRequest) (*tokens.RegenerateTokenResponse, error) {
|
func (srv *TokensServer) RegenerateToken(ctx context.Context, in *tokens.RegenerateTokenRequest) (*tokens.RegenerateTokenResponse, error) {
|
||||||
claims, err := services.ClaimsFromContext(ctx)
|
claims, err := controllers.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Context is invalid")
|
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")
|
return nil, status.Error(codes.Aborted, "Context is invalid")
|
||||||
}
|
}
|
||||||
if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
if err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
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")
|
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())
|
tokenVal, err := srv.tokenCtrl.Regenerate(ctx, in.TokenUuid.GetUuid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't list tokens")
|
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].
|
// UpdateToken implements [v1.TokensServiceServer].
|
||||||
func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateTokenRequest) (*tokens.UpdateTokenResponse, error) {
|
func (srv *TokensServer) UpdateToken(ctx context.Context, in *tokens.UpdateTokenRequest) (*tokens.UpdateTokenResponse, error) {
|
||||||
claims, err := services.ClaimsFromContext(ctx)
|
claims, err := controllers.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Aborted, "Context is invalid")
|
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 err := srv.tokenCtrl.VerifyTokenOwner(ctx, claims.UserID, in.TokenUuid.Uuid); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
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")
|
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 {
|
for service, methods := range in.TokenPermissions.Permissions {
|
||||||
permissions[service] = methods.GetMethods()
|
permissions[service] = methods.GetMethods()
|
||||||
}
|
}
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: in.TokenMetadata.Name,
|
Name: in.TokenMetadata.Name,
|
||||||
Scopes: permissions,
|
Scopes: permissions,
|
||||||
}
|
}
|
||||||
if err := srv.tokenCtrl.Update(ctx, tokenData); err != nil {
|
if err := srv.tokenCtrl.Update(ctx, tokenData); err != nil {
|
||||||
if errors.Is(err, services.ErrServerError) {
|
if errors.Is(err, controllers.ErrServerError) {
|
||||||
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
return nil, status.Error(codes.Internal, "Something is broken on our side")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Aborted, "Couldn't list tokens")
|
return nil, status.Error(codes.Aborted, "Couldn't list tokens")
|
||||||
@@ -2,16 +2,15 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/api/v1"
|
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"
|
"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/helpers/postgres"
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
|
||||||
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
|
accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
|
||||||
test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1"
|
test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1"
|
||||||
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1"
|
||||||
@@ -22,9 +21,6 @@ import (
|
|||||||
_ "github.com/jackc/pgx/v5/stdlib"
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/health"
|
|
||||||
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,14 +38,12 @@ type Server struct {
|
|||||||
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
|
DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"`
|
||||||
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
|
RedisHost string `env:"SOFTPLAYER_REDIS_HOST"`
|
||||||
// JWT parameters
|
// JWT parameters
|
||||||
RefrestTokenTTL time.Duration `default:"8h"`
|
RefreshTokenTTL time.Duration `default:"8h"`
|
||||||
AccessTokenTTL time.Duration `default:"15m"`
|
AccessTokenTTL time.Duration `default:"15m"`
|
||||||
JWTSecret string `default:"qwertyu9"`
|
JWTSecret string `default:"qwertyu9"`
|
||||||
// Dev and logging
|
// Dev and logging
|
||||||
Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"`
|
Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"`
|
||||||
DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"`
|
DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"`
|
||||||
// HealthChecks
|
|
||||||
HealthCheckInterval time.Duration `env:"SOFTPLAYER_HEALTH_CHECK_INTERVAL" default:"5s"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the grpc backend server
|
// Run the grpc backend server
|
||||||
@@ -74,10 +68,10 @@ func (cmd *Server) Run(ctx context.Context) error {
|
|||||||
Addr: cmd.RedisHost,
|
Addr: cmd.RedisHost,
|
||||||
})
|
})
|
||||||
|
|
||||||
authController := services.NewAuthController(
|
authController := controllers.NewAuthController(
|
||||||
[]byte(cmd.JWTSecret),
|
[]byte(cmd.JWTSecret),
|
||||||
cmd.AccessTokenTTL,
|
cmd.AccessTokenTTL,
|
||||||
cmd.RefrestTokenTTL,
|
cmd.RefreshTokenTTL,
|
||||||
rdb,
|
rdb,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,16 +97,16 @@ func (cmd *Server) Run(ctx context.Context) error {
|
|||||||
reflection.Register(grpcServer)
|
reflection.Register(grpcServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenCtrl := &services.TokenController{
|
tokenCtrl := &controllers.TokenController{
|
||||||
DB: db,
|
DB: db,
|
||||||
Redis: rdb,
|
Redis: rdb,
|
||||||
}
|
}
|
||||||
|
|
||||||
accountCtrl := &services.AccountController{
|
accountCtrl := &controllers.AccountController{
|
||||||
HashCost: cmd.HashCost,
|
HashCost: cmd.HashCost,
|
||||||
DB: db,
|
DB: db,
|
||||||
DevMode: cmd.DevMode,
|
DevMode: cmd.DevMode,
|
||||||
RefreshTokenTTL: cmd.RefrestTokenTTL,
|
RefreshTokenTTL: cmd.RefreshTokenTTL,
|
||||||
AccessTokenTTL: cmd.AccessTokenTTL,
|
AccessTokenTTL: cmd.AccessTokenTTL,
|
||||||
JWTSecret: []byte(cmd.JWTSecret),
|
JWTSecret: []byte(cmd.JWTSecret),
|
||||||
Redis: rdb,
|
Redis: rdb,
|
||||||
@@ -120,38 +114,12 @@ func (cmd *Server) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
// Services that should be accessible for tokens should go here
|
// Services that should be accessible for tokens should go here
|
||||||
accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController))
|
accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController))
|
||||||
|
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
|
||||||
|
accounts.RegisterRefreshSessionServiceServer(grpcServer, v1.NewRefreshSessionServer(authController))
|
||||||
test.RegisterTestServiceServer(grpcServer, v1.NewTestServer())
|
test.RegisterTestServiceServer(grpcServer, v1.NewTestServer())
|
||||||
test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer())
|
test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer())
|
||||||
tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController))
|
tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController))
|
||||||
tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController))
|
tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController))
|
||||||
accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
|
|
||||||
|
|
||||||
healthcheck := health.NewServer()
|
|
||||||
healthgrpc.RegisterHealthServer(grpcServer, healthcheck)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(10 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
|
|
||||||
// Example checks
|
|
||||||
dbOK := checkDatabase(db)
|
|
||||||
redisOK := checkRedis(rdb)
|
|
||||||
|
|
||||||
status := healthpb.HealthCheckResponse_SERVING
|
|
||||||
|
|
||||||
if !dbOK || !redisOK {
|
|
||||||
status = healthpb.HealthCheckResponse_NOT_SERVING
|
|
||||||
}
|
|
||||||
|
|
||||||
healthcheck.SetServingStatus(
|
|
||||||
"",
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
info := grpcServer.GetServiceInfo()
|
info := grpcServer.GetServiceInfo()
|
||||||
tokenCtrl.SetGRPCInfo(info)
|
tokenCtrl.SetGRPCInfo(info)
|
||||||
@@ -177,7 +145,7 @@ func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if serviceName == "Health" {
|
if strings.HasPrefix(serviceName, "RefreshSession") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,33 +159,3 @@ func selectorRequireAuth(ctx context.Context, callMeta interceptors.CallMeta) bo
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDatabase(db *sql.DB) bool {
|
|
||||||
ctx, cancel := context.WithTimeout(
|
|
||||||
context.Background(),
|
|
||||||
2*time.Second,
|
|
||||||
)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Fast connectivity check
|
|
||||||
if err := db.PingContext(ctx); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkRedis(rdb *redis.Client) bool {
|
|
||||||
ctx, cancel := context.WithTimeout(
|
|
||||||
context.Background(),
|
|
||||||
2*time.Second,
|
|
||||||
)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := rdb.Ping(ctx).Err()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
|
||||||
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
|
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
|
||||||
github.com/jackc/pgx/v5 v5.5.4
|
github.com/jackc/pgx/v5 v5.5.4
|
||||||
@@ -43,8 +43,8 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260518175130-4b27db42e21e
|
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
golang.org/x/net v0.51.0 // indirect
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260518175130-4b27db42e21e h1:9pt3cvnJ3slg0lDjCwgVbvS/kI1JlKuNUFxdlYCGWF0=
|
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0 h1:CI6EwQndn8cr6ofpc1HbDsphCwK3NOZrdl2PS0BnX+Q=
|
||||||
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260518175130-4b27db42e21e/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw=
|
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@@ -15,6 +15,7 @@ github.com/alecthomas/kong v1.15.0 h1:BVJstKbpO73zKpmIu+m/aLRrNmWwxXPIGTNin9VmLV
|
|||||||
github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
||||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -56,7 +57,9 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
|||||||
github.com/geozelot/intree v1.0.0 h1:xUyiXMt0wD9zbPMOjy2rVShiUc3PGMPddPuTmi+Jy2s=
|
github.com/geozelot/intree v1.0.0 h1:xUyiXMt0wD9zbPMOjy2rVShiUc3PGMPddPuTmi+Jy2s=
|
||||||
github.com/geozelot/intree v1.0.0/go.mod h1:JrqfsNwe17AgzOM023tCXPyUB89NhaZAb8o5rzfZQ7Q=
|
github.com/geozelot/intree v1.0.0/go.mod h1:JrqfsNwe17AgzOM023tCXPyUB89NhaZAb8o5rzfZQ7Q=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
@@ -84,6 +87,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
@@ -104,8 +109,11 @@ github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBF
|
|||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
@@ -166,14 +174,18 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHS
|
|||||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@@ -185,6 +197,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
|
|||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -212,6 +225,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
@@ -224,6 +238,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
@@ -250,10 +265,13 @@ google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zN
|
|||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ spec:
|
|||||||
- server
|
- server
|
||||||
- --dev-mode
|
- --dev-mode
|
||||||
- --reflection
|
- --reflection
|
||||||
|
{{- with .Values.args }}
|
||||||
|
{{ . | toYaml | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- name: grpc
|
- name: grpc
|
||||||
containerPort: {{ .Values.service.port }}
|
containerPort: {{ .Values.service.port }}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ podAnnotations: {}
|
|||||||
podLabels: {}
|
podLabels: {}
|
||||||
podSecurityContext: {}
|
podSecurityContext: {}
|
||||||
securityContext: {}
|
securityContext: {}
|
||||||
|
|
||||||
|
args: []
|
||||||
# capabilities:
|
# capabilities:
|
||||||
# drop:
|
# drop:
|
||||||
# - ALL
|
# - ALL
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/v1"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewProjectsServer() *ProjectsServer {
|
|
||||||
return &ProjectsServer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// var _ projects.ProjectsServiceServer = (*ProjectsServer)(nil)
|
|
||||||
|
|
||||||
type ProjectsServer struct {
|
|
||||||
projects.UnimplementedProjectsServiceServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateProject implements [v1.ProjectsServiceServer].
|
|
||||||
func (p *ProjectsServer) CreateProject(context.Context, *projects.CreateProjectRequest) (*projects.CreateProjectResponse, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProject implements [v1.ProjectsServiceServer].
|
|
||||||
func (p *ProjectsServer) GetProject(context.Context, *projects.GetProjectRequest) (*projects.GetProjectResponse, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjects implements [v1.ProjectsServiceServer].
|
|
||||||
func (p *ProjectsServer) ListProjects(*projects.ListProjectsRequest, grpc.ServerStreamingServer[projects.ListProjectsResponse]) error {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateProject implements [v1.ProjectsServiceServer].
|
|
||||||
func (p *ProjectsServer) UpdateProject(context.Context, *projects.UpdateProjectRequest) (*projects.UpdateProjectResponse, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package services
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -41,8 +41,6 @@ type AccountData struct {
|
|||||||
Password string
|
Password string
|
||||||
Email string
|
Email string
|
||||||
UUID string
|
UUID string
|
||||||
Name string
|
|
||||||
Surname string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new account
|
// Create a new account
|
||||||
@@ -61,8 +59,6 @@ func (c *AccountController) Create(ctx context.Context, data *AccountData) (stri
|
|||||||
UUID: data.UUID,
|
UUID: data.UUID,
|
||||||
Email: data.Email,
|
Email: data.Email,
|
||||||
PasswordHash: passwordHash,
|
PasswordHash: passwordHash,
|
||||||
Name: data.Name,
|
|
||||||
Surname: data.Surname,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repository.CreateAccount(ctx, c.DB, queryData); err != nil {
|
if err := repository.CreateAccount(ctx, c.DB, queryData); err != nil {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package services_test
|
package controllers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/helpers/postgres"
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
_ "github.com/jackc/pgx/v5/stdlib"
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@@ -40,8 +40,8 @@ func newTestRedisConnection() *redis.Client {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestAccountController(ctx context.Context) *services.AccountController {
|
func newTestAccountController(ctx context.Context) *controllers.AccountController {
|
||||||
return &services.AccountController{
|
return &controllers.AccountController{
|
||||||
DB: newTestDBConnection(ctx),
|
DB: newTestDBConnection(ctx),
|
||||||
Redis: newTestRedisConnection(),
|
Redis: newTestRedisConnection(),
|
||||||
DevMode: true,
|
DevMode: true,
|
||||||
@@ -67,11 +67,9 @@ func newTestUniqueEmail(prefix string) string {
|
|||||||
|
|
||||||
func TestIntegrationAccountCreate_Success(t *testing.T) {
|
func TestIntegrationAccountCreate_Success(t *testing.T) {
|
||||||
ctrl := newTestAccountController(t.Context())
|
ctrl := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
Surname: "Doe",
|
|
||||||
Name: "John",
|
|
||||||
}
|
}
|
||||||
id, err := ctrl.Create(t.Context(), accountData)
|
id, err := ctrl.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -81,11 +79,9 @@ func TestIntegrationAccountCreate_Success(t *testing.T) {
|
|||||||
func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) {
|
func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) {
|
||||||
ctrl := newTestAccountController(t.Context())
|
ctrl := newTestAccountController(t.Context())
|
||||||
email := newTestUniqueEmail("accounts")
|
email := newTestUniqueEmail("accounts")
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: email,
|
Email: email,
|
||||||
Surname: "Doe",
|
|
||||||
Name: "John",
|
|
||||||
}
|
}
|
||||||
id, err := ctrl.Create(t.Context(), accountData)
|
id, err := ctrl.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -94,13 +90,13 @@ func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) {
|
|||||||
id, err = ctrl.Create(t.Context(), accountData)
|
id, err = ctrl.Create(t.Context(), accountData)
|
||||||
assert.Empty(t, id)
|
assert.Empty(t, id)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrEmailUsed)
|
assert.ErrorIs(t, err, controllers.ErrEmailUsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationAccountLogin_Success(t *testing.T) {
|
func TestIntegrationAccountLogin_Success(t *testing.T) {
|
||||||
ctrl := newTestAccountController(t.Context())
|
ctrl := newTestAccountController(t.Context())
|
||||||
email := newTestUniqueEmail("accounts")
|
email := newTestUniqueEmail("accounts")
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
@@ -117,7 +113,7 @@ func TestIntegrationAccountLogin_Success(t *testing.T) {
|
|||||||
func TestIntegrationAccountLogin_WrongPassword(t *testing.T) {
|
func TestIntegrationAccountLogin_WrongPassword(t *testing.T) {
|
||||||
ctrl := newTestAccountController(t.Context())
|
ctrl := newTestAccountController(t.Context())
|
||||||
email := newTestUniqueEmail("accounts")
|
email := newTestUniqueEmail("accounts")
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
@@ -129,13 +125,13 @@ func TestIntegrationAccountLogin_WrongPassword(t *testing.T) {
|
|||||||
id, err = ctrl.Login(t.Context(), accountData.Email, "Wrong Password")
|
id, err = ctrl.Login(t.Context(), accountData.Email, "Wrong Password")
|
||||||
assert.Empty(t, id)
|
assert.Empty(t, id)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrWrongPassword)
|
assert.ErrorIs(t, err, controllers.ErrWrongPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationAccountLogin_WrongEmail(t *testing.T) {
|
func TestIntegrationAccountLogin_WrongEmail(t *testing.T) {
|
||||||
ctrl := newTestAccountController(t.Context())
|
ctrl := newTestAccountController(t.Context())
|
||||||
email := newTestUniqueEmail("accounts")
|
email := newTestUniqueEmail("accounts")
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
@@ -147,5 +143,5 @@ func TestIntegrationAccountLogin_WrongEmail(t *testing.T) {
|
|||||||
id, err = ctrl.Login(t.Context(), "some@email.com", "Wrong Password")
|
id, err = ctrl.Login(t.Context(), "some@email.com", "Wrong Password")
|
||||||
assert.Empty(t, id)
|
assert.Empty(t, id)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrUserNotFound)
|
assert.ErrorIs(t, err, controllers.ErrUserNotFound)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package services
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package services_test
|
package controllers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -16,24 +16,24 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateInvalidTokenType(t *testing.T) {
|
func TestGenerateInvalidTokenType(t *testing.T) {
|
||||||
data := &services.JWTData{
|
data := &controllers.JWTData{
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
TokenType: "invalid_type",
|
TokenType: "invalid_type",
|
||||||
}
|
}
|
||||||
|
|
||||||
authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
||||||
|
|
||||||
token, _, err := authCtrl.GenerateToken(data)
|
token, _, err := authCtrl.GenerateToken(data)
|
||||||
assert.Equal(t, "", token)
|
assert.Equal(t, "", token)
|
||||||
assert.ErrorIs(t, services.ErrUnknownTokenType, err)
|
assert.ErrorIs(t, controllers.ErrUnknownTokenType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateValidateAccessToken(t *testing.T) {
|
func TestGenerateValidateAccessToken(t *testing.T) {
|
||||||
data := &services.JWTData{
|
data := &controllers.JWTData{
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
TokenType: services.TokenTypeAccess,
|
TokenType: controllers.TokenTypeAccess,
|
||||||
}
|
}
|
||||||
authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
token, _, err := authCtrl.GenerateToken(data)
|
token, _, err := authCtrl.GenerateToken(data)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -43,18 +43,18 @@ func TestGenerateValidateAccessToken(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testUserID, claims.UserID)
|
assert.Equal(t, testUserID, claims.UserID)
|
||||||
assert.NotEmpty(t, claims.TokenID)
|
assert.NotEmpty(t, claims.TokenID)
|
||||||
assert.Equal(t, services.TokenTypeAccess, claims.TokenType)
|
assert.Equal(t, controllers.TokenTypeAccess, claims.TokenType)
|
||||||
assert.Equal(t, now.Add(testAccessTTL).Unix(), claims.ExpiresAt.Unix())
|
assert.Equal(t, now.Add(testAccessTTL).Unix(), claims.ExpiresAt.Unix())
|
||||||
assert.Equal(t, now.Unix(), claims.IssuedAt.Unix())
|
assert.Equal(t, now.Unix(), claims.IssuedAt.Unix())
|
||||||
assert.Equal(t, now.Unix(), claims.NotBefore.Unix())
|
assert.Equal(t, now.Unix(), claims.NotBefore.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateValidateRefreshToken(t *testing.T) {
|
func TestGenerateValidateRefreshToken(t *testing.T) {
|
||||||
data := &services.JWTData{
|
data := &controllers.JWTData{
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
TokenType: services.TokenTypeRefresh,
|
TokenType: controllers.TokenTypeRefresh,
|
||||||
}
|
}
|
||||||
authCtrl := services.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
authCtrl := controllers.NewAuthController([]byte("test"), testAccessTTL, testRefreshTTL, nil)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
token, _, err := authCtrl.GenerateToken(data)
|
token, _, err := authCtrl.GenerateToken(data)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -64,7 +64,7 @@ func TestGenerateValidateRefreshToken(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testUserID, claims.UserID)
|
assert.Equal(t, testUserID, claims.UserID)
|
||||||
assert.NotEmpty(t, claims.TokenID)
|
assert.NotEmpty(t, claims.TokenID)
|
||||||
assert.Equal(t, services.TokenTypeRefresh, claims.TokenType)
|
assert.Equal(t, controllers.TokenTypeRefresh, claims.TokenType)
|
||||||
assert.Equal(t, now.Add(testRefreshTTL).Unix(), claims.ExpiresAt.Unix())
|
assert.Equal(t, now.Add(testRefreshTTL).Unix(), claims.ExpiresAt.Unix())
|
||||||
assert.Equal(t, now.Unix(), claims.IssuedAt.Unix())
|
assert.Equal(t, now.Unix(), claims.IssuedAt.Unix())
|
||||||
assert.Equal(t, now.Unix(), claims.NotBefore.Unix())
|
assert.Equal(t, now.Unix(), claims.NotBefore.Unix())
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package controllers for token management
|
// Package controllers for token management
|
||||||
// This a token controller, that implements the logic around tokens
|
// This a token controller, that implements the logic around tokens
|
||||||
package services
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package services_test
|
package controllers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -6,13 +6,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestTokensController(ctx context.Context) *services.TokenController {
|
func newTestTokensController(ctx context.Context) *controllers.TokenController {
|
||||||
return &services.TokenController{
|
return &controllers.TokenController{
|
||||||
DB: newTestDBConnection(ctx),
|
DB: newTestDBConnection(ctx),
|
||||||
Redis: newTestRedisConnection(),
|
Redis: newTestRedisConnection(),
|
||||||
}
|
}
|
||||||
@@ -21,14 +21,14 @@ func newTestTokensController(ctx context.Context) *services.TokenController {
|
|||||||
func TestIntegrationCreateToken_Success(t *testing.T) {
|
func TestIntegrationCreateToken_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
id, err := ctrlAccount.Create(t.Context(), accountData)
|
id, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: id,
|
UserID: id,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -45,7 +45,7 @@ func TestIntegrationCreateToken_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationCreateToken_UserNotExist(t *testing.T) {
|
func TestIntegrationCreateToken_UserNotExist(t *testing.T) {
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: uuid.NewString(),
|
UserID: uuid.NewString(),
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
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)
|
tokenVal, tokenID, err := ctrl.Create(t.Context(), tokenData)
|
||||||
|
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrUserNotFound)
|
assert.ErrorIs(t, err, controllers.ErrUserNotFound)
|
||||||
assert.Empty(t, tokenID)
|
assert.Empty(t, tokenID)
|
||||||
assert.Empty(t, tokenVal)
|
assert.Empty(t, tokenVal)
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func TestIntegrationCreateToken_UserNotExist(t *testing.T) {
|
|||||||
func TestIntegrationGetToken_Success(t *testing.T) {
|
func TestIntegrationGetToken_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func TestIntegrationGetToken_Success(t *testing.T) {
|
|||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -101,7 +101,7 @@ func TestIntegrationGetToken_NotExists(t *testing.T) {
|
|||||||
ctrl := newTestTokensController(t.Context())
|
ctrl := newTestTokensController(t.Context())
|
||||||
token, err := ctrl.Get(t.Context(), uuid.NewString(), uuid.NewString())
|
token, err := ctrl.Get(t.Context(), uuid.NewString(), uuid.NewString())
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrTokenNotFound)
|
assert.ErrorIs(t, err, controllers.ErrTokenNotFound)
|
||||||
assert.Empty(t, token)
|
assert.Empty(t, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +109,14 @@ func TestIntegrationGetToken_WrongRequest(t *testing.T) {
|
|||||||
ctrl := newTestTokensController(t.Context())
|
ctrl := newTestTokensController(t.Context())
|
||||||
token, err := ctrl.Get(t.Context(), "test", "test")
|
token, err := ctrl.Get(t.Context(), "test", "test")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.ErrorIs(t, err, services.ErrServerError)
|
assert.ErrorIs(t, err, controllers.ErrServerError)
|
||||||
assert.Empty(t, token)
|
assert.Empty(t, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationVerifyTokenOwner_Success(t *testing.T) {
|
func TestIntegrationVerifyTokenOwner_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ func TestIntegrationVerifyTokenOwner_Success(t *testing.T) {
|
|||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -142,12 +142,12 @@ func TestIntegrationVerifyTokenOwner_Success(t *testing.T) {
|
|||||||
func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) {
|
func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
|
|
||||||
secondAccountData := &services.AccountData{
|
secondAccountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) {
|
|||||||
secondUserID, err := ctrlAccount.Create(t.Context(), secondAccountData)
|
secondUserID, err := ctrlAccount.Create(t.Context(), secondAccountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -169,13 +169,13 @@ func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) {
|
|||||||
ctrl := newTestTokensController(t.Context())
|
ctrl := newTestTokensController(t.Context())
|
||||||
_, tokenID, err := ctrl.Create(t.Context(), tokenData)
|
_, tokenID, err := ctrl.Create(t.Context(), tokenData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), services.ErrUserTokenMismatch)
|
assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), controllers.ErrUserTokenMismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationForceExpiration_Success(t *testing.T) {
|
func TestIntegrationForceExpiration_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ func TestIntegrationForceExpiration_Success(t *testing.T) {
|
|||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -205,7 +205,7 @@ func TestIntegrationForceExpiration_Success(t *testing.T) {
|
|||||||
func TestIntegrationRegenerateToken_Success(t *testing.T) {
|
func TestIntegrationRegenerateToken_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func TestIntegrationRegenerateToken_Success(t *testing.T) {
|
|||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -255,7 +255,7 @@ func TestIntegrationRegenerateToken_Success(t *testing.T) {
|
|||||||
func TestIntegrationListTokens_Success(t *testing.T) {
|
func TestIntegrationListTokens_Success(t *testing.T) {
|
||||||
// Create a user for the token
|
// Create a user for the token
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ func TestIntegrationListTokens_Success(t *testing.T) {
|
|||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenDataOne := &services.TokenData{
|
tokenDataOne := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -271,7 +271,7 @@ func TestIntegrationListTokens_Success(t *testing.T) {
|
|||||||
"Test": {"test", "test2"},
|
"Test": {"test", "test2"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tokenDataTwo := &services.TokenData{
|
tokenDataTwo := &controllers.TokenData{
|
||||||
Name: "Test Token again",
|
Name: "Test Token again",
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -293,14 +293,14 @@ func TestIntegrationListTokens_Success(t *testing.T) {
|
|||||||
|
|
||||||
func TestIntegrationAuthenticateWithToken_Success(t *testing.T) {
|
func TestIntegrationAuthenticateWithToken_Success(t *testing.T) {
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
id, err := ctrlAccount.Create(t.Context(), accountData)
|
id, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: id,
|
UserID: id,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -330,19 +330,19 @@ func TestIntegrationAuthenticateWithToken_UnknownToken(t *testing.T) {
|
|||||||
auth, err := ctrl.AuthenticateWithToken(t.Context(), "dummy")
|
auth, err := ctrl.AuthenticateWithToken(t.Context(), "dummy")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, auth)
|
assert.Nil(t, auth)
|
||||||
assert.ErrorIs(t, err, services.ErrTokenNotFound)
|
assert.ErrorIs(t, err, controllers.ErrTokenNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) {
|
func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) {
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
id, err := ctrlAccount.Create(t.Context(), accountData)
|
id, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: id,
|
UserID: id,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -365,19 +365,19 @@ func TestIntegrationAuthenticateWithToken_Expired(t *testing.T) {
|
|||||||
auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal)
|
auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, auth)
|
assert.Nil(t, auth)
|
||||||
assert.ErrorIs(t, err, services.ErrBadToken)
|
assert.ErrorIs(t, err, controllers.ErrBadToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) {
|
func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) {
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
ctrlAccount := newTestAccountController(t.Context())
|
||||||
accountData := &services.AccountData{
|
accountData := &controllers.AccountData{
|
||||||
Password: "qwertyu9",
|
Password: "qwertyu9",
|
||||||
Email: newTestUniqueEmail("accounts"),
|
Email: newTestUniqueEmail("accounts"),
|
||||||
}
|
}
|
||||||
id, err := ctrlAccount.Create(t.Context(), accountData)
|
id, err := ctrlAccount.Create(t.Context(), accountData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tokenData := &services.TokenData{
|
tokenData := &controllers.TokenData{
|
||||||
Name: "Test Token",
|
Name: "Test Token",
|
||||||
UserID: id,
|
UserID: id,
|
||||||
ExpiresAt: time.Now().Add(time.Second * 5),
|
ExpiresAt: time.Now().Add(time.Second * 5),
|
||||||
@@ -399,5 +399,5 @@ func TestIntegrationAuthenticateWithToken_Revoked(t *testing.T) {
|
|||||||
auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal)
|
auth, err = ctrl.AuthenticateWithToken(t.Context(), tokenVal)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, auth)
|
assert.Nil(t, auth)
|
||||||
assert.ErrorIs(t, err, services.ErrBadToken)
|
assert.ErrorIs(t, err, controllers.ErrBadToken)
|
||||||
}
|
}
|
||||||
@@ -10,24 +10,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAlreadyExists = errors.New("entry already exists")
|
ErrAlreadyExists = errors.New("entry already exists")
|
||||||
ErrCheckNotPassed = errors.New("sql checks not passed")
|
ErrNotFound = errors.New("entry not found")
|
||||||
ErrNotFound = errors.New("entry not found")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountData struct {
|
type AccountData struct {
|
||||||
UUID string
|
UUID string
|
||||||
Email string
|
Email string
|
||||||
PasswordHash string
|
PasswordHash string
|
||||||
Name string
|
|
||||||
Surname string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAccount adds a new account to a database
|
// CreateAccount adds a new account to a database
|
||||||
func CreateAccount(ctx context.Context, db *sql.DB, account *AccountData) error {
|
func CreateAccount(ctx context.Context, db *sql.DB, account *AccountData) error {
|
||||||
query := "INSERT INTO accounts (uuid, email, password_hash, name, surname) VALUES ($1, $2, $3, $4, $5)"
|
query := "INSERT INTO accounts (uuid, email, password_hash) VALUES ($1, $2, $3)"
|
||||||
|
|
||||||
if _, err := db.ExecContext(ctx, query, account.UUID, account.Email, account.PasswordHash, account.Name, account.Surname); err != nil {
|
if _, err := db.ExecContext(ctx, query, account.UUID, account.Email, account.PasswordHash); err != nil {
|
||||||
var pgErr *pgconn.PgError
|
var pgErr *pgconn.PgError
|
||||||
if errors.As(err, &pgErr) {
|
if errors.As(err, &pgErr) {
|
||||||
if pgErr.Code == pgerrcode.UniqueViolation {
|
if pgErr.Code == pgerrcode.UniqueViolation {
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgerrcode"
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProjectData struct {
|
|
||||||
UUID string
|
|
||||||
Name string
|
|
||||||
Slug string
|
|
||||||
Description string
|
|
||||||
CreatedBy string
|
|
||||||
CreatedAt time.Time
|
|
||||||
ClosedAt sql.NullTime
|
|
||||||
Blocked bool
|
|
||||||
UpdatedAt time.Time
|
|
||||||
UpdatedBy string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateProject adds a new projects to the database
|
|
||||||
func CreateProject(ctx context.Context, db *sql.DB, data *ProjectData) error {
|
|
||||||
tx, err := StartTransaction(ctx, db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
queryProject := `
|
|
||||||
INSERT INTO projects
|
|
||||||
(uuid, name, slug, description, owner_user_id, billing_account_id, created_by, created_at, updated_by, updated_at)
|
|
||||||
VALUES
|
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
||||||
`
|
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, queryProject,
|
|
||||||
data.UUID, data.Name, data.Slug, data.Description, data.CreatedBy,
|
|
||||||
data.CreatedBy, data.CreatedBy, data.CreatedAt, data.CreatedBy, data.CreatedAt); err != nil {
|
|
||||||
var pgErr *pgconn.PgError
|
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
switch pgErr.Code {
|
|
||||||
case pgerrcode.UniqueViolation:
|
|
||||||
return ErrAlreadyExists
|
|
||||||
case pgerrcode.CheckViolation:
|
|
||||||
return ErrCheckNotPassed
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a project is created, we need to insert the default owner project membership
|
|
||||||
queryMembership := `
|
|
||||||
INSERT INTO project_membership
|
|
||||||
(project_uuid, user_uuid, role, status, invited_by, joined_at)
|
|
||||||
VALUES
|
|
||||||
($1, $2, $3, $4, $5, $6);
|
|
||||||
`
|
|
||||||
if _, err := tx.ExecContext(ctx, queryMembership,
|
|
||||||
data.UUID, data.CreatedBy, "owner", "active", data.CreatedBy, data.CreatedAt,
|
|
||||||
); err != nil {
|
|
||||||
var pgErr *pgconn.PgError
|
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
switch pgErr.Code {
|
|
||||||
case pgerrcode.UniqueViolation:
|
|
||||||
return ErrAlreadyExists
|
|
||||||
case pgerrcode.CheckViolation:
|
|
||||||
return ErrCheckNotPassed
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProjectByID returns a project from the database
|
|
||||||
func GetProjectByID(ctx context.Context, db *sql.DB, projectID string) (*ProjectData, error) {
|
|
||||||
data := &ProjectData{}
|
|
||||||
query := `
|
|
||||||
SELECT uuid, name, slug, description, owner_user_id, closed_at, created_at
|
|
||||||
FROM projects
|
|
||||||
WHERE uuid=$1`
|
|
||||||
|
|
||||||
if err := db.QueryRowContext(ctx, query, projectID).Scan(
|
|
||||||
&data.UUID,
|
|
||||||
&data.Name,
|
|
||||||
&data.Slug,
|
|
||||||
&data.Description,
|
|
||||||
&data.CreatedBy,
|
|
||||||
&data.ClosedAt,
|
|
||||||
&data.CreatedAt,
|
|
||||||
); err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateProject change editable project data
|
|
||||||
func UpdateProject(ctx context.Context, db *sql.DB, data *ProjectData) error {
|
|
||||||
query := `
|
|
||||||
UPDATE projects
|
|
||||||
SET
|
|
||||||
name = $2,
|
|
||||||
description = $3,
|
|
||||||
updated_at = $4,
|
|
||||||
updated_by = $5
|
|
||||||
WHERE uuid = $1;`
|
|
||||||
if _, err := db.Query(query, data.UUID, data.Name, data.Description, data.UpdatedAt, data.UpdatedBy); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListProjects get all projects that are available for the user from the database
|
|
||||||
func ListProjects(ctx context.Context, db *sql.DB, userID string) ([]*ProjectData, error) {
|
|
||||||
query := `
|
|
||||||
SELECT p.uuid, p.name
|
|
||||||
FROM projects p
|
|
||||||
JOIN project_membership pm ON pm.project_uuid = p.uuid
|
|
||||||
WHERE pm.user_uuid = $1`
|
|
||||||
rows, err := db.QueryContext(ctx, query, userID)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := []*ProjectData{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
pd := &ProjectData{}
|
|
||||||
err := rows.Scan(&pd.UUID, &pd.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result = append(result, pd)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProjectOwner should return an owner if a project
|
|
||||||
func GetProjectOwner(ctx context.Context, db *sql.DB, projectID string) (userID string, err error) {
|
|
||||||
query := `
|
|
||||||
SELECT user_uuid
|
|
||||||
FROM project_membership
|
|
||||||
WHERE project_uuid = $1 AND role = 'owner'`
|
|
||||||
|
|
||||||
if err := db.QueryRowContext(ctx, query, projectID).Scan(
|
|
||||||
&userID,
|
|
||||||
); err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return "", ErrNotFound
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
package repository_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestUniqueEmail(prefix string) string {
|
|
||||||
if prefix == "" {
|
|
||||||
prefix = "test"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s-%d-%s@example.com",
|
|
||||||
prefix,
|
|
||||||
time.Now().UnixMilli(),
|
|
||||||
uuid.NewString(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func accountForProject(ctx context.Context) (string, error) {
|
|
||||||
account := &repository.AccountData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Email: newTestUniqueEmail("projects"),
|
|
||||||
PasswordHash: "dummy",
|
|
||||||
Name: "John",
|
|
||||||
Surname: "Doe",
|
|
||||||
}
|
|
||||||
if err := repository.CreateAccount(ctx, newTestDBConnection(ctx), account); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return account.UUID, nil
|
|
||||||
}
|
|
||||||
func TestIntegrationProjectsCreate_Success(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-1",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreate_CheckFailed(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-2",
|
|
||||||
Slug: "test_2",
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
assert.Error(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project), repository.ErrCheckNotPassed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreate_AlreadyExistsFail(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-3",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
assert.Error(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project), repository.ErrAlreadyExists)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreateAndGet_Success(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-4",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, project.Name, data.Name)
|
|
||||||
assert.Equal(t, project.Slug, data.Slug)
|
|
||||||
assert.Equal(t, project.Description, data.Description)
|
|
||||||
assert.Equal(t, project.CreatedBy, data.CreatedBy)
|
|
||||||
assert.Equal(t, project.ClosedAt.Time.Truncate(time.Second), data.ClosedAt.Time.Truncate(time.Second))
|
|
||||||
assert.Equal(t, project.CreatedAt.Truncate(time.Second), data.CreatedAt.Truncate(time.Second))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreateAndGet_NotFound(t *testing.T) {
|
|
||||||
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), uuid.NewString())
|
|
||||||
assert.ErrorIs(t, err, repository.ErrNotFound)
|
|
||||||
assert.Nil(t, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreateUpdateAndGet_Success(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-5",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, project.Name, data.Name)
|
|
||||||
assert.Equal(t, project.Slug, data.Slug)
|
|
||||||
assert.Equal(t, project.Description, data.Description)
|
|
||||||
assert.Equal(t, project.CreatedBy, data.CreatedBy)
|
|
||||||
assert.Equal(t, project.ClosedAt.Time.Truncate(time.Second), data.ClosedAt.Time.Truncate(time.Second))
|
|
||||||
assert.Equal(t, project.CreatedAt.Truncate(time.Second), data.CreatedAt.Truncate(time.Second))
|
|
||||||
|
|
||||||
project.UpdatedBy = uuid.NewString()
|
|
||||||
project.UpdatedAt = time.Now()
|
|
||||||
project.Description = "Updated description"
|
|
||||||
project.Name = "update name"
|
|
||||||
|
|
||||||
assert.NoError(t, repository.UpdateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
data, err = repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, project.Name, data.Name)
|
|
||||||
assert.Equal(t, project.Slug, data.Slug)
|
|
||||||
assert.Equal(t, project.Description, data.Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationProjectsCreateAndGetOwner_Success(t *testing.T) {
|
|
||||||
createdBy, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "test-6",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
|
|
||||||
userID, err := repository.GetProjectOwner(t.Context(), newTestDBConnection(t.Context()), project.UUID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, project.CreatedBy, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationListProjectsWorkflow(t *testing.T) {
|
|
||||||
createdBy1, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project1 := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "List 1",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy1,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy1,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project1))
|
|
||||||
|
|
||||||
project2 := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "List 2",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy1,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy1,
|
|
||||||
}
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project2))
|
|
||||||
|
|
||||||
createdBy2, err := accountForProject(t.Context())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
project3 := &repository.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "List 3",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test Project Number 1",
|
|
||||||
CreatedBy: createdBy2,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ClosedAt: sql.NullTime{Time: time.Now()},
|
|
||||||
Blocked: false,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: createdBy2,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project3))
|
|
||||||
|
|
||||||
projects, err := repository.ListProjects(t.Context(), newTestDBConnection(t.Context()), createdBy1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, projects, 2)
|
|
||||||
|
|
||||||
projects, err = repository.ListProjects(t.Context(), newTestDBConnection(t.Context()), createdBy2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, projects, 1)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package repository_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/postgres"
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestDBConnection(ctx context.Context) *sql.DB {
|
|
||||||
connStr, ok := os.LookupEnv("SOFTPLAYER_DB_CONNECTION_STRING")
|
|
||||||
if !ok {
|
|
||||||
// Default connection string
|
|
||||||
connStr = "postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable"
|
|
||||||
}
|
|
||||||
db, err := postgres.Open(ctx, connStr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransactionFlow_Success(t *testing.T) {
|
|
||||||
db := newTestDBConnection(t.Context())
|
|
||||||
tx, err := repository.StartTransaction(t.Context(), db)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
query1 := "CREATE TABLE transaction_test_success (id SERIAL PRIMARY KEY, data VARCHAR (50) NOT NULL)"
|
|
||||||
_, err = db.Exec(query1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
data1 := "data1"
|
|
||||||
data2 := "data2"
|
|
||||||
query2 := "INSERT INTO transaction_test_success (data) VALUES ($1)"
|
|
||||||
_, err = tx.Exec(query2, data1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_, err = tx.Exec(query2, data2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NoError(t, repository.CommitTransaction(t.Context(), tx))
|
|
||||||
|
|
||||||
exists := false
|
|
||||||
queryTest := `SELECT EXISTS(
|
|
||||||
SELECT 1 FROM transaction_test_success WHERE data = $1
|
|
||||||
)`
|
|
||||||
|
|
||||||
err = db.QueryRow(queryTest, data1).Scan(&exists)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, exists)
|
|
||||||
|
|
||||||
err = db.QueryRow(queryTest, data2).Scan(&exists)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, exists)
|
|
||||||
|
|
||||||
queryCleanup := "DROP TABLE transaction_test_success"
|
|
||||||
_, err = db.Exec(queryCleanup)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransactionFlow_Rollback(t *testing.T) {
|
|
||||||
db := newTestDBConnection(t.Context())
|
|
||||||
tx, err := repository.StartTransaction(t.Context(), db)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
query1 := "CREATE TABLE transaction_test_rollback (id SERIAL PRIMARY KEY, data VARCHAR (50) NOT NULL)"
|
|
||||||
_, err = db.Exec(query1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
data1 := "data1"
|
|
||||||
data2 := "data2"
|
|
||||||
query2 := "INSERT INTO transaction_test_rollback (data) VALUES ($1)"
|
|
||||||
_, err = tx.Exec(query2, data1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_, err = tx.Exec(query2, data2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NoError(t, repository.RollBackTransaction(t.Context(), tx))
|
|
||||||
|
|
||||||
exists := false
|
|
||||||
queryTest := `SELECT EXISTS(
|
|
||||||
SELECT 1 FROM transaction_test_rollback WHERE data = $1
|
|
||||||
)`
|
|
||||||
|
|
||||||
err = db.QueryRow(queryTest, data1).Scan(&exists)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, exists)
|
|
||||||
|
|
||||||
err = db.QueryRow(queryTest, data2).Scan(&exists)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, exists)
|
|
||||||
|
|
||||||
queryCleanup := "DROP TABLE transaction_test_rollback"
|
|
||||||
_, err = db.Exec(queryCleanup)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger"
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProjectsController struct {
|
|
||||||
DB *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProjectData struct {
|
|
||||||
UUID string
|
|
||||||
Name string
|
|
||||||
Slug string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrProjectExists = errors.New("project exists")
|
|
||||||
ErrInvalidProject = errors.New("invalid project data")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a new project
|
|
||||||
func (ctrl *ProjectsController) Create(ctx context.Context, data *ProjectData, userID string) (id string, err error) {
|
|
||||||
id = uuid.NewString()
|
|
||||||
log := logger.FromContext(ctx).WithValues("id", id)
|
|
||||||
log.V(2).Info("Creating a project")
|
|
||||||
queryData := &repository.ProjectData{
|
|
||||||
UUID: id,
|
|
||||||
Name: data.Name,
|
|
||||||
Slug: data.Slug,
|
|
||||||
Description: data.Description,
|
|
||||||
CreatedBy: userID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
UpdatedBy: userID,
|
|
||||||
}
|
|
||||||
err = repository.CreateProject(ctx, ctrl.DB, queryData)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, repository.ErrAlreadyExists) {
|
|
||||||
return "", ErrProjectExists
|
|
||||||
} else if errors.Is(err, repository.ErrCheckNotPassed) {
|
|
||||||
return "", ErrInvalidProject
|
|
||||||
}
|
|
||||||
log.Error(err, "Couldn't create a project")
|
|
||||||
return "", ErrServerError
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update an existing project
|
|
||||||
func (ctrl *ProjectsController) Update(ctx context.Context, data *ProjectData) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an existing project by ID
|
|
||||||
func (ctrl *ProjectsController) Get(ctx context.Context, projectID string) (data *ProjectData, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List projects available for a user
|
|
||||||
func (ctrl *ProjectsController) List(ctx context.Context) (data []*ProjectData, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package services_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestProjectsController(ctx context.Context) *services.ProjectsController {
|
|
||||||
return &services.ProjectsController{
|
|
||||||
DB: newTestDBConnection(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationCreateProject_Success(t *testing.T) {
|
|
||||||
ctrlAccount := newTestAccountController(t.Context())
|
|
||||||
accountData := &services.AccountData{
|
|
||||||
Password: "qwertyu9",
|
|
||||||
Email: newTestUniqueEmail("projects"),
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, err := ctrlAccount.Create(t.Context(), accountData)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
ctrlProjects := newTestProjectsController(t.Context())
|
|
||||||
projectData := &services.ProjectData{
|
|
||||||
UUID: uuid.NewString(),
|
|
||||||
Name: "Test project",
|
|
||||||
Slug: uuid.NewString(),
|
|
||||||
Description: "Test project",
|
|
||||||
}
|
|
||||||
projectID, err := ctrlProjects.Create(t.Context(), projectData, userID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, projectID)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE accounts
|
|
||||||
DROP COLUMN name,
|
|
||||||
DROP COLUMN surname;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
-- Up migration (safe for existing data)
|
|
||||||
ALTER TABLE accounts
|
|
||||||
ADD COLUMN name TEXT,
|
|
||||||
ADD COLUMN surname TEXT;
|
|
||||||
|
|
||||||
UPDATE accounts
|
|
||||||
SET
|
|
||||||
name = 'John',
|
|
||||||
surname = 'Doe'
|
|
||||||
WHERE name IS NULL OR surname IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE accounts
|
|
||||||
ALTER COLUMN name SET NOT NULL,
|
|
||||||
ALTER COLUMN surname SET NOT NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE projects;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
CREATE TABLE projects (
|
|
||||||
uuid UUID PRIMARY KEY,
|
|
||||||
name VARCHAR(120) NOT NULL,
|
|
||||||
slug VARCHAR(120) NOT NULL UNIQUE
|
|
||||||
CHECK (
|
|
||||||
slug ~ '^[a-z0-9]+(?:-[a-z0-9]+)*$'
|
|
||||||
),
|
|
||||||
description TEXT,
|
|
||||||
owner_user_id UUID NOT NULL,
|
|
||||||
closed_at TIMESTAMPTZ NULL,
|
|
||||||
billing_account_id UUID NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
||||||
created_by UUID NOT NULL,
|
|
||||||
updated_by UUID NOT NULL
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
DROP INDEX idx_project_membership_project;
|
|
||||||
DROP INDEX idx_project_membership_user;
|
|
||||||
|
|
||||||
DROP TABLE project_membership;
|
|
||||||
|
|
||||||
DROP TYPE membership_status CASCADE;
|
|
||||||
DROP TYPE project_role CASCADE;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
CREATE TYPE project_role AS ENUM (
|
|
||||||
'member',
|
|
||||||
'admin',
|
|
||||||
'owner'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TYPE membership_status AS ENUM (
|
|
||||||
'invited',
|
|
||||||
'active',
|
|
||||||
'suspended'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE project_membership (
|
|
||||||
project_uuid UUID NOT NULL
|
|
||||||
REFERENCES projects(uuid)
|
|
||||||
ON DELETE CASCADE,
|
|
||||||
|
|
||||||
user_uuid UUID NOT NULL
|
|
||||||
REFERENCES accounts(uuid)
|
|
||||||
ON DELETE CASCADE,
|
|
||||||
|
|
||||||
role project_role NOT NULL,
|
|
||||||
status membership_status NOT NULL DEFAULT 'active',
|
|
||||||
|
|
||||||
invited_by UUID NULL
|
|
||||||
REFERENCES accounts(uuid),
|
|
||||||
joined_at TIMESTAMP NULL,
|
|
||||||
PRIMARY KEY (project_uuid, user_uuid)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_project_membership_user
|
|
||||||
ON project_membership(user_uuid);
|
|
||||||
|
|
||||||
CREATE INDEX idx_project_membership_project
|
|
||||||
ON project_membership(project_uuid);
|
|
||||||
Reference in New Issue
Block a user