package logic import ( "context" "errors" "time" "spark-server/internal/module/conversation/entity" "spark-server/internal/pkg/errs" ) type IDGen interface { NextID() (int64, error) } type P2PRepository interface { GetP2P(ctx context.Context, userA, userB int64) (*entity.Conversation, error) CreateP2P(ctx context.Context, c *entity.Conversation, p2p *entity.P2P, members []*entity.Member) error UpdateLastMsg(ctx context.Context, id, msgID, atMS int64) error } // P2PService opens the unique 1:1 conversation for a user pair. type P2PService struct { repo P2PRepository ids IDGen } func NewP2PService(repo P2PRepository, ids IDGen) *P2PService { return &P2PService{repo: repo, ids: ids} } func (s *P2PService) EnsureP2P(ctx context.Context, userA, userB int64) (int64, error) { if s == nil || s.repo == nil || s.ids == nil || userA <= 0 || userB <= 0 || userA == userB { return 0, errs.ErrInvalidArgument } if c, err := s.repo.GetP2P(ctx, userA, userB); err == nil { return c.ID, nil } else if !errors.Is(err, errs.ErrNotFound) { return 0, err } id, err := s.ids.NextID() if err != nil { return 0, err } now := time.Now() small, big := entity.OrderUsers(userA, userB) conv := &entity.Conversation{ID: id, Type: entity.TypeP2P, LastMsgAt: now.UnixMilli()} p2p := &entity.P2P{UserSmall: small, UserBig: big, ConversationID: id} joined := now.UnixMilli() members := []*entity.Member{ {ConversationID: id, UserID: userA, JoinedAtMS: joined}, {ConversationID: id, UserID: userB, JoinedAtMS: joined}, } if err := s.repo.CreateP2P(ctx, conv, p2p, members); err != nil { if errors.Is(err, errs.ErrDuplicate) { c, err := s.repo.GetP2P(ctx, userA, userB) if err != nil { return 0, err } return c.ID, nil } return 0, err } return id, nil } func (s *P2PService) UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error { if s == nil || s.repo == nil { return errs.ErrAvailable } return s.repo.UpdateLastMsg(ctx, conversationID, msgID, atMS) }