Files
Messenger/server/internal/store/groups.go
T
vantablackTea 9d03ea4c51
CI / Go (build + vet + test) (push) Canceled after 0s
CI / Web (build + lint) (push) Canceled after 0s
big update: added reactions, groups, media share
2026-09-27 21:59:48 +03:00

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
}