Skip to content

Commit

Permalink
Entity Framework subscription management improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
tsutomi committed Oct 27, 2023
1 parent 1dc5d35 commit e4f796b
Show file tree
Hide file tree
Showing 39 changed files with 1,058 additions and 355 deletions.
7 changes: 7 additions & 0 deletions Deveel.Webhooks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Webhooks.XUnit", "te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Webhooks.Receiver.TestApi", "test\Deveel.Webhooks.Receiver.TestApi\Deveel.Webhooks.Receiver.TestApi.csproj", "{4942C858-277D-438D-B822-92055B8E8DF7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Webhooks.DeliveryResultLogging.Tests", "test\Deveel.Webhooks.DeliveryResultLogging.Tests\Deveel.Webhooks.DeliveryResultLogging.Tests.csproj", "{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -201,6 +203,10 @@ Global
{4942C858-277D-438D-B822-92055B8E8DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4942C858-277D-438D-B822-92055B8E8DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4942C858-277D-438D-B822-92055B8E8DF7}.Release|Any CPU.Build.0 = Release|Any CPU
{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -236,6 +242,7 @@ Global
{E669EE13-1CBB-453D-B3B0-5DA3B0B51FE6} = {07F23FF6-2FE1-4072-BF37-9238E3750AA1}
{EBD3DB50-0E90-47C7-9DD8-FBBAC8696CE1} = {07F23FF6-2FE1-4072-BF37-9238E3750AA1}
{4942C858-277D-438D-B822-92055B8E8DF7} = {07F23FF6-2FE1-4072-BF37-9238E3750AA1}
{DDD9A4CB-AA5E-4C0B-87F3-CDC24F6A8DA0} = {07F23FF6-2FE1-4072-BF37-9238E3750AA1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E682A9F5-43D7-4D4C-82EA-953545B8F4DE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Deveel.Repository.EntityFramework" Version="1.2.6" />
<PackageReference Include="Deveel.Repository.EntityFramework" Version="1.2.7-6657417435" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class DbWebhookDeliveryResult : IWebhookDeliveryResult {
/// <inheritdoc/>
public string OperationId { get; set; }

public string? TenantId { get; set; }

IEventInfo IWebhookDeliveryResult.EventInfo => EventInfo;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,15 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace Deveel.Webhooks {
[Table("webhook_subscription_headers")]
public class DbWebhookSubscriptionHeader {
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("id")]
public int Id { get; set; }

[Required, Column("key")]
public string Key { get; set; }

[Required, Column("value")]
public string Value { get; set; }

// [ForeignKey(nameof(SubscriptionId))]
public virtual DbWebhookSubscription? Subscription { get; set; }

[Required, Column("subscription_id")]
public string? SubscriptionId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ public static string GetValueType(object? value) {

return valueType switch {
DbWebhookValueTypes.Boolean => ParseBoolean(value),
DbWebhookValueTypes.Integer => Int64.Parse(value, CultureInfo.InvariantCulture),
DbWebhookValueTypes.Number => Double.Parse(value, CultureInfo.InvariantCulture),
var x when x == DbWebhookValueTypes.Integer && Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i32) => i32,
var x when x == DbWebhookValueTypes.Integer && Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i64) => i64,
var x when x == DbWebhookValueTypes.Number && Double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) => d,
var x when x == DbWebhookValueTypes.Number && Single.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var f) => f,
DbWebhookValueTypes.String => value,
DbWebhookValueTypes.DateTime => DateTime.Parse(value, CultureInfo.InvariantCulture),
_ => throw new NotSupportedException($"The value type '{valueType}' is not supported")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@
// limitations under the License.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Deveel.Webhooks {
/// <summary>
/// An implementation of <see cref="IWebhookDeliveryResultRepository{TResult}"/> that
/// uses an Entity Framework Core <see cref="DbContext"/> to store the
/// delivery results of a webhook of type <see cref="DbWebhookDeliveryResult"/>.
/// </summary>
/// <seealso cref="EntityWebhookDeliveryResultStore{TResult}"/>
public sealed class EntityWebhookDeliveryResultStore : EntityWebhookDeliveryResultStore<DbWebhookDeliveryResult> {
/// <seealso cref="EntityWebhookDeliveryResultRepository{TResult}"/>
public sealed class EntityWebhookDeliveryResultRepository : EntityWebhookDeliveryResultRepository<DbWebhookDeliveryResult> {
/// <inheritdoc/>
public EntityWebhookDeliveryResultStore(WebhookDbContext context) : base(context) {
public EntityWebhookDeliveryResultRepository(WebhookDbContext context, ILogger<EntityWebhookDeliveryResultRepository>? logger = null)
: base(context, logger) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Deveel.Data;

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Deveel.Webhooks {
/// <summary>
Expand All @@ -25,26 +26,18 @@ namespace Deveel.Webhooks {
/// <typeparam name="TResult">
/// The type of delivery result to be stored in the database.
/// </typeparam>
public class EntityWebhookDeliveryResultStore<TResult> : EntityRepository<TResult>,
public class EntityWebhookDeliveryResultRepository<TResult> : EntityRepository<TResult>,
IWebhookDeliveryResultRepository<TResult>
where TResult : DbWebhookDeliveryResult {
private readonly WebhookDbContext context;

/// <summary>
/// Constructs the store with the given <see cref="WebhookDbContext"/>.
/// </summary>
/// <param name="context"></param>
public EntityWebhookDeliveryResultStore(WebhookDbContext context) : base(context) {
this.context = context;
public EntityWebhookDeliveryResultRepository(WebhookDbContext context, ILogger<EntityWebhookDeliveryResultRepository<TResult>>? logger = null)
: base(context, logger) {
}

/// <summary>
/// Gets the set of results stored in the database.
/// </summary>
protected DbSet<TResult> Results => context.Set<TResult>();

protected override DbSet<TResult> Entities => Results;

/// <inheritdoc/>
public async Task<TResult?> FindByWebhookIdAsync(string webhookId, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Deveel.Data;

using Finbuckle.MultiTenant;

using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -48,22 +50,19 @@ internal EntityWebhookStorageBuilder(WebhookSubscriptionBuilder<TSubscription> b
public IServiceCollection Services => builder.Services;

private void AddDefaultStorage() {
Services.TryAddScoped<IWebhookSubscriptionRepository<TSubscription>, EntityWebhookSubscriptionRepository<TSubscription>>();
Services.TryAddScoped<EntityWebhookSubscriptionRepository<TSubscription>>();
Services.AddRepository<EntityWebhookSubscriptionRepository<TSubscription>>();

if (typeof(TSubscription) == typeof(DbWebhookSubscription)) {
Services.TryAddScoped<IWebhookSubscriptionRepository<DbWebhookSubscription>, EntityWebhookSubscriptionRepository>();
Services.TryAddScoped<EntityWebhookSubscriptionRepository<DbWebhookSubscription>>();
Services.AddScoped<EntityWebhookSubscriptionRepository>();
Services.AddRepository<EntityWebhookSubscriptionRepository>();
}

if (ResultType != null && ResultType == typeof(DbWebhookDeliveryResult)) {
Services.TryAddScoped<IWebhookDeliveryResultRepository<DbWebhookDeliveryResult>, EntityWebhookDeliveryResultStore>();
Services.AddScoped<EntityWebhookDeliveryResultStore>();
Services.TryAddScoped<EntityWebhookDeliveryResultStore<DbWebhookDeliveryResult>>();
}
if (ResultType != null) {
var resultStoreType = typeof(EntityWebhookDeliveryResultRepository<>).MakeGenericType(ResultType);
Services.AddRepository(resultStoreType);

Services.TryAddSingleton(typeof(IDbWebhookConverter<>), typeof(DefaultDbWebhookConverter<>));
if (ResultType == typeof(DbWebhookDeliveryResult))
Services.AddRepository<EntityWebhookDeliveryResultRepository>();
}
}

/// <summary>
Expand Down Expand Up @@ -147,17 +146,27 @@ public EntityWebhookStorageBuilder<TSubscription> UseContext(Action<DbContextOpt
/// Registers the given type of storage to be used for
/// storing the webhook subscriptions.
/// </summary>
/// <typeparam name="TStore">
/// <typeparam name="TRepository">
/// The type of the storage to use for storing the webhook subscriptions,
/// that is derived from <see cref="EntityWebhookSubscriptionRepository"/>.
/// </typeparam>
/// <returns>
/// Returns the current instance of the builder for chaining.
/// </returns>
public EntityWebhookStorageBuilder<TSubscription> UseSubscriptionStore<TStore>()
where TStore : EntityWebhookSubscriptionRepository<TSubscription> {
Services.AddScoped<IWebhookSubscriptionRepository<TSubscription>, TStore>();
Services.AddScoped<TStore>();
public EntityWebhookStorageBuilder<TSubscription> UseSubscriptionRepository<TRepository>()
where TRepository : EntityWebhookSubscriptionRepository<TSubscription> {

Services.RemoveAll<IRepository<TSubscription>>();
Services.RemoveAll<IPageableRepository<TSubscription>>();
Services.RemoveAll<IQueryableRepository<TRepository>>();
Services.RemoveAll<IWebhookSubscriptionRepository<TSubscription>>();
Services.RemoveAll<EntityWebhookSubscriptionRepository<TSubscription>>();
Services.RemoveAll<EntityWebhookSubscriptionRepository>();

Services.AddRepository<TRepository>();

if (typeof(EntityWebhookSubscriptionRepository<TSubscription>) != typeof(TRepository))
Services.AddScoped<EntityWebhookSubscriptionRepository<TSubscription>, TRepository>();

return this;
}
Expand All @@ -180,8 +189,7 @@ public EntityWebhookStorageBuilder<TSubscription> UseSubscriptionStore<TStore>()
/// <see cref="DbWebhookDeliveryResult"/>.
/// </exception>
public EntityWebhookStorageBuilder<TSubscription> UseResultType(Type type) {
if (type is null)
throw new ArgumentNullException(nameof(type));
ArgumentNullException.ThrowIfNull(type, nameof(type));

if (!typeof(DbWebhookDeliveryResult).IsAssignableFrom(type))
throw new ArgumentException($"The given type '{type}' is not a valid result type");
Expand All @@ -195,17 +203,16 @@ public EntityWebhookStorageBuilder<TSubscription> UseResult<TResult>()
where TResult : DbWebhookDeliveryResult
=> UseResultType(typeof(TResult));

public EntityWebhookStorageBuilder<TSubscription> UseResultStore(Type storeType) {
public EntityWebhookStorageBuilder<TSubscription> UseResultRepository(Type repositoryType) {
if (ResultType == null)
throw new InvalidOperationException("No result type was specified for the storage");

var resultStoreType = typeof(IWebhookDeliveryResultRepository<>).MakeGenericType(ResultType);

if (!resultStoreType.IsAssignableFrom(storeType))
throw new ArgumentException($"The given type '{storeType}' is not a valid result store");
if (!resultStoreType.IsAssignableFrom(repositoryType))
throw new ArgumentException($"The given type '{repositoryType}' is not a valid result store");

Services.AddScoped(resultStoreType, storeType);
Services.AddScoped(storeType);
Services.AddRepository(resultStoreType);

return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Microsoft.Extensions.Logging;

namespace Deveel.Webhooks {
/// <summary>
/// A default implementation of <see cref="IDbWebhookConverter{TWebhook}"/> that
/// stores a <see cref="DbWebhookSubscription"/> in the database.
/// </summary>
public class EntityWebhookSubscriptionRepository : EntityWebhookSubscriptionRepository<DbWebhookSubscription> {
/// <inheritdoc/>
public EntityWebhookSubscriptionRepository(WebhookDbContext context) : base(context) {
public EntityWebhookSubscriptionRepository(WebhookDbContext context, ILogger<EntityWebhookSubscriptionRepository>? logger = null)
: base(context, logger) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ protected override async Task<TSubscription> OnEntityFoundByKeyAsync(object key,
=> await EnsureLoadedAsync(entity, cancellationToken);

/// <inheritdoc/>
public Task<string> GetDestinationUrlAsync(TSubscription subscription, CancellationToken cancellationToken = default) {
public Task<string?> GetDestinationUrlAsync(TSubscription subscription, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

return Task.FromResult(subscription.DestinationUrl);
return Task.FromResult<string?>(subscription.DestinationUrl);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -119,6 +119,24 @@ public Task SetStatusAsync(TSubscription subscription, WebhookSubscriptionStatus
return Task.CompletedTask;
}

/// <inheritdoc/>
public Task<string?> GetSecretAsync(TSubscription subscription, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

return Task.FromResult(subscription.Secret);
}

/// <inheritdoc/>
public Task SetSecretAsync(TSubscription subscription, string? secret, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

subscription.Secret = secret;

return Task.CompletedTask;
}

/// <inheritdoc/>
public Task<string[]> GetEventTypesAsync(TSubscription subscription, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
Expand Down Expand Up @@ -195,5 +213,45 @@ public Task RemoveHeadersAsync(TSubscription subscription, string[] headerKeys,

return Task.CompletedTask;
}

/// <inheritdoc/>
public Task<IDictionary<string, object>> GetPropertiesAsync(TSubscription subscription, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

var properties = subscription.Properties.ToDictionary(x => x.Key, x => DbWebhookValueConvert.Convert(x.Value, x.ValueType));

return Task.FromResult<IDictionary<string, object>>(properties);
}

/// <inheritdoc/>
public Task AddPropertiesAsync(TSubscription subscription, IDictionary<string, object> properties, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

foreach (var property in properties) {
subscription.Properties.Add(new DbWebhookSubscriptionProperty {
Key = property.Key,
Value = DbWebhookValueConvert.ConvertToString(property.Value),
ValueType = DbWebhookValueConvert.GetValueType(property.Value)
});
}

return Task.CompletedTask;
}

/// <inheritdoc/>
public Task RemovePropertiesAsync(TSubscription subscription, string[] propertyKeys, CancellationToken cancellationToken = default) {
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();

foreach (var propertyKey in propertyKeys) {
var found = subscription.Properties.FirstOrDefault(x => x.Key == propertyKey);
if (found != null)
subscription.Properties.Remove(found);
}

return Task.CompletedTask;
}
}
}
Loading

0 comments on commit e4f796b

Please sign in to comment.