Files
parr_api/PARR.API/Controllers/V1/HelpDocumentController.cs

174 lines
7.9 KiB
C#
Raw Permalink Normal View History

using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.Core.Services.HelpDocument.Interfaces;
using PARR.Domain.Common.Roles;
using PARR.Domain.Exceptions;
using PARR.Domain.Settings;
using System.Text.Json.Nodes;
namespace PARR.API.Controllers.V1
{
/// <summary>
/// Контроллер по работе со справочной документацией
/// </summary>
public class HelpDocumentController : BaseApiController
{
private readonly IHelpDocumentService _helpDocumentService;
private readonly IMapper _mapper;
private readonly HelpDocumentSettings _helpDocumentSettings;
private readonly ILogger<HelpDocumentController> _logger;
public HelpDocumentController(
IHelpDocumentService helpDocumentService,
IMapper mapper,
HelpDocumentSettings helpDocumentSettings,
ILogger<HelpDocumentController> logger
)
{
_helpDocumentService = helpDocumentService;
_mapper = mapper;
_helpDocumentSettings = helpDocumentSettings;
_logger = logger;
}
/// <summary>
/// Получить документ справки
/// </summary>
/// <returns></returns>
/// <param name="helpId">Путь к справке (например: ui/billing/invoice-create/vat-calc)</param>
[Authorize(Roles = ParrRoles.Administrator.Role)]
[HttpGet(ApiRoutes.HelpDocument.GetDocument)]
public async Task<IActionResult> GetHelpDocument([FromQuery] string helpId)
{
if (string.IsNullOrWhiteSpace(helpId))
throw new ArgumentNullException(nameof(helpId), "Значение не может быть пустым");
var documnet = await _helpDocumentService.GetHelpDocumentAsync(helpId);
if (documnet == null)
return NotFound();
var response = _mapper.Map<HelpDocumentResponse>(documnet);
return Ok(new Response<HelpDocumentResponse>(response, true));
}
/// <summary>
/// Вебхук от GitLab для автоматического сброса кэша при пуше изменений
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpPost(ApiRoutes.HelpDocument.GitLabWebhook)]
public async Task<IActionResult> GitLabWebhook([FromBody] JsonNode payload)
{
_logger.LogInformation("Получен Webhook запрос от GitLab.");
#region Проверка безопасности
// GitLab передает токен в заголовке X-Gitlab-Token
if (!Request.Headers.TryGetValue("X-Gitlab-Token", out var receivedToken))
{
_logger.LogWarning("Запрос вебхука отклонен. Отсутствует заголовок X-Gitlab-Token.");
throw new UnauthorizedException("Отсутствует авторизационный токен вебхука.");
}
if (receivedToken.ToString() != _helpDocumentSettings.WebhookToken)
{
_logger.LogWarning("Запрос вебхука отклонен. Передан невалидный Secret Token.");
throw new UnauthorizedException("Неверный токен вебхука.");
}
#endregion
_logger.LogDebug("Проверка Secret Token пройдена успешно. Начинается обработка изменений.");
try
{
// Проверяем, что это событие push
var objectKind = payload["object_kind"]?.ToString();
if (objectKind != "push")
{
_logger.LogDebug("Игнорируем вебхук: тип события '{ObjectKind}' не является 'push'.", objectKind);
return Ok(new { message = "Ignored non-push event" });
}
// Извлекаем массив коммитов
var commits = payload["commits"]?.AsArray();
if (commits == null || commits.Count == 0)
{
_logger.LogDebug("В вебхуке отсутствуют коммиты.");
return Ok(new { message = "No commits found" });
}
var filesToInvalidate = new HashSet<string>();
// Пробегаемся по коммитам и собираем измененные/добавленные файлы
foreach (var commit in commits)
{
var modifiedFiles = commit?["modified"]?.AsArray();
if (modifiedFiles != null)
{
foreach (var file in modifiedFiles)
{
var filePath = file?.ToString();
if (filePath != null && filePath.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
{
filesToInvalidate.Add(filePath);
}
}
}
var addedFiles = commit?["added"]?.AsArray();
if (addedFiles != null)
{
foreach (var file in addedFiles)
{
var filePath = file?.ToString();
if (filePath != null && filePath.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
{
filesToInvalidate.Add(filePath);
}
}
}
}
if (filesToInvalidate.Count == 0)
{
_logger.LogDebug("Среди измененных файлов в коммитах нет документов .md. Кэш не изменен.");
return Ok(new { message = "No .md files modified" });
}
_logger.LogInformation("Вебхук зафиксировал изменения в {Count} файлах справок. Начинается сброс кэша...", filesToInvalidate.Count);
// Инвалидируем кэш для каждого измененного файла
foreach (var file in filesToInvalidate)
{
// Превращаем путь "ui/billing/vat-calc.md" обратно в helpId "ui/billing/vat-calc"
var helpId = file.Substring(0, file.Length - 3);
_logger.LogDebug("Сброс кэша по вебхуку для HelpId: {HelpId}", helpId);
await _helpDocumentService.ClearCacheAsync(helpId);
}
_logger.LogInformation("Сброс кэша по вебхуку GitLab успешно завершен.");
return Ok(new { clearedFilesCount = filesToInvalidate.Count });
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка при обработке Webhook от GitLab");
// Возвращаем 200 или 500 в зависимости от политики.
// Обычно возвращают BadRequest/InternalServerError, чтобы в GitLab в истории вебхуков была видна ошибка отладки.
throw new ExternalServiceException("Внутренняя ошибка обработки вебхука");
}
}
}
}