Files
backend/internal/transport/grpcserver/invitation_service.go
T
2026-07-15 22:42:31 +08:00

324 lines
10 KiB
Go

package grpcserver
import (
"context"
"strings"
"time"
"github.com/XingfenD/yoresee_doc/internal/dto"
"github.com/XingfenD/yoresee_doc/internal/repository/user_repo"
"github.com/XingfenD/yoresee_doc/internal/service/auth_service"
"github.com/XingfenD/yoresee_doc/internal/service/invitation_service"
"github.com/XingfenD/yoresee_doc/internal/status"
"github.com/XingfenD/yoresee_doc/internal/utils"
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
)
type InvitationServiceServer struct {
pb.UnimplementedInvitationServiceServer
userRepo *user_repo.UserRepository
}
func NewInvitationServiceServer(userRepo *user_repo.UserRepository) *InvitationServiceServer {
return &InvitationServiceServer{
userRepo: userRepo,
}
}
func (s *InvitationServiceServer) requireAdmin(ctx context.Context) error {
userExternalID, ok := ctx.Value("user_external_id").(string)
if !ok || userExternalID == "" {
return status.StatusTokenInvalid
}
isAdmin, err := auth_service.AuthSvc.IsAdmin(userExternalID)
if err != nil {
return err
}
if !isAdmin {
return status.StatusPermissionDenied
}
return nil
}
func parseOptionalTime(input *string) (*time.Time, error) {
if input == nil || strings.TrimSpace(*input) == "" {
return nil, nil
}
tm, err := time.Parse(time.RFC3339, *input)
if err != nil {
return nil, err
}
return &tm, nil
}
func (s *InvitationServiceServer) CreateInvitation(ctx context.Context, req *pb.CreateInvitationRequest) (*pb.CreateInvitationResponse, error) {
if req == nil {
return &pb.CreateInvitationResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
if err := s.requireAdmin(ctx); err != nil {
return &pb.CreateInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
userExternalID, ok := ctx.Value("user_external_id").(string)
if !ok || userExternalID == "" {
return &pb.CreateInvitationResponse{Base: baseResponseFromStatus(status.StatusTokenInvalid)}, nil
}
expiresAt, err := parseOptionalTime(req.ExpiresAt)
if err != nil {
return &pb.CreateInvitationResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
inv, err := invitation_service.InvitationSvc.CreateInvitation(&dto.CreateInvitationRequest{
CreatorExternalID: userExternalID,
MaxUsedCnt: req.MaxUsedCnt,
ExpiresAt: expiresAt,
Note: req.Note,
})
if err != nil {
return &pb.CreateInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
user, _ := s.userRepo.GetByExternalID(userExternalID).Exec()
resp := &dto.InvitationResponse{
Code: inv.Code,
CreatedByExternalID: userExternalID,
CreatedByName: "",
UsedCnt: inv.UsedCnt,
MaxUsedCnt: inv.MaxUsedCnt,
ExpiresAt: inv.ExpiresAt,
CreatedAt: inv.CreatedAt,
Disabled: inv.Disabled,
Note: inv.Note,
}
if user != nil {
if user.Nickname != "" {
resp.CreatedByName = user.Nickname
} else {
resp.CreatedByName = user.Username
}
}
return &pb.CreateInvitationResponse{
Base: baseResponseFromErr(nil),
Invitation: toInvitationResponse(resp),
}, nil
}
func (s *InvitationServiceServer) ListInvitations(ctx context.Context, req *pb.ListInvitationsRequest) (*pb.ListInvitationsResponse, error) {
if req == nil {
return &pb.ListInvitationsResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
onlyMine := req.OnlyMine != nil && req.GetOnlyMine()
if !onlyMine {
if err := s.requireAdmin(ctx); err != nil {
return &pb.ListInvitationsResponse{Base: baseResponseFromErr(err)}, nil
}
}
var creatorID *int64
if onlyMine {
userExternalID, ok := ctx.Value("user_external_id").(string)
if !ok || strings.TrimSpace(userExternalID) == "" {
return &pb.ListInvitationsResponse{Base: baseResponseFromStatus(status.StatusTokenInvalid)}, nil
}
id, err := s.userRepo.GetIDByExternalID(userExternalID).Exec()
if err != nil {
return &pb.ListInvitationsResponse{Base: baseResponseFromErr(status.StatusUserNotFound)}, nil
}
creatorID = utils.Of(id)
}
sortArgs := dto.SortArgs{Field: "created_at", Desc: true}
if req.OrderBy != nil {
sortArgs.Field = req.GetOrderBy()
}
if req.OrderDesc != nil {
sortArgs.Desc = req.GetOrderDesc()
}
listReq := &dto.ListInvitationsReq{
CreatorID: creatorID,
MaxUsedCnt: req.MaxUsedCnt,
ExpiresAtStart: req.ExpiresAtStart,
ExpiresAtEnd: req.ExpiresAtEnd,
CreatedAtStart: req.CreatedAtStart,
CreatedAtEnd: req.CreatedAtEnd,
Disabled: req.Disabled,
OnlyMine: req.OnlyMine,
Keyword: req.Keyword,
SortArgs: sortArgs,
Pagination: dto.Pagination{
Page: int(req.Page),
PageSize: int(req.PageSize),
},
}
invitations, total, err := invitation_service.InvitationSvc.ListInvitations(listReq)
if err != nil {
return &pb.ListInvitationsResponse{Base: baseResponseFromErr(err)}, nil
}
creatorIDs := make([]int64, 0, len(invitations))
for _, inv := range invitations {
creatorIDs = append(creatorIDs, inv.CreatedBy)
}
creatorMap, _ := s.userRepo.MGetUserByID(creatorIDs).Exec()
respInvites := make([]*pb.InvitationResponse, 0, len(invitations))
for _, inv := range invitations {
createdByExternalID := ""
createdByName := ""
if creator := creatorMap[inv.CreatedBy]; creator != nil {
createdByExternalID = creator.ExternalID
if creator.Nickname != "" {
createdByName = creator.Nickname
} else {
createdByName = creator.Username
}
}
resp := &dto.InvitationResponse{
Code: inv.Code,
CreatedByExternalID: createdByExternalID,
CreatedByName: createdByName,
UsedCnt: inv.UsedCnt,
MaxUsedCnt: inv.MaxUsedCnt,
ExpiresAt: inv.ExpiresAt,
CreatedAt: inv.CreatedAt,
Disabled: inv.Disabled,
Note: inv.Note,
}
respInvites = append(respInvites, toInvitationResponse(resp))
}
return &pb.ListInvitationsResponse{
Base: baseResponseFromErr(nil),
Invitations: respInvites,
Total: total,
}, nil
}
func (s *InvitationServiceServer) UpdateInvitation(ctx context.Context, req *pb.UpdateInvitationRequest) (*pb.UpdateInvitationResponse, error) {
if req == nil || strings.TrimSpace(req.Code) == "" {
return &pb.UpdateInvitationResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
if err := s.requireAdmin(ctx); err != nil {
return &pb.UpdateInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
expiresAt, err := parseOptionalTime(req.ExpiresAt)
if err != nil {
return &pb.UpdateInvitationResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
if err := invitation_service.InvitationSvc.UpdateInvitation(&dto.UpdateInvitationRequest{
Code: req.Code,
MaxUsedCnt: req.MaxUsedCnt,
ExpiresAt: expiresAt,
Disabled: req.Disabled,
Note: req.Note,
}); err != nil {
return &pb.UpdateInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
return &pb.UpdateInvitationResponse{Base: baseResponseFromErr(nil)}, nil
}
func (s *InvitationServiceServer) DeleteInvitation(ctx context.Context, req *pb.DeleteInvitationRequest) (*pb.DeleteInvitationResponse, error) {
if req == nil || strings.TrimSpace(req.Code) == "" {
return &pb.DeleteInvitationResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
if err := s.requireAdmin(ctx); err != nil {
return &pb.DeleteInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
if err := invitation_service.InvitationSvc.DeleteInvitation(&dto.DeleteInvitationRequest{Code: req.Code}); err != nil {
return &pb.DeleteInvitationResponse{Base: baseResponseFromErr(err)}, nil
}
return &pb.DeleteInvitationResponse{Base: baseResponseFromErr(nil)}, nil
}
func (s *InvitationServiceServer) ListInvitationRecords(ctx context.Context, req *pb.ListInvitationRecordsRequest) (*pb.ListInvitationRecordsResponse, error) {
if req == nil {
return &pb.ListInvitationRecordsResponse{Base: baseResponseFromStatus(status.StatusParamError)}, nil
}
onlyMine := req.OnlyMine != nil && req.GetOnlyMine()
if !onlyMine {
if err := s.requireAdmin(ctx); err != nil {
return &pb.ListInvitationRecordsResponse{Base: baseResponseFromErr(err)}, nil
}
}
var creatorID *int64
if onlyMine {
userExternalID, ok := ctx.Value("user_external_id").(string)
if !ok || strings.TrimSpace(userExternalID) == "" {
return &pb.ListInvitationRecordsResponse{Base: baseResponseFromStatus(status.StatusTokenInvalid)}, nil
}
id, err := s.userRepo.GetIDByExternalID(userExternalID).Exec()
if err != nil {
return &pb.ListInvitationRecordsResponse{Base: baseResponseFromErr(status.StatusUserNotFound)}, nil
}
creatorID = utils.Of(id)
}
listReq := &dto.ListInvitationRecordsRequest{
Code: req.Code,
Status: req.Status,
UsedAtStart: req.UsedAtStart,
UsedAtEnd: req.UsedAtEnd,
CreatorID: creatorID,
OnlyMine: req.OnlyMine,
Keyword: req.Keyword,
Pagination: dto.Pagination{
Page: int(req.Page),
PageSize: int(req.PageSize),
},
}
records, total, err := invitation_service.InvitationSvc.ListInvitationRecords(listReq)
if err != nil {
return &pb.ListInvitationRecordsResponse{Base: baseResponseFromErr(err)}, nil
}
userIDs := make([]int64, 0, len(records))
for _, r := range records {
if r.UsedByUserID != nil {
userIDs = append(userIDs, *r.UsedByUserID)
}
}
userMap, _ := s.userRepo.MGetUserByID(userIDs).Exec()
respRecords := make([]*pb.InvitationRecordResponse, 0, len(records))
for _, r := range records {
usedBy := r.UsedBy
usedByExternalID := ""
if r.UsedByUserID != nil {
if user := userMap[*r.UsedByUserID]; user != nil {
usedByExternalID = user.ExternalID
if user.Nickname != "" {
usedBy = user.Nickname
} else if user.Username != "" {
usedBy = user.Username
}
}
}
resp := &dto.InvitationRecordResponse{
Code: r.Code,
UsedBy: usedBy,
UsedByExternalID: usedByExternalID,
UsedAt: r.UsedAt,
Status: r.Status,
}
respRecords = append(respRecords, toInvitationRecordResponse(resp))
}
return &pb.ListInvitationRecordsResponse{
Base: baseResponseFromErr(nil),
Records: respRecords,
Total: total,
}, nil
}