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

173 lines
7.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.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.");
return Unauthorized("Отсутствует авторизационный токен вебхука.");
}
if (receivedToken.ToString() != _helpDocumentSettings.WebhookToken)
{
_logger.LogWarning("Запрос вебхука отклонен. Передан невалидный Secret Token.");
return Unauthorized("Неверный токен вебхука.");
}
#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 - 4);
_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 в истории вебхуков была видна ошибка отладки.
return StatusCode(500, "Внутренняя ошибка обработки вебхука");
}
}
}
}