122 lines
3.4 KiB
Go
122 lines
3.4 KiB
Go
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
|
|
}
|