95 lines
5.5 KiB
C#
95 lines
5.5 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
|
||
|
||
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||
|
||
/// <summary>
|
||
/// POST {BaseUrl}/Webhook/Subscribe?listName=…&url=…&notificationUrl=… — просит
|
||
/// портал завести подписку SharePoint на только что созданный список данных.
|
||
///
|
||
/// Параметры уходят строкой запроса, тела у запроса нет — так описана ручка.
|
||
///
|
||
/// Отдельный клиент, а не метод в ITnsuPortalClient: тот про справочник сотрудников
|
||
/// и обёрнут кешем (CachingTnsuPortalClient), а здесь команда, которую кешировать
|
||
/// нельзя ни в каком виде. Авторизация общая с ним — см. TnsuPortalAuthorizer.
|
||
///
|
||
/// Адрес приёмника уведомлений живёт в SharePoint:NotificationUrl — в одном блоке с
|
||
/// остальными настройками SharePoint, а не в своём: подписка относится к спискам,
|
||
/// а не к порталу как таковому.
|
||
///
|
||
/// Ошибки НЕ пробрасываются наружу. Список к этому моменту уже создан, и уронить
|
||
/// провижининг из-за недоступности портала значило бы оставить партиционирование
|
||
/// нерабочим целиком (задача Hangfire ушла бы в повторы, а порог по элементам
|
||
/// продолжал бы срабатывать). Поэтому сбой — Error в лог с полным контекстом
|
||
/// (имя списка и сайт видны, подписку можно завести руками), а провижининг идёт
|
||
/// дальше. Отдельно предупреждаем про 401/403: в фоновой задаче нет входящего
|
||
/// токена, а client_credentials требует заполненных TnsuPortal:ClientSecret/Scope
|
||
/// (в конфигурации они пустые — см. TnsuPortalAuthorizer).
|
||
/// </summary>
|
||
public sealed class HttpSharePointWebhookSubscriber(
|
||
HttpClient httpClient,
|
||
TnsuPortalAuthorizer authorizer,
|
||
IOptions<SharePointOptions> options,
|
||
ILogger<HttpSharePointWebhookSubscriber> logger) : ISharePointWebhookSubscriber
|
||
{
|
||
public async Task<bool> TrySubscribeAsync(string siteUrl, string listTitle, CancellationToken cancellationToken)
|
||
{
|
||
var notificationUrl = options.Value.NotificationUrl;
|
||
if (string.IsNullOrWhiteSpace(notificationUrl))
|
||
{
|
||
// Без адреса приёмника подписка бессмысленна — он же служит выключателем
|
||
// (см. SharePointOptions.NotificationUrl).
|
||
logger.LogInformation(
|
||
"SharePoint:NotificationUrl не задан — список «{ListTitle}» на уведомления не подписан.",
|
||
listTitle);
|
||
return false;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(listTitle))
|
||
{
|
||
// Без имени списка ручке нечего подписывать: у неё нет другого способа
|
||
// его найти (адрес списка она не принимает).
|
||
logger.LogError("Заголовок нового списка пуст — подписка на уведомления невозможна.");
|
||
return false;
|
||
}
|
||
|
||
// Полный адрес сайта целиком («https://tnsukz.sharepoint.com/sites/requests»),
|
||
// а не короткое имя — именно это ждёт ручка.
|
||
var site = siteUrl.TrimEnd('/');
|
||
var url =
|
||
$"/Webhook/Subscribe?listName={Uri.EscapeDataString(listTitle)}" +
|
||
$"&url={Uri.EscapeDataString(site)}" +
|
||
$"¬ificationUrl={Uri.EscapeDataString(notificationUrl)}";
|
||
|
||
try
|
||
{
|
||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||
await authorizer.AuthorizeAsync(request, cancellationToken);
|
||
|
||
using var response = await httpClient.SendAsync(request, cancellationToken);
|
||
if (!response.IsSuccessStatusCode)
|
||
{
|
||
// Тело читаем именно на неуспехе — в нём причина отказа портала.
|
||
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||
logger.LogError(
|
||
"Портал отказал в подписке списка «{ListTitle}» (сайт {Site}): {StatusCode}. Ответ: {Body}",
|
||
listTitle, site, (int)response.StatusCode, body);
|
||
return false;
|
||
}
|
||
|
||
logger.LogInformation(
|
||
"Список «{ListTitle}» (сайт {Site}) подписан на уведомления SharePoint.", listTitle, site);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(
|
||
ex,
|
||
"Не удалось подписать список «{ListTitle}» (сайт {Site}) на уведомления — подпишите вручную.",
|
||
listTitle, site);
|
||
return false;
|
||
}
|
||
}
|
||
}
|