package grpc import ( "context" "datarush/internal/competition/domain" pb "datarush/pkg/api/competition" "strings" "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" ) // ICompetitionService defines the interface for business logic. // It works with domain models. type ICompetitionService interface { CreateCompetition(ctx context.Context, comp *domain.Competition) (*domain.Competition, error) GetCompetition(ctx context.Context, id uuid.UUID) (*domain.Competition, error) EditCompetition(ctx context.Context, comp *domain.Competition) (*domain.Competition, error) DeleteCompetition(ctx context.Context, id uuid.UUID) error ListCompetitions(ctx context.Context, pageSize int32, pageToken int32, state *domain.CompetitionState, isParticipating *bool, searchQuery *string) (competitions []domain.Competition, totalCount int32, nextPageToken int32, err error) ChangeCompetitionState(ctx context.Context, id uuid.UUID, state domain.CompetitionState) (*domain.Competition, error) } type CompetitionHandler struct { pb.UnimplementedCompetitionServiceServer service ICompetitionService } func NewCompetitionHandler(s ICompetitionService) *CompetitionHandler { return &CompetitionHandler{service: s} } func (h *CompetitionHandler) CreateCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) { domainComp, err := toDomainCompetition(req) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "failed to map competition: %v", err) } createdComp, err := h.service.CreateCompetition(ctx, domainComp) if err != nil { return nil, status.Errorf(codes.Internal, "failed to create competition: %v", err) } return fromDomainCompetition(createdComp), nil } func (h *CompetitionHandler) GetCompetition(ctx context.Context, req *pb.GetCompetitionRequest) (*pb.Competition, error) { id, err := uuid.Parse(req.GetCompetitionId()) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid competition id format") } comp, err := h.service.GetCompetition(ctx, id) if err != nil { return nil, status.Errorf(codes.NotFound, "failed to get competition: %v", err) } return fromDomainCompetition(comp), nil } func (h *CompetitionHandler) EditCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) { domainComp, err := toDomainCompetition(req) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "failed to map competition: %v", err) } updatedComp, err := h.service.EditCompetition(ctx, domainComp) if err != nil { return nil, status.Errorf(codes.Internal, "failed to edit competition: %v", err) } return fromDomainCompetition(updatedComp), nil } func (h *CompetitionHandler) DeleteCompetition(ctx context.Context, req *pb.DeleteCompetitionRequest) (*emptypb.Empty, error) { id, err := uuid.Parse(req.GetCompetitionId()) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid competition id format") } if err := h.service.DeleteCompetition(ctx, id); err != nil { return nil, status.Errorf(codes.Internal, "failed to delete competition: %v", err) } return &emptypb.Empty{}, nil } func (h *CompetitionHandler) ListCompetitions(ctx context.Context, req *pb.ListCompetitionsRequest) (*pb.ListCompetitionsResponse, error) { var state *domain.CompetitionState if req.State != nil { s := toDomainCompetitionState(*req.State) state = &s } var searchQuery *string if req.SearchQuery != nil { searchQuery = req.SearchQuery } competitions, totalCount, nextPageToken, err := h.service.ListCompetitions(ctx, req.PageSize, req.PageToken, state, req.IsParticipating, searchQuery) if err != nil { return nil, status.Errorf(codes.Internal, "failed to list competitions: %v", err) } pbCompetitions := make([]*pb.Competition, len(competitions)) for i, c := range competitions { pbCompetitions[i] = fromDomainCompetition(&c) } return &pb.ListCompetitionsResponse{ Competitions: pbCompetitions, TotalCount: totalCount, NextPageToken: nextPageToken, }, nil } func (h *CompetitionHandler) ChangeCompetitionState(ctx context.Context, req *pb.ChangeCompetitionStateRequest) (*pb.Competition, error) { id, err := uuid.Parse(req.GetCompetitionId()) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid competition id format") } comp, err := h.service.ChangeCompetitionState(ctx, id, toDomainCompetitionState(req.GetState())) if err != nil { return nil, status.Errorf(codes.Internal, "failed to change competition state: %v", err) } return fromDomainCompetition(comp), nil } func toDomainCompetition(c *pb.Competition) (*domain.Competition, error) { if c == nil { return nil, nil } var id uuid.UUID var err error if c.Id != "" { id, err = uuid.Parse(c.Id) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid id format") } } return &domain.Competition{ ID: id, State: toDomainCompetitionState(c.State), Title: c.Title, Description: c.Description, ImageURL: c.ImageUrl, StartTime: c.StartTime.AsTime(), EndTime: c.EndTime.AsTime(), Type: toDomainCompetitionType(c.Type), ParticipationType: toDomainParticipationType(c.ParticipationType), CreatedAt: c.CreatedAt.AsTime(), UpdatedAt: c.UpdatedAt.AsTime(), }, nil } func fromDomainCompetition(c *domain.Competition) *pb.Competition { if c == nil { return nil } return &pb.Competition{ Id: c.ID.String(), State: fromDomainCompetitionState(c.State), Title: c.Title, Description: c.Description, ImageUrl: c.ImageURL, StartTime: timestamppb.New(c.StartTime), EndTime: timestamppb.New(c.EndTime), Type: fromDomainCompetitionType(c.Type), ParticipationType: fromDomainParticipationType(c.ParticipationType), CreatedAt: timestamppb.New(c.CreatedAt), UpdatedAt: timestamppb.New(c.UpdatedAt), } } func toDomainCompetitionState(s pb.CompetitionState) domain.CompetitionState { return domain.CompetitionState(strings.TrimPrefix(s.String(), "COMPETITION_STATE_")) } func fromDomainCompetitionState(s domain.CompetitionState) pb.CompetitionState { return pb.CompetitionState(pb.CompetitionState_value["COMPETITION_STATE_"+string(s)]) } func toDomainParticipationType(pt pb.ParticipationType) domain.ParticipationType { return domain.ParticipationType(strings.TrimPrefix(pt.String(), "PARTICIPATION_TYPE_")) } func fromDomainParticipationType(pt domain.ParticipationType) pb.ParticipationType { return pb.ParticipationType(pb.ParticipationType_value["PARTICIPATION_TYPE_"+string(pt)]) } func toDomainCompetitionType(ct pb.CompetitionType) domain.CompetitionType { return domain.CompetitionType(strings.TrimPrefix(ct.String(), "COMPETITION_TYPE_")) } func fromDomainCompetitionType(ct domain.CompetitionType) pb.CompetitionType { return pb.CompetitionType(pb.CompetitionType_value["COMPETITION_TYPE_"+string(ct)]) }