package service import ( "context" "net/http" "cmr-backend/internal/apperr" "cmr-backend/internal/store/postgres" ) type ProfileService struct { store *postgres.Store } type ProfileResult struct { User struct { ID string `json:"id"` PublicID string `json:"publicId"` Status string `json:"status"` Nickname *string `json:"nickname,omitempty"` AvatarURL *string `json:"avatarUrl,omitempty"` } `json:"user"` Bindings struct { HasMobile bool `json:"hasMobile"` HasWechatMini bool `json:"hasWechatMini"` HasWechatUnion bool `json:"hasWechatUnion"` Items []ProfileBindingItem `json:"items"` } `json:"bindings"` RecentSessions []EntrySessionSummary `json:"recentSessions"` } type ProfileBindingItem struct { IdentityType string `json:"identityType"` Provider string `json:"provider"` Status string `json:"status"` CountryCode *string `json:"countryCode,omitempty"` Mobile *string `json:"mobile,omitempty"` MaskedLabel string `json:"maskedLabel"` } func NewProfileService(store *postgres.Store) *ProfileService { return &ProfileService{store: store} } func (s *ProfileService) GetProfile(ctx context.Context, userID string) (*ProfileResult, error) { if userID == "" { return nil, apperr.New(http.StatusUnauthorized, "unauthorized", "user is required") } user, err := s.store.GetUserByID(ctx, s.store.Pool(), userID) if err != nil { return nil, err } if user == nil { return nil, apperr.New(http.StatusNotFound, "user_not_found", "user not found") } identities, err := s.store.ListIdentitiesByUserID(ctx, userID) if err != nil { return nil, err } sessions, err := s.store.ListSessionsByUserID(ctx, userID, 5) if err != nil { return nil, err } result := &ProfileResult{} result.User.ID = user.ID result.User.PublicID = user.PublicID result.User.Status = user.Status result.User.Nickname = user.Nickname result.User.AvatarURL = user.AvatarURL for _, identity := range identities { item := ProfileBindingItem{ IdentityType: identity.IdentityType, Provider: identity.Provider, Status: identity.Status, CountryCode: identity.CountryCode, Mobile: identity.Mobile, MaskedLabel: maskIdentity(identity), } result.Bindings.Items = append(result.Bindings.Items, item) switch identity.Provider { case "mobile": result.Bindings.HasMobile = true case "wechat_mini": result.Bindings.HasWechatMini = true case "wechat_unionid": result.Bindings.HasWechatUnion = true } } for i := range sessions { result.RecentSessions = append(result.RecentSessions, buildEntrySessionSummary(&sessions[i])) } return result, nil } func maskIdentity(identity postgres.LoginIdentity) string { if identity.Provider == "mobile" && identity.Mobile != nil { value := *identity.Mobile if len(value) >= 7 { return value[:3] + "****" + value[len(value)-4:] } return value } if identity.Provider == "wechat_mini" { return "WeChat Mini bound" } if identity.Provider == "wechat_unionid" { return "WeChat Union bound" } return identity.Provider }