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>
82 lines
2.4 KiB
Go
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
|
|
}
|