From 3ea6765486cbb73ba318dfe80f30d8a7dea34431 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Tue, 9 Jun 2026 14:52:06 +0200 Subject: [PATCH] Implement a couple of rpcs Signed-off-by: Nikolai Rodionov --- cmd/server.go | 12 ++++--- go.mod | 4 +-- go.sum | 4 +-- internal/api/v1/projects.go | 52 ++++++++++++++++++++++++++---- internal/api/v1/refresh_session.go | 24 +++++++------- internal/repository/projects.go | 3 +- internal/services/projects.go | 27 +++++++++++++--- 7 files changed, 95 insertions(+), 31 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 9df1e62..7dc2893 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -13,6 +13,7 @@ import ( "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" + projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/v1" test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" tokens "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/tokens/v1" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" @@ -24,7 +25,6 @@ import ( "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" ) @@ -118,6 +118,10 @@ func (cmd *Server) Run(ctx context.Context) error { Redis: rdb, } + projectsCtrl := &services.ProjectsController{ + DB: db, + } + // Services that should be accessible for tokens should go here accounts.RegisterAccountsServiceServer(grpcServer, v1.NewAccountServer(accountCtrl, authController)) accounts.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController)) @@ -126,7 +130,7 @@ func (cmd *Server) Run(ctx context.Context) error { test.RegisterPublicTestServiceServer(grpcServer, v1.NewPublicTestServer()) tokens.RegisterTokensServiceServer(grpcServer, v1.NewTokensServer(tokenCtrl, authController)) tokens.RegisterPublicTokensServiceServer(grpcServer, v1.NewPublicTokensServer(tokenCtrl, authController)) - + projects.RegisterProjectsServiceServer(grpcServer, v1.NewProjectsServer(projectsCtrl)) healthcheck := health.NewServer() healthgrpc.RegisterHealthServer(grpcServer, healthcheck) @@ -140,10 +144,10 @@ func (cmd *Server) Run(ctx context.Context) error { dbOK := checkDatabase(db) redisOK := checkRedis(rdb) - status := healthpb.HealthCheckResponse_SERVING + status := healthgrpc.HealthCheckResponse_SERVING if !dbOK || !redisOK { - status = healthpb.HealthCheckResponse_NOT_SERVING + status = healthgrpc.HealthCheckResponse_NOT_SERVING } healthcheck.SetServingStatus( diff --git a/go.mod b/go.mod index 5fb9f95..e44ad03 100644 --- a/go.mod +++ b/go.mod @@ -43,8 +43,8 @@ require ( ) require ( - gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0 - github.com/golang/protobuf v1.5.4 // indirect + gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260609122010-15a8c9529644 + github.com/golang/protobuf v1.5.4 golang.org/x/net v0.51.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.34.0 // indirect diff --git a/go.sum b/go.sum index 1b58645..fbc3fbd 100644 --- a/go.sum +++ b/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/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -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-20260528090010-7bf4ddafe7f0/go.mod h1:EcQEZ3NN06b3UmKxiRnQnXDDjQ9kmJgoQQBAS+fpRQw= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260609122010-15a8c9529644 h1:PSLGU8NVSfAi7EZwIbrLZO0AYrx5N8aMAAW3B7kkhRc= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260609122010-15a8c9529644/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/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/internal/api/v1/projects.go b/internal/api/v1/projects.go index 58c6a3d..97257df 100644 --- a/internal/api/v1/projects.go +++ b/internal/api/v1/projects.go @@ -3,23 +3,44 @@ package v1 import ( "context" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/v1" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) -func NewProjectsServer() *ProjectsServer { - return &ProjectsServer{} +func NewProjectsServer(projectCtrl *services.ProjectsController) *ProjectsServer { + return &ProjectsServer{ + projectCtrl: projectCtrl, + } } // var _ projects.ProjectsServiceServer = (*ProjectsServer)(nil) type ProjectsServer struct { projects.UnimplementedProjectsServiceServer + projectCtrl *services.ProjectsController } // CreateProject implements [v1.ProjectsServiceServer]. -func (p *ProjectsServer) CreateProject(context.Context, *projects.CreateProjectRequest) (*projects.CreateProjectResponse, error) { - panic("unimplemented") +func (p *ProjectsServer) CreateProject(ctx context.Context, in *projects.CreateProjectRequest) (*projects.CreateProjectResponse, error) { + claims, err := services.ClaimsFromContext(ctx) + if err != nil { + return nil, status.Error(codes.Aborted, "Context is invalid") + } + + projectData := &services.ProjectData{ + Name: in.Name, + Slug: in.Slug, + Description: in.Description, + } + + id, err := p.projectCtrl.Create(ctx, projectData, claims.UserID) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't create a project") + } + return &projects.CreateProjectResponse{Id: id}, nil } // GetProject implements [v1.ProjectsServiceServer]. @@ -28,8 +49,27 @@ func (p *ProjectsServer) GetProject(context.Context, *projects.GetProjectRequest } // ListProjects implements [v1.ProjectsServiceServer]. -func (p *ProjectsServer) ListProjects(*projects.ListProjectsRequest, grpc.ServerStreamingServer[projects.ListProjectsResponse]) error { - panic("unimplemented") +func (p *ProjectsServer) ListProjects(_in *projects.ListProjectsRequest, stream grpc.ServerStreamingServer[projects.ListProjectsResponse]) error { + claims, err := services.ClaimsFromContext(stream.Context()) + if err != nil { + return status.Error(codes.Aborted, "Context is invalid") + } + + res, err := p.projectCtrl.List(stream.Context(), claims.UserID) + if err != nil { + return status.Error(codes.Aborted, "Couldn't list projects") + } + for _, project := range res { + payload := &projects.ListProjectsResponse{ + Id: project.UUID, + Slug: "dummy", + Name: project.Name, + } + if err := stream.Send(payload); err != nil { + return status.Error(codes.Aborted, "Couldn't send data") + } + } + return nil } // UpdateProject implements [v1.ProjectsServiceServer]. diff --git a/internal/api/v1/refresh_session.go b/internal/api/v1/refresh_session.go index c0b53b8..294b366 100644 --- a/internal/api/v1/refresh_session.go +++ b/internal/api/v1/refresh_session.go @@ -5,14 +5,14 @@ import ( "errors" "fmt" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func NewRefreshSessionServer( - authorizationCtrl *controllers.AuthController, + authorizationCtrl *services.AuthController, ) *RefreshSessionService { return &RefreshSessionService{ authorizationCtrl: authorizationCtrl, @@ -21,7 +21,7 @@ func NewRefreshSessionServer( type RefreshSessionService struct { accounts.UnimplementedRefreshSessionServiceServer - authorizationCtrl *controllers.AuthController + authorizationCtrl *services.AuthController } func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accounts.RefreshSessionRequest) (*accounts.RefreshSessionResponse, error) { @@ -32,13 +32,13 @@ func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accoun return nil, status.Error(codes.Aborted, "Invalid token is sent") } - if claims.TokenType != controllers.TokenTypeRefresh { + 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, controllers.ErrSessionNotFound) { + 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") @@ -48,24 +48,24 @@ func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accoun return nil, status.Error(codes.Unauthenticated, "Invalid session") } - accessToken, _, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{ + accessToken, _, err := srv.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: claims.UserID, - TokenType: controllers.TokenTypeAccess, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeAccess, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{ + refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&services.JWTData{ UserID: claims.UserID, - TokenType: controllers.TokenTypeRefresh, - TokenAud: controllers.TokenAudWeb, + TokenType: services.TokenTypeRefresh, + TokenAud: services.TokenAudWeb, }) if err != nil { return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - newSession := &controllers.Session{UserID: session.UserID} + 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") diff --git a/internal/repository/projects.go b/internal/repository/projects.go index fa8530e..eec4bb9 100644 --- a/internal/repository/projects.go +++ b/internal/repository/projects.go @@ -60,7 +60,8 @@ func CreateProject(ctx context.Context, db *sql.DB, data *ProjectData) error { VALUES ($1, $2, $3, $4, $5, $6); ` - if _, err := tx.ExecContext(ctx, queryMembership, + if _, err := tx.ExecContext( + ctx, queryMembership, data.UUID, data.CreatedBy, "owner", "active", data.CreatedBy, data.CreatedAt, ); err != nil { var pgErr *pgconn.PgError diff --git a/internal/services/projects.go b/internal/services/projects.go index 48eca48..67e4704 100644 --- a/internal/services/projects.go +++ b/internal/services/projects.go @@ -23,8 +23,9 @@ type ProjectData struct { } var ( - ErrProjectExists = errors.New("project exists") - ErrInvalidProject = errors.New("invalid project data") + ErrProjectExists = errors.New("project exists") + ErrInvalidProject = errors.New("invalid project data") + ErrProjectNotFound = errors.New("project not found") ) // Create a new project @@ -66,6 +67,24 @@ func (ctrl *ProjectsController) Get(ctx context.Context, projectID string) (data } // List projects available for a user -func (ctrl *ProjectsController) List(ctx context.Context) (data []*ProjectData, err error) { - return nil, nil +func (ctrl *ProjectsController) List(ctx context.Context, userID string) (data []*ProjectData, err error) { + log := logger.FromContext(ctx) + log.V(2).Info("Listing projects") + data = []*ProjectData{} + res, err := repository.ListProjects(ctx, ctrl.DB, userID) + if err != nil { + if errors.Is(err, repository.ErrNotFound) { + return nil, ErrProjectNotFound + } + log.Error(err, "Couldn't list projects") + return nil, ErrServerError + } + for _, val := range res { + data = append(data, &ProjectData{ + UUID: val.UUID, + Name: val.Name, + }) + } + + return }