Files
relay-server/database/groups.go
2025-02-11 23:03:31 +01:00

252 lines
7.4 KiB
Go

package database
import (
"database/sql"
"fmt"
"github.com/google/uuid"
"relay-server/model"
"relay-server/utils"
)
func CreateGroup(db *sql.DB, groupName string, userID uuid.UUID) (uuid.UUID, error) {
createConversationQuery := `
INSERT INTO Conversations (conversation_type, name)
VALUES ('group', $1)
RETURNING conversation_id AS group_id;
`
insertGroupAdminQuery := `
INSERT INTO GroupAdmins (conversation_id, user_id, granted_by, is_owner)
VALUES ($1, $2, $3, true)
RETURNING granted_at;
`
// Create conversation
var groupID uuid.UUID
err := db.QueryRow(createConversationQuery, groupName).Scan(&groupID)
if err != nil {
return uuid.Nil, utils.NewError(utils.ErrInternal, "Failed to create group", fmt.Errorf("failed to create group: %w", err))
}
// Insert group admin (make user that created the group an admin)
var grantedAt string
err = db.QueryRow(insertGroupAdminQuery, groupID, userID, userID).Scan(&grantedAt)
if err != nil {
return uuid.Nil, utils.NewError(utils.ErrInternal, "Failed to create group", fmt.Errorf("failed to insert group admin: %w", err))
}
// Add self as group member
_, err = AddMemberToGroup(db, userID, groupID)
if err != nil {
return uuid.Nil, err
}
// Insert group contact
_, err = InsertContactByID(db, userID, groupID)
if err != nil {
return groupID, utils.NewError(utils.ErrInternal, "Failed to create group contact", fmt.Errorf("failed to insert group contact: %w", err))
}
return groupID, nil
}
func AddMemberToGroup(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (uuid.UUID, error) {
query := `
INSERT INTO Memberships (conversation_id, user_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
RETURNING user_id;
`
var memberID uuid.UUID
err := db.QueryRow(query, groupID, userID).Scan(&memberID)
if err != nil {
return uuid.Nil, utils.NewError(utils.ErrInternal, "Failed to add member to group", fmt.Errorf("failed to add member to group: %w", err))
}
return memberID, nil
}
func GetMembers(db *sql.DB, groupID uuid.UUID) ([]*model.Member, error) {
query := `
SELECT
a.user_id,
a.username,
CASE
WHEN ga.user_id IS NOT NULL THEN TRUE
ELSE FALSE
END AS isAdmin,
COALESCE(ga.is_owner, FALSE) AS isOwner
FROM Memberships m
JOIN Accounts a ON m.user_id = a.user_id
LEFT JOIN GroupAdmins ga ON m.user_id = ga.user_id AND m.conversation_id = ga.conversation_id
WHERE m.conversation_id = $1;
`
rows, err := db.Query(query, groupID)
if err != nil {
return []*model.Member{}, utils.NewError(utils.ErrInternal, "Failed to get members", fmt.Errorf("failed to get members: %w", err))
}
defer rows.Close()
var members []*model.Member
for rows.Next() {
var member model.Member
err = rows.Scan(&member.UserID, &member.Username, &member.IsAdmin, &member.IsOwner)
if err != nil {
return []*model.Member{}, utils.NewError(utils.ErrInternal, "Failed to get members", fmt.Errorf("failed to scan member: %w", err))
}
members = append(members, &member)
}
if err = rows.Err(); err != nil {
return []*model.Member{}, utils.NewError(utils.ErrInternal, "Failed to get members", fmt.Errorf("error iterating members: %w", err))
}
return members, nil
}
func IsAdmin(db *sql.DB, userID uuid.UUID, conversationID uuid.UUID) (bool, error) {
query := `
SELECT COUNT(1) FROM GroupAdmins
WHERE user_id = $1
AND conversation_id = $2;
`
var count int
err := db.QueryRow(query, userID, conversationID).Scan(&count)
if err != nil {
return false, utils.NewError(utils.ErrInternal, "Failed to check admin status", fmt.Errorf("failed to check admin status: %w", err))
}
return count > 0, nil
}
func IsMember(db *sql.DB, userID uuid.UUID, conversationID uuid.UUID) (bool, error) {
query := `
SELECT COUNT(1) FROM Memberships
WHERE user_id = $1
AND conversation_id = $2;
`
var count int
err := db.QueryRow(query, userID, conversationID).Scan(&count)
if err != nil {
return false, utils.NewError(utils.ErrInternal, "Failed to check membership", fmt.Errorf("failed to check membership: %w", err))
}
return count > 0, nil
}
func GetUserConversations(db *sql.DB, userID string) ([]string, error) {
query := `
SELECT DISTINCT m.conversation_id
FROM Memberships m
JOIN Conversations c ON m.conversation_id = c.conversation_id
WHERE m.user_id = $1
AND c.conversation_type = 'group';
`
var conversations []string
rows, err := db.Query(query, userID)
if err != nil {
return []string{}, fmt.Errorf("failed to get user conversations: %w", err)
}
defer rows.Close()
for rows.Next() {
var conversationID string
err = rows.Scan(&conversationID)
if err != nil {
return []string{}, fmt.Errorf("failed to scan conversation: %w", err)
}
conversations = append(conversations, conversationID)
}
if err := rows.Err(); err != nil {
return []string{}, fmt.Errorf("error iterating conversations: %w", err)
}
return conversations, nil
}
func RemoveUserFromGroup(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (string, error) {
removeUserFromGroupQuery := `
DELETE FROM Memberships
WHERE conversation_id = $1 AND user_id = $2;
`
removeAdministratorPermissionsQuery := `
DELETE FROM GroupAdmins
WHERE conversation_id = $1 AND user_id = $2;
`
isOwner, err := IsGroupOwner(db, userID, groupID)
if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to check if user is group owner: %w", err)
}
if !isOwner {
return "Cannot remove group owner", nil
}
res, err := db.Exec(removeUserFromGroupQuery, groupID, userID)
if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to remove user from group: %w", err)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return "User is not a member of the group", nil
}
// If user is an admin, remove admin permissions
_, err = db.Exec(removeAdministratorPermissionsQuery, groupID, userID)
if err != nil {
return "Failed to remove user from group", fmt.Errorf("failed to remove admin permissions: %w", err)
}
return "Successfully removed user from group", nil
}
func IsGroupOwner(db *sql.DB, userID uuid.UUID, groupID uuid.UUID) (bool, error) {
query := `
SELECT EXISTS (
SELECT 1 FROM GroupAdmins
WHERE conversation_id = $1
AND user_id = $2
AND is_owner = true
) AS is_owner;
`
var isOwner bool
err := db.QueryRow(query, groupID, userID).Scan(&isOwner)
if err != nil {
return false, fmt.Errorf("failed to check if user is group owner: %w", err)
}
return isOwner, nil
}
func AddAdministrator(db *sql.DB, userID uuid.UUID, groupID uuid.UUID, grantedBy uuid.UUID) (string, error) {
query := `
INSERT INTO GroupAdmins (conversation_id, user_id, granted_by, is_owner)
VALUES ($1, $2, $3, false)
RETURNING granted_at;
`
isOwner, err := IsGroupOwner(db, grantedBy, groupID)
if err != nil {
return "Failed to add administrator", err
}
if !isOwner {
return "You are not the group owner", nil
}
isMember, err := IsMember(db, userID, groupID)
if err != nil {
return "Failed to add administrator", err
}
if !isMember {
return "User is not a member of the group", nil
}
var grantedAt string
err = db.QueryRow(query, groupID, userID, grantedBy).Scan(&grantedAt)
if err != nil {
return "Failed to add administrator", fmt.Errorf("failed to add administrator: %w", err)
}
return "Successfully added administrator", nil
}