diff --git a/PepperBot/Application/Interfaces/IHealthMonitor.cs b/PepperBot/Application/Interfaces/IHealthMonitor.cs new file mode 100644 index 0000000..d47cd40 --- /dev/null +++ b/PepperBot/Application/Interfaces/IHealthMonitor.cs @@ -0,0 +1,21 @@ +namespace PepperBot.Application.Interfaces; + +public interface IHealthMonitor +{ + /// + /// Отметить успешный запрос к Pepper, вернувший более одной скидки. + /// + /// Время успешного запроса (UTC). + void ReportSuccess(DateTimeOffset timestamp); + + /// + /// Время последнего успешного запроса или null, если их ещё не было. + /// + DateTimeOffset? LastSuccessfulRequestAt { get; } + + /// + /// Текущее состояние healthcheck. + /// + bool IsHealthy(TimeSpan successTtl); +} + diff --git a/PepperBot/Application/Services/DealService.cs b/PepperBot/Application/Services/DealService.cs index 840ca3b..45103be 100644 --- a/PepperBot/Application/Services/DealService.cs +++ b/PepperBot/Application/Services/DealService.cs @@ -10,23 +10,32 @@ public class DealService private readonly IDealRepository _repository; private readonly ITelegramNotifier _notifier; private readonly ILogger _logger; + private readonly IHealthMonitor _healthMonitor; public DealService( IPepperClient pepperClient, IDealRepository repository, ITelegramNotifier notifier, - ILogger logger) + ILogger logger, + IHealthMonitor healthMonitor) { _pepperClient = pepperClient; _repository = repository; _notifier = notifier; _logger = logger; + _healthMonitor = healthMonitor; } public async Task CheckAndNotifyAsync(CancellationToken cancellationToken) { var deals = await _pepperClient.GetLatestDealsAsync(cancellationToken); + // Хелсчек: успешный запрос, вернувший более одной скидки (пусть и не новой) + if (deals.Count > 1) + { + _healthMonitor.ReportSuccess(DateTimeOffset.UtcNow); + } + foreach (var deal in deals) { if (await _repository.ExistsAsync(deal.Id, cancellationToken)) diff --git a/PepperBot/Infrastructure/Health/PepperHealthMonitor.cs b/PepperBot/Infrastructure/Health/PepperHealthMonitor.cs new file mode 100644 index 0000000..4c8d050 --- /dev/null +++ b/PepperBot/Infrastructure/Health/PepperHealthMonitor.cs @@ -0,0 +1,30 @@ +using System.Threading; +using PepperBot.Application.Interfaces; + +namespace PepperBot.Infrastructure.Health; + +public class PepperHealthMonitor : IHealthMonitor +{ + private DateTimeOffset? _lastSuccessfulRequestAt; + + public DateTimeOffset? LastSuccessfulRequestAt => _lastSuccessfulRequestAt; + + public void ReportSuccess(DateTimeOffset timestamp) + { + // Просто запоминаем последнее успешное время; избыточную синхронизацию не вводим + // так как запись идёт по одному воркеру. + _lastSuccessfulRequestAt = timestamp; + } + + public bool IsHealthy(TimeSpan successTtl) + { + var last = _lastSuccessfulRequestAt; + if (last is null) + { + return false; + } + + return DateTimeOffset.UtcNow - last.Value < successTtl; + } +} + diff --git a/PepperBot/PepperBot.csproj b/PepperBot/PepperBot.csproj index 3534f9f..a52748c 100644 --- a/PepperBot/PepperBot.csproj +++ b/PepperBot/PepperBot.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,6 +7,10 @@ enable + + + + diff --git a/PepperBot/Program.cs b/PepperBot/Program.cs index 5a9173c..aaa6502 100644 --- a/PepperBot/Program.cs +++ b/PepperBot/Program.cs @@ -1,14 +1,16 @@ -using PepperBot; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using PepperBot.Application.Services; +using PepperBot; using PepperBot.Application.Interfaces; +using PepperBot.Application.Services; using PepperBot.Infrastructure.Data; +using PepperBot.Infrastructure.Health; using PepperBot.Infrastructure.Pepper; using PepperBot.Infrastructure.Telegram; -var builder = Host.CreateApplicationBuilder(args); +var builder = WebApplication.CreateBuilder(args); builder.Services.AddLogging(logging => { @@ -19,10 +21,28 @@ builder.Services.AddLogging(logging => builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); -var host = builder.Build(); +var app = builder.Build(); -await host.RunAsync(); +app.MapGet("/health", (IHealthMonitor healthMonitor) => +{ + var successTtl = TimeSpan.FromMinutes(15); + var isHealthy = healthMonitor.IsHealthy(successTtl); + + if (isHealthy) + { + return Results.Ok(new + { + status = "Healthy", + lastSuccessfulRequestAt = healthMonitor.LastSuccessfulRequestAt + }); + } + + return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); +}); + +await app.RunAsync(); diff --git a/docker-compose.yml b/docker-compose.yml index 486921b..db396c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,10 @@ services: environment: - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID} + - ASPNETCORE_URLS=http://0.0.0.0:8080 volumes: - ./data:/app/data + ports: + - "9001:8080" restart: unless-stopped