合并 origin/main 设备注册后,纳入本地 music 模块、seed 命令与 popadmin 相关路由/文档;music migration 编号为 0020 以避免与 0019_devices 冲突。 Co-authored-by: Cursor <cursoragent@cursor.com>
141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
// Package logic seeds demo bots, welcome messages, and the English sample group.
|
|
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
friendentity "spark-server/internal/module/friend/entity"
|
|
groupentity "spark-server/internal/module/group/entity"
|
|
msgentity "spark-server/internal/module/message/entity"
|
|
userentity "spark-server/internal/module/user/entity"
|
|
usermodel "spark-server/internal/module/user/model"
|
|
"spark-server/internal/pkg/errs"
|
|
)
|
|
|
|
// IDGen issues Snowflake ids for users, conversations, and messages.
|
|
type IDGen interface {
|
|
NextID() (int64, error)
|
|
}
|
|
|
|
// Users loads and creates accounts used by the seed.
|
|
type Users interface {
|
|
GetByEmail(ctx context.Context, email string) (*userentity.User, error)
|
|
CreateWithEmail(ctx context.Context, u *userentity.User, email string) error
|
|
ListAccounts(ctx context.Context) ([]usermodel.Account, error)
|
|
}
|
|
|
|
// Friends writes directed friend edges.
|
|
type Friends interface {
|
|
GetRelation(ctx context.Context, userID, friendID int64) (*friendentity.Relation, error)
|
|
AcceptFriend(ctx context.Context, in friendentity.AcceptParams) error
|
|
}
|
|
|
|
// Chats opens P2P conversations and bumps last_msg.
|
|
type Chats interface {
|
|
EnsureP2P(ctx context.Context, userA, userB int64) (int64, error)
|
|
UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error
|
|
}
|
|
|
|
// Messages persists private / group bodies.
|
|
type Messages interface {
|
|
Create(ctx context.Context, m *msgentity.Message) error
|
|
GetByClientMsgID(ctx context.Context, senderID, conversationID int64, clientMsgID string) (*msgentity.Message, error)
|
|
}
|
|
|
|
// Groups creates the sample group and membership.
|
|
type Groups interface {
|
|
FindByOwnerAndName(ctx context.Context, ownerID int64, name string) (*groupentity.Group, error)
|
|
CreateGroup(ctx context.Context, g *groupentity.Group, members []*groupentity.Member) error
|
|
GetMember(ctx context.Context, groupID, userID int64) (*groupentity.Member, error)
|
|
AddMembers(ctx context.Context, groupID int64, members []*groupentity.Member) error
|
|
UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error
|
|
}
|
|
|
|
// Service is the idempotent demo / onboarding seeder.
|
|
type Service struct {
|
|
users Users
|
|
friends Friends
|
|
chats Chats
|
|
msgs Messages
|
|
groups Groups
|
|
ids IDGen
|
|
}
|
|
|
|
// NewService wires seed dependencies.
|
|
func NewService(users Users, friends Friends, chats Chats, msgs Messages, groups Groups, ids IDGen) *Service {
|
|
return &Service{users: users, friends: friends, chats: chats, msgs: msgs, groups: groups, ids: ids}
|
|
}
|
|
|
|
// Result summarizes what the seed pass did (created or already present).
|
|
type Result struct {
|
|
Bots int `json:"bots"`
|
|
TestUsers int `json:"test_users"`
|
|
Targets int `json:"targets"`
|
|
GroupID int64 `json:"group_id"`
|
|
SampleMsgs int `json:"sample_msgs"`
|
|
}
|
|
|
|
func (s *Service) ready() bool {
|
|
return s != nil && s.users != nil && s.friends != nil && s.chats != nil &&
|
|
s.msgs != nil && s.groups != nil && s.ids != nil
|
|
}
|
|
|
|
// Run ensures bots, test accounts, per-user welcome/greeting, and the sample group.
|
|
func (s *Service) Run(ctx context.Context) (*Result, error) {
|
|
if !s.ready() {
|
|
return nil, errs.ErrAvailable
|
|
}
|
|
bots, err := s.ensureBots(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.ensureTestUsers(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
targets, err := s.listTargets(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
welcome := bots[userentity.WelcomeBotIndex-1]
|
|
for _, u := range targets {
|
|
if err := s.onboardUser(ctx, u, welcome); err != nil {
|
|
return nil, fmt.Errorf("onboard %d: %w", u.ID, err)
|
|
}
|
|
}
|
|
gid, samples, err := s.ensureSampleGroup(ctx, bots, targets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Result{
|
|
Bots: len(bots), TestUsers: 10, Targets: len(targets),
|
|
GroupID: gid, SampleMsgs: samples,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) listTargets(ctx context.Context) ([]*userentity.User, error) {
|
|
accounts, err := s.users.ListAccounts(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]*userentity.User, 0, len(accounts))
|
|
for _, a := range accounts {
|
|
if a.User == nil || userentity.IsBotEmail(a.Email) {
|
|
continue
|
|
}
|
|
if a.User.Status != userentity.StatusActive {
|
|
continue
|
|
}
|
|
out = append(out, a.User)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func ignoreDup(err error) error {
|
|
if err == nil || errors.Is(err, errs.ErrDuplicate) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|