spark-server/internal/module/group/logic/invite.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"
"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 InviteInput struct {
UIDs []int64 `json:"uids" validate:"required,min=1,max=199,dive,gt=0" label:"field.member_uids" errmsg:"required:validate.group.members_required;min:validate.group.members_required;max:validate.group.members_invalid"`
}
func (s *Service) Invite(ctx context.Context, actorID, groupID int64, in *InviteInput) (*View, error) {
if !s.ready() || actorID <= 0 {
return nil, errs.ErrUnauthorized
}
if in == nil {
return nil, errs.ErrInvalidArgument
}
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
return nil, err
}
g, err := s.loadActive(ctx, groupID)
if err != nil {
return nil, err
}
if _, err := s.requireMember(ctx, groupID, actorID); err != nil {
return nil, err
}
added, err := s.newMembers(ctx, g, actorID, in.UIDs)
if err != nil {
return nil, err
}
if err := s.repo.AddMembers(ctx, groupID, added); err != nil {
return nil, err
}
g.MemberCount += len(added)
s.invalidateMembers(g.ConversationID)
ids := make([]int64, 0, len(added))
for _, mem := range added {
ids = append(ids, mem.UserID)
}
_ = s.notice(ctx, g, actorID, entity.SystemKindInvite, ids)
mem, err := s.requireMember(ctx, groupID, actorID)
if err != nil {
return viewOf(g, entity.RoleMember), nil
}
return viewOf(g, mem.Role), nil
}
func (s *Service) newMembers(ctx context.Context, g *entity.Group, actorID int64, uids []int64) ([]*entity.Member, error) {
lang := reqctx.Lang(ctx)
now := nowMS()
added := make([]*entity.Member, 0, len(uids))
for _, uid := range entity.UniqueUIDs(uids) {
if uid == actorID {
continue
}
if _, err := s.repo.GetMember(ctx, g.ID, uid); err == nil {
continue
}
if err := s.activeUser(ctx, uid); err != nil {
return nil, err
}
if err := s.requireFriend(ctx, actorID, uid); err != nil {
return nil, err
}
added = append(added, &entity.Member{GroupID: g.ID, UserID: uid, Role: entity.RoleMember, JoinedAtMS: now})
}
if len(added) == 0 {
return nil, errs.ErrDuplicate.WithMsg(i18n.T(lang, i18n.KeyGroupAlreadyMember, nil))
}
if g.MemberCount+len(added) > entity.MaxMembers {
return nil, errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyGroupFull, nil))
}
return added, nil
}