spark-server/internal/module/group/logic/member_update.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

82 lines
2.4 KiB
Go

package logic
import (
"context"
"strings"
"spark-server/internal/module/group/entity"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/i18n"
"spark-server/internal/pkg/reqctx"
"spark-server/internal/pkg/validate"
)
type UpdateMemberInput struct {
Role *int `json:"role" validate:"omitempty,oneof=2 3" label:"field.role" errmsg:"oneof:validate.group.role_invalid"`
SpeakMuted *bool `json:"speak_muted"`
Nickname *string `json:"nickname" validate:"omitempty,max=32" label:"field.group_nickname" errmsg:"max:validate.group.nickname_invalid"`
}
func (s *Service) UpdateMember(ctx context.Context, actorID, groupID, targetID int64, in *UpdateMemberInput) (*MemberView, error) {
if !s.ready() || actorID <= 0 {
return nil, errs.ErrUnauthorized
}
if in == nil || targetID <= 0 {
return nil, errs.ErrInvalidArgument
}
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
return nil, err
}
if in.Role == nil && in.SpeakMuted == nil && in.Nickname == nil {
return nil, errs.ErrInvalidArgument
}
g, err := s.loadActive(ctx, groupID)
if err != nil {
return nil, err
}
target, err := s.requireMember(ctx, groupID, targetID)
if err != nil {
return nil, err
}
if err := s.applyMemberPatch(ctx, g, actorID, target, in); err != nil {
return nil, err
}
if err := s.repo.UpdateMember(ctx, target); err != nil {
return nil, err
}
u, _ := s.users.Get(ctx, target.UserID)
return memberViewOf(target, u), nil
}
func (s *Service) applyMemberPatch(ctx context.Context, g *entity.Group, actorID int64, target *entity.Member, in *UpdateMemberInput) error {
lang := reqctx.Lang(ctx)
self := actorID == target.UserID
if in.Nickname != nil {
if !self {
if _, err := s.requireManage(ctx, g.ID, actorID); err != nil {
return err
}
}
target.Nickname = strings.TrimSpace(*in.Nickname)
}
if in.Role != nil {
if err := s.requireOwner(ctx, g, actorID); err != nil {
return err
}
if self || (*in.Role != entity.RoleAdmin && *in.Role != entity.RoleMember) {
return errs.ErrForbidden.WithMsg(i18n.T(lang, i18n.KeyGroupRoleDenied, nil))
}
target.Role = *in.Role
}
if in.SpeakMuted != nil {
if _, err := s.requireManage(ctx, g.ID, actorID); err != nil {
return err
}
if target.Role == entity.RoleOwner {
return errs.ErrForbidden.WithMsg(i18n.T(lang, i18n.KeyGroupRoleDenied, nil))
}
target.SpeakMuted = *in.SpeakMuted
}
return nil
}