feat: begin working through how we will handle resubscribing and unsubscribing
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services
|
||||
@@ -28,25 +26,7 @@ app.UseHttpsRedirection();
|
||||
|
||||
const string ytCallback = "yt-callback";
|
||||
|
||||
app.MapGet(
|
||||
ytCallback,
|
||||
static (
|
||||
[FromQuery(Name = "hub.mode")] string mode,
|
||||
[FromQuery(Name = "hub.topic")] string topic,
|
||||
[FromQuery(Name = "hub.challenge")] string challenge,
|
||||
[FromServices] IOptions<SubscriptionOptions> subOptions,
|
||||
[FromServices] ILogger<Program> logger
|
||||
) =>
|
||||
{
|
||||
if (topic != subOptions.Value.TopicUrl)
|
||||
{
|
||||
logger.LogInformation("Received verification request for wrong topic: {Topic}", topic);
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Results.Text(challenge);
|
||||
}
|
||||
);
|
||||
app.MapGet(ytCallback, VerifySubscriptionHandler.HandleAsync);
|
||||
|
||||
// TODO: Implement logic to do the following:
|
||||
// - extract video id from notification
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
global using System.ComponentModel.DataAnnotations;
|
||||
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.Extensions.Options;
|
||||
|
||||
global using StevesBot.Webhook;
|
||||
|
||||
@@ -2,13 +2,7 @@ namespace StevesBot.Webhook.YouTube;
|
||||
|
||||
internal interface IPubSubClient
|
||||
{
|
||||
Task SubscribeAsync(
|
||||
string callbackUrl,
|
||||
string topicUrl,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
Task UnsubscribeAsync(
|
||||
Task<bool> SubscribeAsync(
|
||||
string callbackUrl,
|
||||
string topicUrl,
|
||||
CancellationToken cancellationToken = default
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
|
||||
namespace StevesBot.Webhook.YouTube;
|
||||
|
||||
internal sealed class PubSubClient(HttpClient httpClient) : IPubSubClient
|
||||
internal sealed class PubSubClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<PubSubClient> logger
|
||||
) : IPubSubClient
|
||||
{
|
||||
private const string SubscribeEndpoint = "subscribe";
|
||||
private readonly HttpClient _httpClient = httpClient;
|
||||
private readonly ILogger<PubSubClient> _logger = logger;
|
||||
|
||||
public async Task SubscribeAsync(string callbackUrl, string topicUrl, CancellationToken cancellationToken = default)
|
||||
public async Task<bool> SubscribeAsync(string callbackUrl, string topicUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var uri = new Uri(SubscribeEndpoint, UriKind.Relative);
|
||||
var formFields = new Dictionary<string, string>()
|
||||
@@ -21,12 +24,16 @@ internal sealed class PubSubClient(HttpClient httpClient) : IPubSubClient
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PubSubClientException("Failed to subscribe");
|
||||
}
|
||||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Failed to subscribe to topic {TopicUrl} with callback {CallbackUrl}: {ErrorMessage}",
|
||||
topicUrl,
|
||||
callbackUrl,
|
||||
responseContent
|
||||
);
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(string callbackUrl, string topicUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace StevesBot.Webhook.YouTube;
|
||||
|
||||
internal sealed class PubSubClientException : Exception
|
||||
{
|
||||
public PubSubClientException()
|
||||
{
|
||||
}
|
||||
|
||||
public PubSubClientException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PubSubClientException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -1,4 +1,4 @@
|
||||
namespace StevesBot.Webhook;
|
||||
namespace StevesBot.Webhook.YouTube;
|
||||
|
||||
internal sealed class SubscriptionWorker(
|
||||
ILogger<SubscriptionWorker> logger,
|
||||
@@ -24,11 +24,19 @@ internal sealed class SubscriptionWorker(
|
||||
{
|
||||
_logger.LogInformation("Subscribing to notifications");
|
||||
|
||||
await _pubSubClient.SubscribeAsync(
|
||||
var isSubscribed = await _pubSubClient.SubscribeAsync(
|
||||
_options.CallbackUrl,
|
||||
_options.TopicUrl,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
if (isSubscribed)
|
||||
{
|
||||
_logger.LogInformation("Successfully subscribed to notifications");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Failed to subscribe to notifications");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace StevesBot.Webhook.YouTube.Tasks;
|
||||
|
||||
internal sealed record SubscribeTask : SubscriptionTask
|
||||
{
|
||||
public DateTimeOffset ExpiresAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace StevesBot.Webhook.YouTube.Tasks;
|
||||
|
||||
internal abstract record SubscriptionTask
|
||||
{
|
||||
public string CallbackUrl { get; init; } = string.Empty;
|
||||
public string TopicUrl { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace StevesBot.Webhook.YouTube.Tasks;
|
||||
|
||||
internal sealed record UnsubscribeTask : SubscriptionTask
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace StevesBot.Webhook.YouTube;
|
||||
|
||||
internal static class VerifySubscriptionHandler
|
||||
{
|
||||
public static IResult HandleAsync(
|
||||
[FromQuery(Name = "hub.mode")] string mode,
|
||||
[FromQuery(Name = "hub.topic")] string topic,
|
||||
[FromQuery(Name = "hub.reason")] string? reason,
|
||||
[FromQuery(Name = "hub.challenge")] string? challenge,
|
||||
[FromQuery(Name = "hub.lease_seconds")] string? leaseSeconds,
|
||||
[FromServices] IOptions<SubscriptionOptions> subOptions,
|
||||
[FromServices] ILogger<Program> logger
|
||||
)
|
||||
{
|
||||
if (mode is "denied")
|
||||
{
|
||||
logger.LogInformation("Received subscription denial for topic: {Topic}, reason: {Reason}", topic, reason);
|
||||
return Results.BadRequest("Subscription denied");
|
||||
}
|
||||
|
||||
if (topic != subOptions.Value.TopicUrl)
|
||||
{
|
||||
logger.LogInformation("Received verification request for wrong topic: {Topic}", topic);
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
if (mode is "subscribe")
|
||||
{
|
||||
// TODO: If it is a subscription request
|
||||
// we need to queue up a resubscription
|
||||
// request to be executed just before
|
||||
// the hub.lease expires.
|
||||
logger.LogInformation(
|
||||
"Received subscription request for topic: {Topic}, challenge: {Challenge}, lease: {LeaseSeconds}",
|
||||
topic,
|
||||
challenge,
|
||||
leaseSeconds
|
||||
);
|
||||
}
|
||||
|
||||
if (mode is "unsubscribe")
|
||||
{
|
||||
// TODO: If it is a unsubscription request
|
||||
// we need to queue up an ubsubscription
|
||||
// request to be executed immediately
|
||||
logger.LogInformation(
|
||||
"Received unsubscription request for topic: {Topic}, challenge: {Challenge}",
|
||||
topic,
|
||||
challenge
|
||||
);
|
||||
}
|
||||
|
||||
return Results.Text(challenge);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user