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; ` 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 } 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 }