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