package logic import ( "context" "errors" "fmt" "time" friendentity "spark-server/internal/module/friend/entity" msgentity "spark-server/internal/module/message/entity" userentity "spark-server/internal/module/user/entity" "spark-server/internal/pkg/errs" ) const ( welcomeBody = "Welcome to Spark! This is a system welcome message. Explore chats, groups, and sample content anytime." greetingBody = "Hi! I'm %s — your sample robot friend. Reply anytime; this is an English greeting demo." ) func (s *Service) onboardUser(ctx context.Context, user, bot *userentity.User) error { if user == nil || bot == nil || user.ID == bot.ID { return errs.ErrInvalidArgument } if err := s.ensureFriendship(ctx, user.ID, bot.ID); err != nil { return err } convID, err := s.chats.EnsureP2P(ctx, user.ID, bot.ID) if err != nil { return err } if err := s.ensureSystemWelcome(ctx, user.ID, convID); err != nil { return err } return s.ensureBotGreeting(ctx, user.ID, bot, convID) } func (s *Service) ensureFriendship(ctx context.Context, userID, botID int64) error { rel, err := s.friends.GetRelation(ctx, userID, botID) if err == nil && rel.Active() { return nil } if err != nil && !errors.Is(err, errs.ErrNotFound) { return err } now := time.Now() return ignoreDup(s.friends.AcceptFriend(ctx, friendentity.AcceptParams{ Edges: []*friendentity.Relation{ friendentity.NewFriendEdge(userID, botID, "", friendentity.SourceEmail, now), friendentity.NewFriendEdge(botID, userID, "", friendentity.SourceEmail, now), }, })) } func (s *Service) ensureSystemWelcome(ctx context.Context, userID, convID int64) error { clientID := fmt.Sprintf("seed:welcome:%d", userID) msgID, err := s.ids.NextID() if err != nil { return err } raw, err := msgentity.NewSystem(&msgentity.System{ Kind: userentity.SystemKindWelcome, Body: welcomeBody, }).Marshal() if err != nil { return err } now := time.Now().UnixMilli() msg := &msgentity.Message{ ID: msgID, ConversationID: convID, SenderID: 0, ReceiverID: userID, ClientMsgID: clientID, Type: msgentity.TypeSystem, Kind: msgentity.KindPrivate, Content: raw, Status: msgentity.StatusNormal, CreatedAtMS: now, } if err := s.msgs.Create(ctx, msg); err != nil { if errors.Is(err, errs.ErrDuplicate) { return nil } return err } return s.chats.UpdateLastMsg(ctx, convID, msgID, now) } func (s *Service) ensureBotGreeting(ctx context.Context, userID int64, bot *userentity.User, convID int64) error { clientID := fmt.Sprintf("seed:greet:%d", userID) if _, err := s.msgs.GetByClientMsgID(ctx, bot.ID, convID, clientID); err == nil { return nil } else if err != nil && !errors.Is(err, errs.ErrNotFound) { return err } msgID, err := s.ids.NextID() if err != nil { return err } body := fmt.Sprintf(greetingBody, bot.Nickname) raw, err := msgentity.NewText(body).Marshal() if err != nil { return err } now := time.Now().UnixMilli() msg := &msgentity.Message{ ID: msgID, ConversationID: convID, SenderID: bot.ID, ReceiverID: userID, ClientMsgID: clientID, Type: msgentity.TypeText, Kind: msgentity.KindPrivate, Content: raw, Status: msgentity.StatusNormal, CreatedAtMS: now, } if err := ignoreDup(s.msgs.Create(ctx, msg)); err != nil { return err } return s.chats.UpdateLastMsg(ctx, convID, msgID, now) }