package store import ( "context" "errors" "github.com/jackc/pgx/v5" ) // CreateGroup creates a group conversation with the creator as owner and // the given members added, returning the created conversation. func (s *Store) CreateGroup(ctx context.Context, creatorID int64, name string, memberIDs []int64) (*Conversation, error) { conn, err := s.pool.Acquire(ctx) if err != nil { return nil, err } defer conn.Release() tx, err := conn.Begin(ctx) if err != nil { return nil, err } defer tx.Rollback(ctx) var conv Conversation err = tx.QueryRow(ctx, ` INSERT INTO conversations(type, name) VALUES('group', $1) RETURNING id, type, name, avatar, created_at `, name).Scan(&conv.ID, &conv.Type, &conv.Name, &conv.Avatar, &conv.CreatedAt) if err != nil { return nil, err } if _, err := tx.Exec(ctx, ` INSERT INTO conversation_members(conversation_id, user_id, role) VALUES($1, $2, 'owner') `, conv.ID, creatorID); err != nil { return nil, err } seen := map[int64]struct{}{creatorID: {}} for _, uid := range memberIDs { if _, ok := seen[uid]; ok { continue } seen[uid] = struct{}{} if _, err := tx.Exec(ctx, ` INSERT INTO conversation_members(conversation_id, user_id, role) VALUES($1, $2, 'member') ON CONFLICT (conversation_id, user_id) DO NOTHING `, conv.ID, uid); err != nil { return nil, err } } if err := tx.Commit(ctx); err != nil { return nil, err } return &conv, nil } // AddMember adds a user to a conversation; it is a no-op if already a member. func (s *Store) AddMember(ctx context.Context, conversationID, userID int64) error { _, err := s.pool.Exec(ctx, ` INSERT INTO conversation_members(conversation_id, user_id, role) VALUES($1, $2, 'member') ON CONFLICT (conversation_id, user_id) DO NOTHING `, conversationID, userID) return err } // RemoveMember removes a user from a conversation; it is a no-op if absent. func (s *Store) RemoveMember(ctx context.Context, conversationID, userID int64) error { _, err := s.pool.Exec(ctx, ` DELETE FROM conversation_members WHERE conversation_id=$1 AND user_id=$2 `, conversationID, userID) return err } // MemberRole returns the role of a user in a conversation, or ErrNotFound. func (s *Store) MemberRole(ctx context.Context, conversationID, userID int64) (string, error) { var role string err := s.pool.QueryRow(ctx, ` SELECT role FROM conversation_members WHERE conversation_id=$1 AND user_id=$2 `, conversationID, userID).Scan(&role) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return "", ErrNotFound } return "", err } return role, nil } // ListMembers returns the members of a conversation with their profiles. func (s *Store) ListMembers(ctx context.Context, conversationID int64) ([]MemberInfo, error) { rows, err := s.pool.Query(ctx, ` SELECT u.id, u.username, u.display_name, u.avatar, u.created_at, cm.role, cm.joined_at FROM conversation_members cm JOIN users u ON u.id = cm.user_id WHERE cm.conversation_id = $1 ORDER BY cm.joined_at, u.id `, conversationID) if err != nil { return nil, err } defer rows.Close() var members []MemberInfo for rows.Next() { var m MemberInfo if err := rows.Scan( &m.User.ID, &m.User.Username, &m.User.DisplayName, &m.User.Avatar, &m.User.CreatedAt, &m.Role, &m.JoinedAt, ); err != nil { return nil, err } members = append(members, m) } return members, nil }