feat(#20): довести RSVP и напоминания до полного набора событий
PR Checks / test-and-build (pull_request) Successful in 3m12s
PR Checks / test-and-build (pull_request) Successful in 3m12s
- Добавлена абстракция ISystemClock + SystemClock / FakeSystemClock для тестируемого scheduling. - Добавлена миграция V014: confirmation_sent_at в sessions. - Обновлен SendConfirmationHandler: записывает confirmation_sent_at. - Обновлен SessionSchedulerService: - выделен ISessionTriggerStore / DbSessionTriggerStore - SQL-запросы используют параметр @Now вместо now() - добавлен публичный TickAsync для тестов - защита от дублей через confirmation_sent_at IS NULL - Обновлен RescheduleVotingDeadlineService: использует ISystemClock. - Добавлены интерфейсы ISendConfirmationHandler, ISendOneHourReminderHandler, ISendJoinLinkHandler для unit-тестируемости. - Добавлены 8 unit-тестов SessionSchedulerService: - все 3 триггера (T-24h, T-1h, T-5min) - идемпотентность при повторном запуске - ошибки handler не падают и не блокируют другие сессии - ошибки store логируются без падения worker-а Bump version -> 1.13.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using Dapper;
|
||||
using GmRelay.Shared.Domain;
|
||||
using Npgsql;
|
||||
|
||||
namespace GmRelay.Bot.Infrastructure.Scheduling;
|
||||
|
||||
public interface ISessionTriggerStore
|
||||
{
|
||||
Task<IReadOnlyList<Guid>> GetSessionsNeedingConfirmationAsync(DateTimeOffset now, CancellationToken ct);
|
||||
Task<IReadOnlyList<Guid>> GetSessionsNeedingOneHourReminderAsync(DateTimeOffset now, CancellationToken ct);
|
||||
Task<IReadOnlyList<Guid>> GetSessionsNeedingJoinLinkAsync(DateTimeOffset now, CancellationToken ct);
|
||||
}
|
||||
|
||||
public sealed class DbSessionTriggerStore(NpgsqlDataSource dataSource) : ISessionTriggerStore
|
||||
{
|
||||
private static readonly TimeSpan ConfirmationLeadTime = TimeSpan.FromHours(24);
|
||||
private static readonly TimeSpan OneHourReminderLeadTime = TimeSpan.FromHours(1);
|
||||
private static readonly TimeSpan JoinLinkLeadTime = TimeSpan.FromMinutes(5);
|
||||
|
||||
public async Task<IReadOnlyList<Guid>> GetSessionsNeedingConfirmationAsync(DateTimeOffset now, CancellationToken ct)
|
||||
{
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<Guid>(
|
||||
"""
|
||||
SELECT id
|
||||
FROM sessions
|
||||
WHERE status = @Planned
|
||||
AND scheduled_at - @LeadTime <= @Now
|
||||
AND confirmation_sent_at IS NULL
|
||||
""",
|
||||
new
|
||||
{
|
||||
Planned = SessionStatus.Planned,
|
||||
LeadTime = ConfirmationLeadTime,
|
||||
Now = now.UtcDateTime
|
||||
});
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Guid>> GetSessionsNeedingOneHourReminderAsync(DateTimeOffset now, CancellationToken ct)
|
||||
{
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<Guid>(
|
||||
"""
|
||||
SELECT id
|
||||
FROM sessions
|
||||
WHERE status IN (@Confirmed, @ConfirmationSent)
|
||||
AND scheduled_at - @LeadTime <= @Now
|
||||
AND one_hour_reminder_processed_at IS NULL
|
||||
""",
|
||||
new
|
||||
{
|
||||
Confirmed = SessionStatus.Confirmed,
|
||||
ConfirmationSent = SessionStatus.ConfirmationSent,
|
||||
LeadTime = OneHourReminderLeadTime,
|
||||
Now = now.UtcDateTime
|
||||
});
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Guid>> GetSessionsNeedingJoinLinkAsync(DateTimeOffset now, CancellationToken ct)
|
||||
{
|
||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||
|
||||
var results = await connection.QueryAsync<Guid>(
|
||||
"""
|
||||
SELECT id
|
||||
FROM sessions
|
||||
WHERE status = @Confirmed
|
||||
AND scheduled_at - @LeadTime <= @Now
|
||||
AND link_message_id IS NULL
|
||||
""",
|
||||
new
|
||||
{
|
||||
Confirmed = SessionStatus.Confirmed,
|
||||
LeadTime = JoinLinkLeadTime,
|
||||
Now = now.UtcDateTime
|
||||
});
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user