Implement a couple of rpcs
All checks were successful
ci/woodpecker/push/build Pipeline was successful

Signed-off-by: Nikolai Rodionov <iam@allanger.xyz>
This commit is contained in:
2026-06-09 14:52:06 +02:00
parent 7c55e758fc
commit 3ea6765486
7 changed files with 95 additions and 31 deletions

View File

@@ -13,6 +13,7 @@ import (
"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" "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"
projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/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"
grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
@@ -24,7 +25,6 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/health" "google.golang.org/grpc/health"
healthgrpc "google.golang.org/grpc/health/grpc_health_v1" 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"
) )
@@ -118,6 +118,10 @@ func (cmd *Server) Run(ctx context.Context) error {
Redis: rdb, Redis: rdb,
} }
projectsCtrl := &services.ProjectsController{
DB: db,
}
// 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.RegisterPublicAccountsServiceServer(grpcServer, v1.NewPublicAccountServer(accountCtrl, authController))
@@ -126,7 +130,7 @@ func (cmd *Server) Run(ctx context.Context) error {
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))
projects.RegisterProjectsServiceServer(grpcServer, v1.NewProjectsServer(projectsCtrl))
healthcheck := health.NewServer() healthcheck := health.NewServer()
healthgrpc.RegisterHealthServer(grpcServer, healthcheck) healthgrpc.RegisterHealthServer(grpcServer, healthcheck)
@@ -140,10 +144,10 @@ func (cmd *Server) Run(ctx context.Context) error {
dbOK := checkDatabase(db) dbOK := checkDatabase(db)
redisOK := checkRedis(rdb) redisOK := checkRedis(rdb)
status := healthpb.HealthCheckResponse_SERVING status := healthgrpc.HealthCheckResponse_SERVING
if !dbOK || !redisOK { if !dbOK || !redisOK {
status = healthpb.HealthCheckResponse_NOT_SERVING status = healthgrpc.HealthCheckResponse_NOT_SERVING
} }
healthcheck.SetServingStatus( healthcheck.SetServingStatus(

4
go.mod
View File

@@ -43,8 +43,8 @@ require (
) )
require ( require (
gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260528090010-7bf4ddafe7f0 gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260609122010-15a8c9529644
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4
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

4
go.sum
View File

@@ -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-20260528090010-7bf4ddafe7f0 h1:CI6EwQndn8cr6ofpc1HbDsphCwK3NOZrdl2PS0BnX+Q= gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260609122010-15a8c9529644 h1:PSLGU8NVSfAi7EZwIbrLZO0AYrx5N8aMAAW3B7kkhRc=
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/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=

View File

@@ -3,23 +3,44 @@ package v1
import ( import (
"context" "context"
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/services"
projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/v1" projects "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/projects/v1"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
func NewProjectsServer() *ProjectsServer { func NewProjectsServer(projectCtrl *services.ProjectsController) *ProjectsServer {
return &ProjectsServer{} return &ProjectsServer{
projectCtrl: projectCtrl,
}
} }
// var _ projects.ProjectsServiceServer = (*ProjectsServer)(nil) // var _ projects.ProjectsServiceServer = (*ProjectsServer)(nil)
type ProjectsServer struct { type ProjectsServer struct {
projects.UnimplementedProjectsServiceServer projects.UnimplementedProjectsServiceServer
projectCtrl *services.ProjectsController
} }
// CreateProject implements [v1.ProjectsServiceServer]. // CreateProject implements [v1.ProjectsServiceServer].
func (p *ProjectsServer) CreateProject(context.Context, *projects.CreateProjectRequest) (*projects.CreateProjectResponse, error) { func (p *ProjectsServer) CreateProject(ctx context.Context, in *projects.CreateProjectRequest) (*projects.CreateProjectResponse, error) {
panic("unimplemented") 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]. // GetProject implements [v1.ProjectsServiceServer].
@@ -28,8 +49,27 @@ func (p *ProjectsServer) GetProject(context.Context, *projects.GetProjectRequest
} }
// ListProjects implements [v1.ProjectsServiceServer]. // ListProjects implements [v1.ProjectsServiceServer].
func (p *ProjectsServer) ListProjects(*projects.ListProjectsRequest, grpc.ServerStreamingServer[projects.ListProjectsResponse]) error { func (p *ProjectsServer) ListProjects(_in *projects.ListProjectsRequest, stream grpc.ServerStreamingServer[projects.ListProjectsResponse]) error {
panic("unimplemented") 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]. // UpdateProject implements [v1.ProjectsServiceServer].

View File

@@ -5,14 +5,14 @@ import (
"errors" "errors"
"fmt" "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" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
func NewRefreshSessionServer( func NewRefreshSessionServer(
authorizationCtrl *controllers.AuthController, authorizationCtrl *services.AuthController,
) *RefreshSessionService { ) *RefreshSessionService {
return &RefreshSessionService{ return &RefreshSessionService{
authorizationCtrl: authorizationCtrl, authorizationCtrl: authorizationCtrl,
@@ -21,7 +21,7 @@ func NewRefreshSessionServer(
type RefreshSessionService struct { type RefreshSessionService struct {
accounts.UnimplementedRefreshSessionServiceServer accounts.UnimplementedRefreshSessionServiceServer
authorizationCtrl *controllers.AuthController authorizationCtrl *services.AuthController
} }
func (srv *RefreshSessionService) RefreshSession(ctx context.Context, in *accounts.RefreshSessionRequest) (*accounts.RefreshSessionResponse, error) { 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") 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") return nil, status.Error(codes.Unauthenticated, "Invalid token")
} }
session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID) session, err := srv.authorizationCtrl.GetSession(ctx, claims.TokenID)
if err != nil { 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.Unauthenticated, "Session doesn't exists")
} }
return nil, status.Error(codes.Internal, "Somethings is broken on our side") 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") 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, UserID: claims.UserID,
TokenType: controllers.TokenTypeAccess, TokenType: services.TokenTypeAccess,
TokenAud: controllers.TokenAudWeb, TokenAud: services.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 := srv.authorizationCtrl.GenerateToken(&controllers.JWTData{ refreshToken, tokenID, err := srv.authorizationCtrl.GenerateToken(&services.JWTData{
UserID: claims.UserID, UserID: claims.UserID,
TokenType: controllers.TokenTypeRefresh, TokenType: services.TokenTypeRefresh,
TokenAud: controllers.TokenAudWeb, TokenAud: services.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")
} }
newSession := &controllers.Session{UserID: session.UserID} newSession := &services.Session{UserID: session.UserID}
if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil { if err := srv.authorizationCtrl.SaveSession(ctx, tokenID, newSession); err != nil {
return nil, status.Error(codes.Aborted, "Couldn't store session") return nil, status.Error(codes.Aborted, "Couldn't store session")

View File

@@ -60,7 +60,8 @@ func CreateProject(ctx context.Context, db *sql.DB, data *ProjectData) error {
VALUES VALUES
($1, $2, $3, $4, $5, $6); ($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, data.UUID, data.CreatedBy, "owner", "active", data.CreatedBy, data.CreatedAt,
); err != nil { ); err != nil {
var pgErr *pgconn.PgError var pgErr *pgconn.PgError

View File

@@ -23,8 +23,9 @@ type ProjectData struct {
} }
var ( var (
ErrProjectExists = errors.New("project exists") ErrProjectExists = errors.New("project exists")
ErrInvalidProject = errors.New("invalid project data") ErrInvalidProject = errors.New("invalid project data")
ErrProjectNotFound = errors.New("project not found")
) )
// Create a new project // Create a new project
@@ -66,6 +67,24 @@ func (ctrl *ProjectsController) Get(ctx context.Context, projectID string) (data
} }
// List projects available for a user // List projects available for a user
func (ctrl *ProjectsController) List(ctx context.Context) (data []*ProjectData, err error) { func (ctrl *ProjectsController) List(ctx context.Context, userID string) (data []*ProjectData, err error) {
return nil, nil 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
} }