mirror of https://github.com/Squidex/squidex.git
Browse Source
# Conflicts: # src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs # src/Squidex/app/features/content/shared/content-item.component.html # src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html # src/Squidex/app/features/settings/pages/contributors/contributors-page.component.scss # src/Squidex/app/shared/components/saved-queries.component.ts # src/Squidex/app/shared/state/queries.spec.ts # src/Squidex/app/shared/state/ui.state.tspull/419/head
235 changed files with 4681 additions and 3042 deletions
@ -1,72 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsByNameIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IAppsByNameIndex index; |
|||
|
|||
public AppsByNameIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
var createApp = context.Command as CreateApp; |
|||
|
|||
var isReserved = false; |
|||
try |
|||
{ |
|||
if (createApp != null) |
|||
{ |
|||
isReserved = await index.ReserveAppAsync(createApp.AppId, createApp.Name); |
|||
|
|||
if (!isReserved) |
|||
{ |
|||
var error = new ValidationError("An app with the same name already exists.", nameof(createApp.Name)); |
|||
|
|||
throw new ValidationException("Cannot create app.", error); |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
if (createApp != null) |
|||
{ |
|||
await index.AddAppAsync(createApp.AppId, createApp.Name); |
|||
} |
|||
else if (context.Command is ArchiveApp archiveApp) |
|||
{ |
|||
await index.RemoveAppAsync(archiveApp.AppId); |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if (isReserved) |
|||
{ |
|||
await index.RemoveReservationAsync(createApp.AppId, createApp.Name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsByUserIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AppsByUserIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateApp createApp: |
|||
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId); |
|||
break; |
|||
case AssignContributor assignContributor: |
|||
await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId); |
|||
break; |
|||
case RemoveContributor removeContributor: |
|||
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId); |
|||
break; |
|||
case ArchiveApp archiveApp: |
|||
{ |
|||
var appState = await grainFactory.GetGrain<IAppGrain>(archiveApp.AppId).GetStateAsync(); |
|||
|
|||
foreach (var contributorId in appState.Value.Contributors.Keys) |
|||
{ |
|||
await Index(contributorId).RemoveAppAsync(archiveApp.AppId); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private static string GetUserId(CreateApp createApp) |
|||
{ |
|||
return createApp.Actor.Identifier; |
|||
} |
|||
|
|||
private static string GetUserId(AssignContributor assignContributor) |
|||
{ |
|||
return assignContributor.ContributorId; |
|||
} |
|||
|
|||
private static string GetUserId(RemoveContributor removeContributor) |
|||
{ |
|||
return removeContributor.ContributorId; |
|||
} |
|||
|
|||
private IAppsByUserIndex Index(string id) |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByUserIndex>(id); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Orleans; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Infrastructure.Validation; |
|||
using Squidex.Shared; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public sealed class AppsIndex : IAppsIndex, ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AppsIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors) |
|||
{ |
|||
foreach (var contributorId in contributors) |
|||
{ |
|||
await Index(contributorId).AddAsync(appId); |
|||
} |
|||
} |
|||
|
|||
public Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps) |
|||
{ |
|||
return Index(contributorId).RebuildAsync(apps); |
|||
} |
|||
|
|||
public Task RebuildAsync(Dictionary<string, Guid> appsByName) |
|||
{ |
|||
return Index().RebuildAsync(appsByName); |
|||
} |
|||
|
|||
public Task RemoveReservationAsync(string token) |
|||
{ |
|||
return Index().RemoveReservationAsync(token); |
|||
} |
|||
|
|||
public Task<List<Guid>> GetIdsAsync() |
|||
{ |
|||
return Index().GetIdsAsync(); |
|||
} |
|||
|
|||
public Task<bool> AddAsync(string token) |
|||
{ |
|||
return Index().AddAsync(token); |
|||
} |
|||
|
|||
public Task<string> ReserveAsync(Guid id, string name) |
|||
{ |
|||
return Index().ReserveAsync(id, name); |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetAppsAsync() |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var ids = await GetAppIdsAsync(); |
|||
|
|||
var apps = |
|||
await Task.WhenAll(ids |
|||
.Select(id => GetAppAsync(id))); |
|||
|
|||
return apps.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var ids = |
|||
await Task.WhenAll( |
|||
GetAppIdsByUserAsync(userId), |
|||
GetAppIdsAsync(permissions.ToAppNames())); |
|||
|
|||
var apps = |
|||
await Task.WhenAll(ids |
|||
.SelectMany(x => x) |
|||
.Select(id => GetAppAsync(id))); |
|||
|
|||
return apps.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string name) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var appId = await GetAppIdAsync(name); |
|||
|
|||
if (appId == default) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return await GetAppAsync(appId); |
|||
} |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<AppsIndex>()) |
|||
{ |
|||
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
|||
|
|||
if (IsFound(app.Value)) |
|||
{ |
|||
return app.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsByUserAsync(string userId) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByUserIndexGrain>(userId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsAsync() |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetAppIdsAsync(string[] names) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdsAsync(names); |
|||
} |
|||
} |
|||
|
|||
private async Task<Guid> GetAppIdAsync(string name) |
|||
{ |
|||
using (Profiler.TraceMethod<AppProvider>()) |
|||
{ |
|||
return await grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id).GetIdAsync(name); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.Command is CreateApp createApp) |
|||
{ |
|||
var index = Index(); |
|||
|
|||
string token = await CheckAppAsync(index, createApp); |
|||
|
|||
try |
|||
{ |
|||
await next(); |
|||
} |
|||
finally |
|||
{ |
|||
if (token != null) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
await index.AddAsync(token); |
|||
|
|||
if (createApp.Actor.IsSubject) |
|||
{ |
|||
await Index(createApp.Actor.Identifier).AddAsync(createApp.AppId); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await index.RemoveReservationAsync(token); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case AssignContributor assignContributor: |
|||
await AssignContributorAsync(assignContributor); |
|||
break; |
|||
|
|||
case RemoveContributor removeContributor: |
|||
await RemoveContributorAsync(removeContributor); |
|||
break; |
|||
|
|||
case ArchiveApp archiveApp: |
|||
await ArchiveAppAsync(archiveApp); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<string> CheckAppAsync(IAppsByNameIndexGrain index, CreateApp command) |
|||
{ |
|||
var name = command.Name; |
|||
|
|||
if (name.IsSlug()) |
|||
{ |
|||
var token = await index.ReserveAsync(command.AppId, name); |
|||
|
|||
if (token == null) |
|||
{ |
|||
var error = new ValidationError("An app with this already exists."); |
|||
|
|||
throw new ValidationException("Cannot create app.", error); |
|||
} |
|||
|
|||
return token; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private Task AssignContributorAsync(AssignContributor command) |
|||
{ |
|||
return Index(command.ContributorId).AddAsync(command.AppId); |
|||
} |
|||
|
|||
private Task RemoveContributorAsync(RemoveContributor command) |
|||
{ |
|||
return Index(command.ContributorId).RemoveAsync(command.AppId); |
|||
} |
|||
|
|||
private async Task ArchiveAppAsync(ArchiveApp command) |
|||
{ |
|||
var appId = command.AppId; |
|||
|
|||
var app = await grainFactory.GetGrain<IAppGrain>(appId).GetStateAsync(); |
|||
|
|||
if (IsFound(app.Value)) |
|||
{ |
|||
await Index().RemoveAsync(appId); |
|||
} |
|||
|
|||
foreach (var contributorId in app.Value.Contributors.Keys) |
|||
{ |
|||
await Index(contributorId).RemoveAsync(appId); |
|||
} |
|||
} |
|||
|
|||
private static bool IsFound(IAppEntity app) |
|||
{ |
|||
return app.Version > EtagVersion.Empty && !app.IsArchived; |
|||
} |
|||
|
|||
private IAppsByNameIndexGrain Index() |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByNameIndexGrain>(SingleGrain.Id); |
|||
} |
|||
|
|||
private IAppsByUserIndexGrain Index(string id) |
|||
{ |
|||
return grainFactory.GetGrain<IAppsByUserIndexGrain>(id); |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsByNameIndex : IGrainWithStringKey |
|||
{ |
|||
Task<long> CountAsync(); |
|||
|
|||
Task<bool> ReserveAppAsync(Guid appId, string name); |
|||
|
|||
Task AddAppAsync(Guid appId, string name); |
|||
|
|||
Task RemoveAppAsync(Guid appId); |
|||
|
|||
Task RebuildAsync(Dictionary<string, Guid> apps); |
|||
|
|||
Task RemoveReservationAsync(Guid appId, string name); |
|||
|
|||
Task<List<Guid>> GetAppIdsAsync(); |
|||
|
|||
Task<List<Guid>> GetAppIdsAsync(string[] names); |
|||
|
|||
Task<Guid> GetAppIdAsync(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsByUserIndexGrain : IIdsIndexGrain<Guid>, IGrainWithStringKey |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Security; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Indexes |
|||
{ |
|||
public interface IAppsIndex |
|||
{ |
|||
Task<List<Guid>> GetIdsAsync(); |
|||
|
|||
Task<List<IAppEntity>> GetAppsAsync(); |
|||
|
|||
Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions); |
|||
|
|||
Task<IAppEntity> GetAppAsync(string name); |
|||
|
|||
Task<IAppEntity> GetAppAsync(Guid appId); |
|||
|
|||
Task<string> ReserveAsync(Guid id, string name); |
|||
|
|||
Task<bool> AddAsync(string token); |
|||
|
|||
Task RemoveReservationAsync(string token); |
|||
|
|||
Task RebuildByContributorsAsync(string contributorId, HashSet<Guid> apps); |
|||
|
|||
Task RebuildAsync(Dictionary<string, Guid> apps); |
|||
|
|||
Task RebuildByContributorsAsync(Guid appId, HashSet<string> contributors); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public interface IAssetLoader |
|||
{ |
|||
Task<IAssetEntity> GetAsync(Guid id, long version = EtagVersion.Any); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Queries |
|||
{ |
|||
public sealed class AssetLoader : IAssetLoader |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public AssetLoader(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task<IAssetEntity> GetAsync(Guid id, long version) |
|||
{ |
|||
using (Profiler.TraceMethod<AssetLoader>()) |
|||
{ |
|||
var grain = grainFactory.GetGrain<IAssetGrain>(id); |
|||
|
|||
var content = await grain.GetStateAsync(version); |
|||
|
|||
if (content.Value == null || content.Value.Version != version) |
|||
{ |
|||
throw new DomainObjectNotFoundException(id.ToString(), typeof(IAssetEntity)); |
|||
} |
|||
|
|||
return content.Value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public sealed class CommentsLoader : ICommentsLoader |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public CommentsLoader(IGrainFactory grainFactory) |
|||
{ |
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task<CommentsResult> GetCommentsAsync(Guid id, long version = EtagVersion.Any) |
|||
{ |
|||
var grain = grainFactory.GetGrain<ICommentsGrain>(id); |
|||
|
|||
return grain.GetCommentsAsync(version); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Comments |
|||
{ |
|||
public interface ICommentsLoader |
|||
{ |
|||
Task<CommentsResult> GetCommentsAsync(Guid id, long version = EtagVersion.Any); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public interface IRulesByAppIndexGrain : IIdsIndexGrain<Guid>, IGrainWithGuidKey |
|||
{ |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public sealed class RulesByAppIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public RulesByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateRule createRule: |
|||
await Index(createRule.AppId.Id).AddRuleAsync(createRule.RuleId); |
|||
break; |
|||
case DeleteRule deleteRule: |
|||
{ |
|||
var schema = await grainFactory.GetGrain<IRuleGrain>(deleteRule.RuleId).GetStateAsync(); |
|||
|
|||
await Index(schema.Value.AppId.Id).RemoveRuleAsync(deleteRule.RuleId); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private IRulesByAppIndex Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<IRulesByAppIndex>(appId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Rules.Indexes |
|||
{ |
|||
public sealed class RulesIndex : ICommandMiddleware, IRulesIndex |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public RulesIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task RebuildAsync(Guid appId, HashSet<Guid> rues) |
|||
{ |
|||
return Index(appId).RebuildAsync(rues); |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
var ids = await GetRuleIdsAsync(appId); |
|||
|
|||
var rules = |
|||
await Task.WhenAll( |
|||
ids.Select(GetRuleAsync)); |
|||
|
|||
return rules.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
private async Task<IRuleEntity> GetRuleAsync(Guid id) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
var ruleEntity = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(ruleEntity.Value)) |
|||
{ |
|||
return ruleEntity.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetRuleIdsAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<RulesIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateRule createRule: |
|||
await CreateRuleAsync(createRule); |
|||
break; |
|||
case DeleteRule deleteRule: |
|||
await DeleteRuleAsync(deleteRule); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task CreateRuleAsync(CreateRule command) |
|||
{ |
|||
await Index(command.AppId.Id).AddAsync(command.RuleId); |
|||
} |
|||
|
|||
private async Task DeleteRuleAsync(DeleteRule command) |
|||
{ |
|||
var id = command.RuleId; |
|||
|
|||
var rule = await grainFactory.GetGrain<IRuleGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(rule.Value)) |
|||
{ |
|||
await Index(rule.Value.AppId.Id).RemoveAsync(id); |
|||
} |
|||
} |
|||
|
|||
private IRulesByAppIndexGrain Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<IRulesByAppIndexGrain>(appId); |
|||
} |
|||
|
|||
private static bool IsFound(IRuleEntity rule) |
|||
{ |
|||
return rule.Version > EtagVersion.Empty && !rule.IsDeleted; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Orleans.Indexes; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public interface ISchemasByAppIndexGrain : IUniqueNameIndexGrain<Guid>, IGrainWithGuidKey |
|||
{ |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public sealed class SchemasByAppIndexCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public SchemasByAppIndexCommandMiddleware(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
switch (context.Command) |
|||
{ |
|||
case CreateSchema createSchema: |
|||
await Index(createSchema.AppId.Id).AddSchemaAsync(createSchema.SchemaId, createSchema.Name); |
|||
break; |
|||
case DeleteSchema deleteSchema: |
|||
{ |
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(deleteSchema.SchemaId).GetStateAsync(); |
|||
|
|||
await Index(schema.Value.AppId.Id).RemoveSchemaAsync(deleteSchema.SchemaId); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
await next(); |
|||
} |
|||
|
|||
private ISchemasByAppIndex Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<ISchemasByAppIndex>(appId); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,181 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes |
|||
{ |
|||
public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex |
|||
{ |
|||
private readonly IGrainFactory grainFactory; |
|||
|
|||
public SchemasIndex(IGrainFactory grainFactory) |
|||
{ |
|||
Guard.NotNull(grainFactory, nameof(grainFactory)); |
|||
|
|||
this.grainFactory = grainFactory; |
|||
} |
|||
|
|||
public Task RebuildAsync(Guid appId, Dictionary<string, Guid> schemas) |
|||
{ |
|||
return Index(appId).RebuildAsync(schemas); |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var ids = await GetSchemaIdsAsync(appId); |
|||
|
|||
var schemas = |
|||
await Task.WhenAll( |
|||
ids.Select(id => GetSchemaAsync(appId, id, allowDeleted))); |
|||
|
|||
return schemas.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var id = await GetSchemaIdAsync(appId, name); |
|||
|
|||
if (id == default) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return await GetSchemaAsync(appId, id, allowDeleted); |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(id).GetStateAsync(); |
|||
|
|||
if (IsFound(schema.Value, allowDeleted)) |
|||
{ |
|||
return schema.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private async Task<Guid> GetSchemaIdAsync(Guid appId, string name) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdAsync(name); |
|||
} |
|||
} |
|||
|
|||
private async Task<List<Guid>> GetSchemaIdsAsync(Guid appId) |
|||
{ |
|||
using (Profiler.TraceMethod<SchemasIndex>()) |
|||
{ |
|||
return await Index(appId).GetIdsAsync(); |
|||
} |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (context.Command is CreateSchema createSchema) |
|||
{ |
|||
var index = Index(createSchema.AppId.Id); |
|||
|
|||
string token = await CheckSchemaAsync(index, createSchema); |
|||
|
|||
try |
|||
{ |
|||
await next(); |
|||
} |
|||
finally |
|||
{ |
|||
if (token != null) |
|||
{ |
|||
if (context.IsCompleted) |
|||
{ |
|||
await index.AddAsync(token); |
|||
} |
|||
else |
|||
{ |
|||
await index.RemoveReservationAsync(token); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(); |
|||
|
|||
if (context.IsCompleted) |
|||
{ |
|||
if (context.Command is DeleteSchema deleteSchema) |
|||
{ |
|||
await DeleteSchemaAsync(deleteSchema); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task<string> CheckSchemaAsync(ISchemasByAppIndexGrain index, CreateSchema command) |
|||
{ |
|||
var name = command.Name; |
|||
|
|||
if (name.IsSlug()) |
|||
{ |
|||
var token = await index.ReserveAsync(command.SchemaId, name); |
|||
|
|||
if (token == null) |
|||
{ |
|||
var error = new ValidationError("A schema with this name already exists."); |
|||
|
|||
throw new ValidationException("Cannot create schema.", error); |
|||
} |
|||
|
|||
return token; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private async Task DeleteSchemaAsync(DeleteSchema commmand) |
|||
{ |
|||
var schemaId = commmand.SchemaId; |
|||
|
|||
var schema = await grainFactory.GetGrain<ISchemaGrain>(schemaId).GetStateAsync(); |
|||
|
|||
if (IsFound(schema.Value, true)) |
|||
{ |
|||
await Index(schema.Value.AppId.Id).RemoveAsync(schemaId); |
|||
} |
|||
} |
|||
|
|||
private ISchemasByAppIndexGrain Index(Guid appId) |
|||
{ |
|||
return grainFactory.GetGrain<ISchemasByAppIndexGrain>(appId); |
|||
} |
|||
|
|||
private static bool IsFound(ISchemaEntity entity, bool allowDeleted) |
|||
{ |
|||
return entity.Version > EtagVersion.Empty && (!entity.IsDeleted || allowDeleted); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public interface IIdsIndexGrain<T> |
|||
{ |
|||
Task<long> CountAsync(); |
|||
|
|||
Task RebuildAsync(HashSet<T> ids); |
|||
|
|||
Task AddAsync(T id); |
|||
|
|||
Task RemoveAsync(T id); |
|||
|
|||
Task ClearAsync(); |
|||
|
|||
Task<List<T>> GetIdsAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public interface IUniqueNameIndexGrain<T> |
|||
{ |
|||
Task<string> ReserveAsync(T id, string name); |
|||
|
|||
Task<bool> AddAsync(string token); |
|||
|
|||
Task<long> CountAsync(); |
|||
|
|||
Task RemoveReservationAsync(string token); |
|||
|
|||
Task RemoveAsync(T id); |
|||
|
|||
Task RebuildAsync(Dictionary<string, T> values); |
|||
|
|||
Task ClearAsync(); |
|||
|
|||
Task<T> GetIdAsync(string name); |
|||
|
|||
Task<List<T>> GetIdsAsync(string[] names); |
|||
|
|||
Task<List<T>> GetIdsAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class IdsIndexGrain<TState, T> : Grain, IIdsIndexGrain<T> where TState : IdsIndexState<T>, new() |
|||
{ |
|||
private readonly IGrainState<TState> state; |
|||
|
|||
public IdsIndexGrain(IGrainState<TState> state) |
|||
{ |
|||
Guard.NotNull(state, nameof(state)); |
|||
|
|||
this.state = state; |
|||
} |
|||
|
|||
public Task<long> CountAsync() |
|||
{ |
|||
return Task.FromResult<long>(state.Value.Ids.Count); |
|||
} |
|||
|
|||
public Task RebuildAsync(HashSet<T> ids) |
|||
{ |
|||
state.Value = new TState { Ids = ids }; |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task AddAsync(T id) |
|||
{ |
|||
state.Value.Ids.Add(id); |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task RemoveAsync(T id) |
|||
{ |
|||
state.Value.Ids.Remove(id); |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return state.ClearAsync(); |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync() |
|||
{ |
|||
return Task.FromResult(state.Value.Ids.ToList()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class IdsIndexState<T> |
|||
{ |
|||
public HashSet<T> Ids { get; set; } = new HashSet<T>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Orleans; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class UniqueNameIndexGrain<TState, T> : Grain, IUniqueNameIndexGrain<T> where TState : UniqueNameIndexState<T>, new() |
|||
{ |
|||
private readonly Dictionary<string, (string Name, T Id)> reservations = new Dictionary<string, (string Name, T Id)>(); |
|||
private readonly IGrainState<TState> state; |
|||
|
|||
public UniqueNameIndexGrain(IGrainState<TState> state) |
|||
{ |
|||
Guard.NotNull(state, nameof(state)); |
|||
|
|||
this.state = state; |
|||
} |
|||
|
|||
public Task<long> CountAsync() |
|||
{ |
|||
return Task.FromResult<long>(state.Value.Names.Count); |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
reservations.Clear(); |
|||
|
|||
return state.ClearAsync(); |
|||
} |
|||
|
|||
public Task RebuildAsync(Dictionary<string, T> names) |
|||
{ |
|||
state.Value = new TState { Names = names }; |
|||
|
|||
return state.WriteAsync(); |
|||
} |
|||
|
|||
public Task<string> ReserveAsync(T id, string name) |
|||
{ |
|||
string token = default; |
|||
|
|||
if (!IsInUse(name) && !IsReserved(name)) |
|||
{ |
|||
token = RandomHash.Simple(); |
|||
|
|||
reservations.Add(token, (name, id)); |
|||
} |
|||
|
|||
return Task.FromResult(token); |
|||
} |
|||
|
|||
public async Task<bool> AddAsync(string token) |
|||
{ |
|||
if (reservations.TryGetValue(token ?? string.Empty, out var reservation)) |
|||
{ |
|||
state.Value.Names.Add(reservation.Name, reservation.Id); |
|||
|
|||
await state.WriteAsync(); |
|||
|
|||
reservations.Remove(token); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public Task RemoveReservationAsync(string token) |
|||
{ |
|||
reservations.Remove(token ?? string.Empty); |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public async Task RemoveAsync(T id) |
|||
{ |
|||
var name = state.Value.Names.FirstOrDefault(x => Equals(x.Value, id)).Key; |
|||
|
|||
if (name != null) |
|||
{ |
|||
state.Value.Names.Remove(name); |
|||
|
|||
await state.WriteAsync(); |
|||
} |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync(string[] names) |
|||
{ |
|||
var result = new List<T>(); |
|||
|
|||
if (names != null) |
|||
{ |
|||
foreach (var name in names) |
|||
{ |
|||
if (state.Value.Names.TryGetValue(name, out var id)) |
|||
{ |
|||
result.Add(id); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public Task<T> GetIdAsync(string name) |
|||
{ |
|||
state.Value.Names.TryGetValue(name, out var id); |
|||
|
|||
return Task.FromResult(id); |
|||
} |
|||
|
|||
public Task<List<T>> GetIdsAsync() |
|||
{ |
|||
return Task.FromResult(state.Value.Names.Values.ToList()); |
|||
} |
|||
|
|||
private bool IsInUse(string name) |
|||
{ |
|||
return state.Value.Names.ContainsKey(name); |
|||
} |
|||
|
|||
private bool IsReserved(string name) |
|||
{ |
|||
return reservations.Values.Any(x => x.Name == name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Orleans.Indexes |
|||
{ |
|||
public class UniqueNameIndexState<T> |
|||
{ |
|||
public Dictionary<string, T> Names { get; set; } = new Dictionary<string, T>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
// tslint:disable: component-selector
|
|||
|
|||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
|
|||
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal'; |
|||
|
|||
@Component({ |
|||
selector: '[sqxEventConsumer]', |
|||
template: ` |
|||
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0"> |
|||
<td class="auto-auto"> |
|||
<span class="truncate"> |
|||
<i class="faulted-icon icon icon-bug" (click)="error.emit()" [class.hidden]="!eventConsumer.error || eventConsumer.error?.length === 0"></i> |
|||
|
|||
{{eventConsumer.name}} |
|||
</span> |
|||
</td> |
|||
<td class="cell-auto-right"> |
|||
<span>{{eventConsumer.position}}</span> |
|||
</td> |
|||
<td class="cell-actions-lg"> |
|||
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="Reset Event Consumer"> |
|||
<i class="icon icon-reset"></i> |
|||
</button> |
|||
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="Start Event Consumer"> |
|||
<i class="icon icon-play"></i> |
|||
</button> |
|||
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="Stop Event Consumer"> |
|||
<i class="icon icon-pause"></i> |
|||
</button> |
|||
</td> |
|||
</tr> |
|||
<tr class="spacer"></tr>`,
|
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class EventConsumerComponent { |
|||
@Output() |
|||
public error = new EventEmitter(); |
|||
|
|||
@Input('sqxEventConsumer') |
|||
public eventConsumer: EventConsumerDto; |
|||
|
|||
constructor( |
|||
public readonly eventConsumersState: EventConsumersState |
|||
) { |
|||
} |
|||
|
|||
public start() { |
|||
this.eventConsumersState.start(this.eventConsumer); |
|||
} |
|||
|
|||
public stop() { |
|||
this.eventConsumersState.stop(this.eventConsumer); |
|||
} |
|||
|
|||
public reset() { |
|||
this.eventConsumersState.reset(this.eventConsumer); |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
// tslint:disable: component-selector
|
|||
|
|||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
|||
|
|||
import { UserDto, UsersState } from '@app/features/administration/internal'; |
|||
|
|||
@Component({ |
|||
selector: '[sqxUser]', |
|||
template: ` |
|||
<tr [routerLink]="user.id" routerLinkActive="active"> |
|||
<td class="cell-user"> |
|||
<img class="user-picture" title="{{user.displayName}}" [attr.src]="user | sqxUserDtoPicture" /> |
|||
</td> |
|||
<td class="cell-auto"> |
|||
<span class="user-name table-cell">{{user.displayName}}</span> |
|||
</td> |
|||
<td class="cell-auto"> |
|||
<span class="user-email table-cell">{{user.email}}</span> |
|||
</td> |
|||
<td class="cell-actions"> |
|||
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="Lock User"> |
|||
<i class="icon icon-unlocked"></i> |
|||
</button> |
|||
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="Unlock User"> |
|||
<i class="icon icon-lock"></i> |
|||
</button> |
|||
</td> |
|||
</tr> |
|||
<tr class="spacer"></tr>`,
|
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class UserComponent { |
|||
@Input('sqxUser') |
|||
public user: UserDto; |
|||
|
|||
constructor( |
|||
private readonly usersState: UsersState |
|||
) { |
|||
} |
|||
|
|||
public lock() { |
|||
this.usersState.lock(this.user); |
|||
} |
|||
|
|||
public unlock() { |
|||
this.usersState.unlock(this.user); |
|||
} |
|||
} |
|||
@ -1,4 +1,4 @@ |
|||
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title> |
|||
<sqx-title message="GraphQL"></sqx-title> |
|||
|
|||
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
|||
<div inner #graphiQLContainer></div> |
|||
|
|||
@ -1,98 +0,0 @@ |
|||
<td class="cell-select" sqxStopClick> |
|||
<ng-container *ngIf="!isReference; else referenceTemplate"> |
|||
<input type="checkbox" class="form-check" |
|||
[disabled]="!selectable" |
|||
[ngModel]="selected || !selectable" |
|||
(ngModelChange)="selectedChange.emit($event)" /> |
|||
</ng-container> |
|||
|
|||
<ng-template #referenceTemplate> |
|||
<i class="icon-drag2 drag-handle"></i> |
|||
</ng-template> |
|||
</td> |
|||
|
|||
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
|||
<div class="dropdown dropdown-options" *ngIf="content"> |
|||
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
|||
<i class="icon-dots"></i> |
|||
</button> |
|||
|
|||
<ng-container *sqxModal="dropdown;closeAlways:true"> |
|||
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
|||
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
|||
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
|||
</a> |
|||
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
|||
Clone |
|||
</a> |
|||
|
|||
<div class="dropdown-divider"></div> |
|||
|
|||
<a class="dropdown-item dropdown-item-delete" |
|||
(sqxConfirmClick)="emitDelete()" |
|||
confirmTitle="Delete content" |
|||
confirmText="Do you really want to delete the content?"> |
|||
Delete |
|||
</a> |
|||
</div> |
|||
</ng-container> |
|||
</div> |
|||
</td> |
|||
|
|||
<ng-container *ngIf="isDirty"> |
|||
<td class="cell-actions" > |
|||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</td> |
|||
|
|||
<td class="cell-user" > |
|||
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
|||
<i class="icon-checkmark"></i> |
|||
</button> |
|||
</td> |
|||
</ng-container> |
|||
|
|||
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
|||
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
|||
</td> |
|||
|
|||
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
|||
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
|||
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
|||
</ng-container> |
|||
|
|||
<ng-template #displayTemplate> |
|||
<sqx-content-value [value]="values[i]"></sqx-content-value> |
|||
</ng-template> |
|||
</td> |
|||
|
|||
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
|||
<sqx-content-status |
|||
[status]="content.status" |
|||
[statusColor]="content.statusColor" |
|||
[scheduledTo]="content.scheduleJob?.status" |
|||
[scheduledAt]="content.scheduleJob?.dueTime" |
|||
[isPending]="content.isPending"> |
|||
</sqx-content-status> |
|||
|
|||
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
|||
</td> |
|||
|
|||
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
|||
<div class="reference-edit"> |
|||
<button type="button" class="btn btn-text-secondary"> |
|||
<i class="icon-dots"></i> |
|||
</button> |
|||
|
|||
<div class="reference-menu"> |
|||
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
|||
<i class="icon-pencil"></i> |
|||
</a> |
|||
|
|||
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</td> |
|||
@ -0,0 +1,101 @@ |
|||
<tr [routerLink]="link" routerLinkActive="active"> |
|||
<td class="cell-select" sqxStopClick> |
|||
<ng-container *ngIf="!isReference; else referenceTemplate"> |
|||
<input type="checkbox" class="form-check" |
|||
[disabled]="!selectable" |
|||
[ngModel]="selected || !selectable" |
|||
(ngModelChange)="selectedChange.emit($event)" /> |
|||
</ng-container> |
|||
|
|||
<ng-template #referenceTemplate> |
|||
<i class="icon-drag2 drag-handle"></i> |
|||
</ng-template> |
|||
</td> |
|||
|
|||
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
|||
<div class="dropdown dropdown-options" *ngIf="content"> |
|||
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
|||
<i class="icon-dots"></i> |
|||
</button> |
|||
|
|||
<ng-container *sqxModal="dropdown;closeAlways:true"> |
|||
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
|||
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
|||
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
|||
</a> |
|||
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
|||
Clone |
|||
</a> |
|||
|
|||
<div class="dropdown-divider"></div> |
|||
|
|||
<a class="dropdown-item dropdown-item-delete" |
|||
(sqxConfirmClick)="emitDelete()" |
|||
confirmTitle="Delete content" |
|||
confirmText="Do you really want to delete the content?"> |
|||
Delete |
|||
</a> |
|||
</div> |
|||
</ng-container> |
|||
</div> |
|||
</td> |
|||
|
|||
<ng-container *ngIf="isDirty"> |
|||
<td class="cell-actions" > |
|||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</td> |
|||
|
|||
<td class="cell-user" > |
|||
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
|||
<i class="icon-checkmark"></i> |
|||
</button> |
|||
</td> |
|||
</ng-container> |
|||
|
|||
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
|||
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
|||
</td> |
|||
|
|||
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
|||
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
|||
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
|||
</ng-container> |
|||
|
|||
<ng-template #displayTemplate> |
|||
<sqx-content-value [value]="values[i]"></sqx-content-value> |
|||
</ng-template> |
|||
</td> |
|||
|
|||
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
|||
<sqx-content-status |
|||
[status]="content.status" |
|||
[statusColor]="content.statusColor" |
|||
[scheduledTo]="content.scheduleJob?.status" |
|||
[scheduledAt]="content.scheduleJob?.dueTime" |
|||
[isPending]="content.isPending"> |
|||
</sqx-content-status> |
|||
|
|||
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
|||
</td> |
|||
|
|||
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
|||
<div class="reference-edit"> |
|||
<button type="button" class="btn btn-text-secondary"> |
|||
<i class="icon-dots"></i> |
|||
</button> |
|||
|
|||
<div class="reference-menu"> |
|||
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
|||
<i class="icon-pencil"></i> |
|||
</a> |
|||
|
|||
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
|||
<i class="icon-close"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</td> |
|||
</tr> |
|||
<tr class="spacer"></tr> |
|||
@ -0,0 +1,87 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
// tslint:disable: component-selector
|
|||
|
|||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
|||
|
|||
import { |
|||
ActionsDto, |
|||
RuleDto, |
|||
RulesState, |
|||
TriggersDto |
|||
} from '@app/shared'; |
|||
|
|||
@Component({ |
|||
selector: '[sqxRule]', |
|||
template: ` |
|||
<tr> |
|||
<td class="cell-separator"> |
|||
<h3>If</h3> |
|||
</td> |
|||
<td class="cell-auto"> |
|||
<span (click)="editTrigger.emit()"> |
|||
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element> |
|||
</span> |
|||
</td> |
|||
<td class="cell-separator"> |
|||
<h3>then</h3> |
|||
</td> |
|||
<td class="cell-auto"> |
|||
<span (click)="editAction.emit()"> |
|||
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element> |
|||
</span> |
|||
</td> |
|||
<td class="cell-actions"> |
|||
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle()"></sqx-toggle> |
|||
</td> |
|||
<td class="cell-actions"> |
|||
<button type="button" class="btn btn-text-danger" |
|||
[disabled]="!rule.canDelete" |
|||
(sqxConfirmClick)="delete()" |
|||
confirmTitle="Delete rule" |
|||
confirmText="Do you really want to delete the rule?"> |
|||
<i class="icon-bin2"></i> |
|||
</button> |
|||
</td> |
|||
</tr> |
|||
<tr class="spacer"></tr>`,
|
|||
changeDetection: ChangeDetectionStrategy.OnPush |
|||
}) |
|||
export class RuleComponent { |
|||
@Output() |
|||
public editTrigger = new EventEmitter(); |
|||
|
|||
@Output() |
|||
public editAction = new EventEmitter(); |
|||
|
|||
@Input() |
|||
public ruleTriggers: TriggersDto; |
|||
|
|||
@Input() |
|||
public ruleActions: ActionsDto; |
|||
|
|||
@Input('sqxRule') |
|||
public rule: RuleDto; |
|||
|
|||
constructor( |
|||
private readonly rulesState: RulesState |
|||
) { |
|||
} |
|||
|
|||
public delete() { |
|||
this.rulesState.delete(this.rule); |
|||
} |
|||
|
|||
public toggle() { |
|||
if (this.rule.isEnabled) { |
|||
this.rulesState.disable(this.rule); |
|||
} else { |
|||
this.rulesState.enable(this.rule); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue