109 lines
3.6 KiB
C#
109 lines
3.6 KiB
C#
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Notify.Models;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace StellaOps.Notifier.Worker.Channels;
|
|
|
|
/// <summary>
|
|
/// Channel adapter for Slack webhook delivery.
|
|
/// </summary>
|
|
public sealed class SlackChannelAdapter : INotifyChannelAdapter
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ILogger<SlackChannelAdapter> _logger;
|
|
|
|
public SlackChannelAdapter(HttpClient httpClient, ILogger<SlackChannelAdapter> logger)
|
|
{
|
|
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
public NotifyChannelType ChannelType => NotifyChannelType.Slack;
|
|
|
|
public async Task<ChannelDispatchResult> SendAsync(
|
|
NotifyChannel channel,
|
|
NotifyDeliveryRendered rendered,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(channel);
|
|
ArgumentNullException.ThrowIfNull(rendered);
|
|
|
|
var endpoint = channel.Config?.Endpoint;
|
|
if (string.IsNullOrWhiteSpace(endpoint))
|
|
{
|
|
return ChannelDispatchResult.Fail("Slack webhook URL not configured", shouldRetry: false);
|
|
}
|
|
|
|
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
|
|
{
|
|
return ChannelDispatchResult.Fail($"Invalid Slack webhook URL: {endpoint}", shouldRetry: false);
|
|
}
|
|
|
|
// Build Slack message payload
|
|
var slackPayload = new
|
|
{
|
|
channel = channel.Config?.Target,
|
|
text = rendered.Title,
|
|
blocks = new object[]
|
|
{
|
|
new
|
|
{
|
|
type = "section",
|
|
text = new
|
|
{
|
|
type = "mrkdwn",
|
|
text = rendered.Body
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, uri);
|
|
request.Content = JsonContent.Create(slackPayload, options: new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
|
|
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
|
var statusCode = (int)response.StatusCode;
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
_logger.LogInformation(
|
|
"Slack delivery to channel {Target} succeeded.",
|
|
channel.Config?.Target ?? "(default)");
|
|
return ChannelDispatchResult.Ok(statusCode);
|
|
}
|
|
|
|
var shouldRetry = statusCode >= 500 || statusCode == 429;
|
|
_logger.LogWarning(
|
|
"Slack delivery failed with status {StatusCode}. Retry: {ShouldRetry}.",
|
|
statusCode,
|
|
shouldRetry);
|
|
|
|
return ChannelDispatchResult.Fail(
|
|
$"HTTP {statusCode}",
|
|
shouldRetry: shouldRetry,
|
|
httpStatusCode: statusCode);
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
_logger.LogError(ex, "Slack delivery failed with network error.");
|
|
return ChannelDispatchResult.Fail(ex.Message, shouldRetry: true);
|
|
}
|
|
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch (TaskCanceledException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Slack delivery timed out.");
|
|
return ChannelDispatchResult.Fail("Request timeout", shouldRetry: true);
|
|
}
|
|
}
|
|
}
|