173 lines
7.8 KiB
C#
173 lines
7.8 KiB
C#
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 - 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 в истории вебхуков была видна ошибка отладки.
|
||
return StatusCode(500, "Внутренняя ошибка обработки вебхука");
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|