diff --git a/src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardDraftRepository.cs b/src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardDraftRepository.cs new file mode 100644 index 0000000..53aa086 --- /dev/null +++ b/src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardDraftRepository.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Dapper; +using Npgsql; + +namespace GmRelay.Shared.Features.Sessions.CreateSession.Wizard; + +public sealed class WizardDraftRepository(NpgsqlDataSource dataSource) +{ + public async Task GetActiveAsync( + long chatId, int? messageThreadId, long ownerTelegramId, CancellationToken ct) + { + const string sql = """ + SELECT id AS Id, + chat_id AS ChatId, + message_thread_id AS MessageThreadId, + owner_telegram_id AS OwnerTelegramId, + step AS Step, + payload::text AS PayloadJson, + draft_message_id AS DraftMessageId, + created_at AS CreatedAt, + updated_at AS UpdatedAt, + expires_at AS ExpiresAt + FROM wizard_drafts + WHERE chat_id = @ChatId + AND (message_thread_id = @ThreadId OR (@ThreadId IS NULL AND message_thread_id IS NULL)) + AND owner_telegram_id = @OwnerId + AND expires_at > NOW() + LIMIT 1 + """; + + await using var connection = await dataSource.OpenConnectionAsync(ct); + return await connection.QuerySingleOrDefaultAsync( + new CommandDefinition(sql, + new { ChatId = chatId, ThreadId = messageThreadId, OwnerId = ownerTelegramId }, + cancellationToken: ct)); + } + + public async Task UpsertAsync(WizardDraft draft, CancellationToken ct) + { + const string sql = """ + INSERT INTO wizard_drafts + (id, chat_id, message_thread_id, owner_telegram_id, step, payload, draft_message_id, created_at, updated_at, expires_at) + VALUES + (@Id, @ChatId, @MessageThreadId, @OwnerTelegramId, @Step, @Payload::jsonb, @DraftMessageId, @CreatedAt, @UpdatedAt, @ExpiresAt) + ON CONFLICT (id) DO UPDATE + SET step = EXCLUDED.step, + payload = EXCLUDED.payload, + draft_message_id = EXCLUDED.draft_message_id, + updated_at = EXCLUDED.updated_at, + expires_at = EXCLUDED.expires_at; + """; + + await using var connection = await dataSource.OpenConnectionAsync(ct); + await connection.ExecuteAsync(new CommandDefinition(sql, draft, cancellationToken: ct)); + } + + public async Task DeleteAsync(Guid id, CancellationToken ct) + { + const string sql = "DELETE FROM wizard_drafts WHERE id = @Id"; + await using var connection = await dataSource.OpenConnectionAsync(ct); + await connection.ExecuteAsync(new CommandDefinition(sql, new { Id = id }, cancellationToken: ct)); + } + + public async Task DeleteExpiredAsync(CancellationToken ct) + { + const string sql = "DELETE FROM wizard_drafts WHERE expires_at <= NOW()"; + await using var connection = await dataSource.OpenConnectionAsync(ct); + return await connection.ExecuteAsync(new CommandDefinition(sql, cancellationToken: ct)); + } +}