mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
118 changed files with 1217 additions and 349 deletions
@ -0,0 +1,13 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps; |
|||
|
|||
public sealed class AppsOptions |
|||
{ |
|||
public bool DeletePermanent { get; set; } |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents; |
|||
|
|||
public sealed class ContentEventDeleter(IContentRepository contentRepository, IEventStore eventStore) : IDeleter |
|||
{ |
|||
public int Order => -1000; |
|||
|
|||
public Task DeleteAppAsync(App app, CancellationToken ct) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public async Task DeleteSchemAsync(App app, Schema schema, |
|||
CancellationToken ct) |
|||
{ |
|||
await foreach (var id in contentRepository.StreamIds(app.Id, schema.Id, SearchScope.All, ct)) |
|||
{ |
|||
var streamFilter = StreamFilter.Prefix($"content-{DomainId.Combine(app.Id, id)}"); |
|||
|
|||
await eventStore.DeleteAsync(streamFilter, ct); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Domain.Apps.Entities.Schemas.DomainObject; |
|||
using Squidex.Domain.Apps.Events.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas; |
|||
|
|||
public sealed class SchemaPermanentDeleter( |
|||
IAppProvider appProvider, |
|||
IEnumerable<IDeleter> deleters, |
|||
IOptions<SchemasOptions> options, |
|||
IDomainObjectFactory factory, |
|||
TypeRegistry typeRegistry) |
|||
: IEventConsumer |
|||
{ |
|||
private readonly IEnumerable<IDeleter> deleters = deleters.OrderBy(x => x.Order).ToList(); |
|||
private readonly SchemasOptions options = options.Value; |
|||
private readonly HashSet<string> consumingTypes = |
|||
[ |
|||
typeRegistry.GetName<IEvent, SchemaDeleted>(), |
|||
]; |
|||
|
|||
public StreamFilter EventsFilter { get; } = StreamFilter.Prefix("schema-"); |
|||
|
|||
public ValueTask<bool> HandlesAsync(StoredEvent @event) |
|||
{ |
|||
return new ValueTask<bool>(consumingTypes.Contains(@event.Data.Type)); |
|||
} |
|||
|
|||
public async Task On(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Headers.Restored()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
switch (@event.Payload) |
|||
{ |
|||
case SchemaDeleted schemaDeleted: |
|||
await OnDeleteAsync(schemaDeleted); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private async Task OnDeleteAsync(SchemaDeleted schemaDeleted) |
|||
{ |
|||
// The user can either remove the app itself or via a global setting for all apps.
|
|||
if (!schemaDeleted.Permanent && !options.DeletePermanent) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem"); |
|||
|
|||
var app = await appProvider.GetAppAsync(schemaDeleted.AppId.Id); |
|||
if (app == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var schema = await GetSchemaAsync(app.Id, schemaDeleted.SchemaId.Id); |
|||
if (schema == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var deleter in deleters) |
|||
{ |
|||
using (Telemetry.Activities.StartActivity(deleter.GetType().Name)) |
|||
{ |
|||
await deleter.DeleteSchemaAsync(app, schema.Snapshot, default); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<SchemaDomainObject?> GetSchemaAsync(DomainId appId, DomainId schemaId) |
|||
{ |
|||
// Bypass our normal resolve process, so that we can also retrieve the deleted schema.
|
|||
var schema = factory.Create<SchemaDomainObject>(DomainId.Combine(appId, schemaId)); |
|||
|
|||
await schema.EnsureLoadedAsync(); |
|||
// If the app does not exist, the version is lower than zero.
|
|||
return schema.Version < 0 ? null : schema; |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas; |
|||
|
|||
public sealed class SchemasOptions |
|||
{ |
|||
public bool DeletePermanent { get; set; } |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models; |
|||
|
|||
[OpenApiRequest] |
|||
public sealed class DeleteAppDto |
|||
{ |
|||
/// <summary>
|
|||
/// True to delete the app permanently.
|
|||
/// </summary>
|
|||
[FromQuery(Name = "permanent")] |
|||
public bool Permanent { get; set; } |
|||
|
|||
public DeleteApp ToCommand() |
|||
{ |
|||
return SimpleMapper.Map(this, new DeleteApp()); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Schemas.Models; |
|||
|
|||
[OpenApiRequest] |
|||
public sealed class DeleteSchemaDto |
|||
{ |
|||
/// <summary>
|
|||
/// True to delete the schema and the contents permanently.
|
|||
/// </summary>
|
|||
[FromQuery(Name = "permanent")] |
|||
public bool Permanent { get; set; } |
|||
|
|||
public DeleteSchema ToCommand() |
|||
{ |
|||
return SimpleMapper.Map(this, new DeleteSchema()); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.TestHelpers; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents; |
|||
|
|||
public class ContentEventDeleterTests : GivenContext |
|||
{ |
|||
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); |
|||
private readonly IEventStore eventStore = A.Fake<IEventStore>(); |
|||
private readonly ContentEventDeleter sut; |
|||
|
|||
public ContentEventDeleterTests() |
|||
{ |
|||
sut = new ContentEventDeleter(contentRepository, eventStore); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_run_at_beginning() |
|||
{ |
|||
var order = sut.Order; |
|||
|
|||
Assert.Equal(-1000, order); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_do_nothing_when_app_deleted() |
|||
{ |
|||
await sut.DeleteAppAsync(App, CancellationToken); |
|||
|
|||
A.CallTo(eventStore) |
|||
.MustNotHaveHappened(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_events_from_streams() |
|||
{ |
|||
var id1 = DomainId.NewGuid(); |
|||
var id2 = DomainId.NewGuid(); |
|||
var ids = new[] { id1, id2 }; |
|||
|
|||
A.CallTo(() => contentRepository.StreamIds(App.Id, Schema.Id, SearchScope.All, CancellationToken)) |
|||
.Returns(ids.ToAsyncEnumerable()); |
|||
|
|||
await sut.DeleteSchemAsync(App, Schema, CancellationToken); |
|||
|
|||
A.CallTo(() => eventStore.DeleteAsync( |
|||
A<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id1}")), |
|||
CancellationToken)) |
|||
.MustHaveHappened(); |
|||
|
|||
A.CallTo(() => eventStore.DeleteAsync( |
|||
A<StreamFilter>.That.Matches(x => x.Prefixes!.Contains($"content-{AppId.Id}--{id2}")), |
|||
CancellationToken)) |
|||
.MustHaveHappened(); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue