112 lines
3.9 KiB
C#
112 lines
3.9 KiB
C#
using Dapper;
|
|
using GmRelay.Shared.Domain;
|
|
using GmRelay.Shared.Platform;
|
|
using Microsoft.Extensions.Logging;
|
|
using Npgsql;
|
|
|
|
namespace GmRelay.Shared.Features.Sessions.ListSessions;
|
|
|
|
internal sealed record DeleteSessionInfoDto(
|
|
string Title,
|
|
Guid BatchId,
|
|
Guid GroupId,
|
|
bool CanManage,
|
|
int? ThreadId,
|
|
bool TopicCreatedByBot);
|
|
|
|
public sealed record DeleteSessionResult(
|
|
bool Success,
|
|
string? ReplyText,
|
|
string? Title,
|
|
Guid? GroupId,
|
|
int? ThreadId,
|
|
bool TopicCreatedByBot,
|
|
int RemainingInTopic);
|
|
|
|
public sealed class DeleteSessionHandler(
|
|
NpgsqlDataSource dataSource)
|
|
{
|
|
public async Task<DeleteSessionResult> HandleAsync(DeleteSessionCommand command, CancellationToken ct)
|
|
{
|
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
|
await using var transaction = await connection.BeginTransactionAsync(ct);
|
|
|
|
// 1. Use the database mutation order before locking the session or linked portfolio cards.
|
|
await connection.ExecuteAsync(
|
|
"SELECT pg_advisory_xact_lock(20260530, 108)",
|
|
transaction: transaction);
|
|
|
|
// 2. Lock the session before any linked portfolio card and verify group manager.
|
|
var session = await connection.QuerySingleOrDefaultAsync<DeleteSessionInfoDto>(
|
|
"""
|
|
SELECT s.title AS Title,
|
|
s.batch_id AS BatchId,
|
|
s.group_id AS GroupId,
|
|
s.thread_id AS ThreadId,
|
|
s.topic_created_by_bot AS TopicCreatedByBot,
|
|
EXISTS (
|
|
SELECT 1
|
|
FROM group_managers gm
|
|
JOIN players p ON p.id = gm.player_id
|
|
WHERE gm.group_id = s.group_id
|
|
AND p.platform = @Platform
|
|
AND p.external_user_id = @ExternalUserId
|
|
) AS CanManage
|
|
FROM sessions s
|
|
WHERE s.id = @SessionId
|
|
FOR UPDATE OF s
|
|
""",
|
|
new { command.SessionId, Platform = command.User.Platform.ToString(), ExternalUserId = command.User.ExternalUserId }, transaction);
|
|
|
|
if (session == null)
|
|
{
|
|
return new DeleteSessionResult(false, "Сессия не найдена.", null, null, null, false, 0);
|
|
}
|
|
|
|
if (!session.CanManage)
|
|
{
|
|
return new DeleteSessionResult(false, "Только owner или co-GM может удалять сессию.", null, null, null, false, 0);
|
|
}
|
|
|
|
// 3. Unpublish a linked portfolio card before its required session link cascades away.
|
|
await connection.ExecuteAsync(
|
|
"""
|
|
UPDATE portfolio_games pg
|
|
SET is_public = false,
|
|
updated_at = now()
|
|
FROM portfolio_game_sessions pgs
|
|
WHERE pgs.portfolio_game_id = pg.id
|
|
AND pgs.session_id = @SessionId
|
|
AND pg.is_public = true
|
|
""",
|
|
new { command.SessionId },
|
|
transaction);
|
|
|
|
// 4. Delete session
|
|
await connection.ExecuteAsync("DELETE FROM sessions WHERE id = @Id", new { Id = command.SessionId }, transaction);
|
|
|
|
var remainingInTopic = session.ThreadId.HasValue
|
|
? await connection.ExecuteScalarAsync<int>(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM sessions
|
|
WHERE group_id = @GroupId
|
|
AND thread_id = @ThreadId
|
|
""",
|
|
new { session.GroupId, ThreadId = session.ThreadId.Value },
|
|
transaction)
|
|
: 0;
|
|
|
|
await transaction.CommitAsync(ct);
|
|
|
|
return new DeleteSessionResult(
|
|
true,
|
|
"Сессия удалена!",
|
|
session.Title,
|
|
session.GroupId,
|
|
session.ThreadId,
|
|
session.TopicCreatedByBot,
|
|
remainingInTopic);
|
|
}
|
|
}
|