package grpc import ( "context" "time" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/achievement" service "hyapp/services/activity-service/internal/service/achievement" ) // AchievementServer adapts achievement and badge display use cases to gRPC. type AchievementServer struct { activityv1.UnimplementedAchievementServiceServer svc *service.Service } func NewAchievementServer(svc *service.Service) *AchievementServer { return &AchievementServer{svc: svc} } func (s *AchievementServer) ListAchievements(ctx context.Context, req *activityv1.ListAchievementsRequest) (*activityv1.ListAchievementsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, total, err := s.svc.ListAchievements(ctx, domain.ListQuery{ UserID: req.GetUserId(), CollectionType: req.GetCollectionType(), CollectionID: req.GetCollectionId(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.ListAchievementsResponse{Achievements: make([]*activityv1.UserAchievement, 0, len(items)), Total: total, ServerTimeMs: time.Now().UnixMilli()} for _, item := range items { resp.Achievements = append(resp.Achievements, userAchievementToProto(item)) } return resp, nil } func (s *AchievementServer) ConsumeAchievementEvent(ctx context.Context, req *activityv1.ConsumeAchievementEventRequest) (*activityv1.ConsumeAchievementEventResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.ConsumeAchievementEvent(ctx, domain.Event{ EventID: req.GetEventId(), EventType: req.GetEventType(), SourceService: req.GetSourceService(), UserID: req.GetUserId(), MetricType: req.GetMetricType(), Value: req.GetValue(), OccurredAtMS: req.GetOccurredAtMs(), DimensionsJSON: req.GetDimensionsJson(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.ConsumeAchievementEventResponse{ EventId: result.EventID, Status: result.Status, MatchedAchievementCount: result.MatchedAchievementCount, UnlockedAchievementCount: result.UnlockedAchievementCount, RewardJobCount: result.RewardJobCount, }, nil } func (s *AchievementServer) ListMyBadges(ctx context.Context, req *activityv1.ListMyBadgesRequest) (*activityv1.ListMyBadgesResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) profile, err := s.svc.ListBadgeProfile(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } return badgeProfileToProto(profile), nil } func (s *AchievementServer) SetBadgeDisplay(ctx context.Context, req *activityv1.SetBadgeDisplayRequest) (*activityv1.SetBadgeDisplayResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items := make([]domain.BadgeDisplayItem, 0, len(req.GetItems())) for _, item := range req.GetItems() { items = append(items, badgeDisplayItemFromProto(item)) } profile, err := s.svc.SetBadgeDisplay(ctx, domain.DisplayCommand{ UserID: req.GetUserId(), Slot: req.GetSlot(), Items: items, CommandID: req.GetCommandId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.SetBadgeDisplayResponse{Profile: badgeProfileToProto(profile)}, nil } // AdminAchievementServer adapts admin achievement configuration commands to gRPC. type AdminAchievementServer struct { activityv1.UnimplementedAdminAchievementServiceServer svc *service.Service } func NewAdminAchievementServer(svc *service.Service) *AdminAchievementServer { return &AdminAchievementServer{svc: svc} } func (s *AdminAchievementServer) ListAchievementDefinitions(ctx context.Context, req *activityv1.ListAchievementsRequest) (*activityv1.ListAchievementsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, total, err := s.svc.ListAchievementDefinitions(ctx, domain.ListQuery{ CollectionType: req.GetCollectionType(), CollectionID: req.GetCollectionId(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.ListAchievementsResponse{Achievements: make([]*activityv1.UserAchievement, 0, len(items)), Total: total, ServerTimeMs: time.Now().UnixMilli()} for _, item := range items { resp.Achievements = append(resp.Achievements, &activityv1.UserAchievement{Definition: achievementDefinitionToProto(item)}) } return resp, nil } func (s *AdminAchievementServer) UpsertAchievementDefinition(ctx context.Context, req *activityv1.UpsertAchievementDefinitionRequest) (*activityv1.UpsertAchievementDefinitionResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) conditions := make([]domain.ConditionCommand, 0, len(req.GetConditions())) for _, condition := range req.GetConditions() { conditions = append(conditions, domain.ConditionCommand{ MetricType: condition.GetMetricType(), TargetValue: condition.GetTargetValue(), TargetUnit: condition.GetTargetUnit(), DimensionFilterJSON: condition.GetDimensionFilterJson(), }) } item, created, err := s.svc.UpsertAchievementDefinition(ctx, domain.DefinitionCommand{ AchievementID: req.GetAchievementId(), CollectionID: req.GetCollectionId(), CollectionType: req.GetCollectionType(), AchievementType: req.GetAchievementType(), Title: req.GetTitle(), Description: req.GetDescription(), Status: req.GetStatus(), PrimaryBadgeResourceID: req.GetPrimaryBadgeResourceId(), RewardResourceGroupID: req.GetRewardResourceGroupId(), AutoPinPolicy: req.GetAutoPinPolicy(), HonorVisibility: req.GetHonorVisibility(), EffectiveFromMS: req.GetEffectiveFromMs(), EffectiveToMS: req.GetEffectiveToMs(), SortOrder: req.GetSortOrder(), DisplayConfigJSON: req.GetDisplayConfigJson(), OperatorAdminID: req.GetOperatorAdminId(), Conditions: conditions, }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.UpsertAchievementDefinitionResponse{Achievement: achievementDefinitionToProto(item), Created: created}, nil } func userAchievementToProto(item domain.UserAchievement) *activityv1.UserAchievement { return &activityv1.UserAchievement{ Definition: achievementDefinitionToProto(item.Definition), CycleKey: item.CycleKey, ProgressValue: item.ProgressValue, TargetValue: item.TargetValue, UserStatus: item.UserStatus, UnlockedAtMs: item.UnlockedAtMS, RewardStatus: item.RewardStatus, } } func achievementDefinitionToProto(item domain.Definition) *activityv1.AchievementDefinition { resp := &activityv1.AchievementDefinition{ AchievementId: item.AchievementID, CollectionId: item.CollectionID, CollectionType: item.CollectionType, AchievementType: item.AchievementType, Title: item.Title, Description: item.Description, Status: item.Status, Version: item.Version, PrimaryBadgeResourceId: item.PrimaryBadgeResourceID, RewardResourceGroupId: item.RewardResourceGroupID, AutoPinPolicy: item.AutoPinPolicy, HonorVisibility: item.HonorVisibility, EffectiveFromMs: item.EffectiveFromMS, EffectiveToMs: item.EffectiveToMS, SortOrder: item.SortOrder, DisplayConfigJson: item.DisplayConfigJSON, CreatedByAdminId: item.CreatedByAdminID, UpdatedByAdminId: item.UpdatedByAdminID, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, Conditions: make([]*activityv1.AchievementCondition, 0, len(item.Conditions)), } for _, condition := range item.Conditions { resp.Conditions = append(resp.Conditions, achievementConditionToProto(condition)) } return resp } func achievementConditionToProto(item domain.Condition) *activityv1.AchievementCondition { return &activityv1.AchievementCondition{ ConditionId: item.ConditionID, AchievementId: item.AchievementID, Version: item.Version, MetricType: item.MetricType, TargetValue: item.TargetValue, TargetUnit: item.TargetUnit, DimensionFilterJson: item.DimensionFilterJSON, } } func badgeProfileToProto(profile domain.BadgeProfile) *activityv1.ListMyBadgesResponse { resp := &activityv1.ListMyBadgesResponse{ StripBadges: make([]*activityv1.BadgeDisplayItem, 0, len(profile.StripBadges)), ProfileTileBadges: make([]*activityv1.BadgeDisplayItem, 0, len(profile.ProfileTileBadges)), HonorBadges: make([]*activityv1.BadgeDisplayItem, 0, len(profile.HonorBadges)), ServerTimeMs: profile.ServerTimeMS, } for _, item := range profile.StripBadges { resp.StripBadges = append(resp.StripBadges, badgeDisplayItemToProto(item)) } for _, item := range profile.ProfileTileBadges { resp.ProfileTileBadges = append(resp.ProfileTileBadges, badgeDisplayItemToProto(item)) } for _, item := range profile.HonorBadges { resp.HonorBadges = append(resp.HonorBadges, badgeDisplayItemToProto(item)) } return resp } func badgeDisplayItemToProto(item domain.BadgeDisplayItem) *activityv1.BadgeDisplayItem { return &activityv1.BadgeDisplayItem{ Slot: item.Slot, Position: item.Position, BadgeForm: item.BadgeForm, ResourceId: item.ResourceID, EntitlementId: item.EntitlementID, SourceType: item.SourceType, SourceId: item.SourceID, PinMode: item.PinMode, UpdatedAtMs: item.UpdatedAtMS, } } func badgeDisplayItemFromProto(item *activityv1.BadgeDisplayItem) domain.BadgeDisplayItem { return domain.BadgeDisplayItem{ Slot: item.GetSlot(), Position: item.GetPosition(), BadgeForm: item.GetBadgeForm(), ResourceID: item.GetResourceId(), EntitlementID: item.GetEntitlementId(), SourceType: item.GetSourceType(), SourceID: item.GetSourceId(), PinMode: item.GetPinMode(), UpdatedAtMS: item.GetUpdatedAtMs(), } }