mirror of https://github.com/Squidex/squidex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
8.3 KiB
264 lines
8.3 KiB
// ==========================================================================
|
|
// Squidex Headless CMS
|
|
// ==========================================================================
|
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
// All rights reserved. Licensed under the MIT license.
|
|
// ==========================================================================
|
|
|
|
using Orleans;
|
|
using Squidex.Caching;
|
|
using Squidex.Domain.Apps.Entities.Apps.Commands;
|
|
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
|
|
using Squidex.Domain.Apps.Entities.Apps.Repositories;
|
|
using Squidex.Infrastructure;
|
|
using Squidex.Infrastructure.Commands;
|
|
using Squidex.Infrastructure.Orleans;
|
|
using Squidex.Infrastructure.Security;
|
|
using Squidex.Infrastructure.Translations;
|
|
using Squidex.Infrastructure.Validation;
|
|
using Squidex.Shared;
|
|
|
|
namespace Squidex.Domain.Apps.Entities.Apps.Indexes
|
|
{
|
|
public sealed class AppsIndex : IAppsIndex, ICommandMiddleware
|
|
{
|
|
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
|
|
private readonly IAppRepository appRepository;
|
|
private readonly IGrainFactory grainFactory;
|
|
private readonly IReplicatedCache grainCache;
|
|
|
|
public AppsIndex(IAppRepository appRepository, IGrainFactory grainFactory, IReplicatedCache grainCache)
|
|
{
|
|
this.appRepository = appRepository;
|
|
this.grainFactory = grainFactory;
|
|
this.grainCache = grainCache;
|
|
}
|
|
|
|
public Task RegisterAsync(DomainId id, string name,
|
|
CancellationToken ct = default)
|
|
{
|
|
return Cache().AddAsync(id, name);
|
|
}
|
|
|
|
public Task RemoveReservationAsync(string? token,
|
|
CancellationToken ct = default)
|
|
{
|
|
return Cache().RemoveReservationAsync(token);
|
|
}
|
|
|
|
public async Task<string?> ReserveAsync(DomainId id, string name,
|
|
CancellationToken ct = default)
|
|
{
|
|
return await Cache().ReserveAsync(id, name);
|
|
}
|
|
|
|
public async Task<List<IAppEntity>> GetAppsForUserAsync(string userId, PermissionSet permissions,
|
|
CancellationToken ct = default)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppsForUserAsync"))
|
|
{
|
|
var ids =
|
|
await Task.WhenAll(
|
|
GetAppIdsByUserAsync(userId),
|
|
GetAppIdsAsync(permissions.ToAppNames()));
|
|
|
|
var apps =
|
|
await Task.WhenAll(ids
|
|
.SelectMany(x => x).Distinct()
|
|
.Select(id => GetAppAsync(id, false, ct)));
|
|
|
|
return apps.NotNull().ToList();
|
|
}
|
|
}
|
|
|
|
public async Task<IAppEntity?> GetAppAsync(string name, bool canCache = false,
|
|
CancellationToken ct = default)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppByNameAsync"))
|
|
{
|
|
if (canCache)
|
|
{
|
|
if (grainCache.TryGetValue(GetCacheKey(name), out var v) && v is IAppEntity cacheApp)
|
|
{
|
|
return cacheApp;
|
|
}
|
|
}
|
|
|
|
var appId = await GetAppIdAsync(name);
|
|
|
|
if (appId == DomainId.Empty)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return await GetAppAsync(appId, canCache, ct);
|
|
}
|
|
}
|
|
|
|
public async Task<IAppEntity?> GetAppAsync(DomainId appId, bool canCache = false,
|
|
CancellationToken ct = default)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppAsync"))
|
|
{
|
|
if (canCache)
|
|
{
|
|
if (grainCache.TryGetValue(GetCacheKey(appId), out var cached) && cached is IAppEntity cachedApp)
|
|
{
|
|
return cachedApp;
|
|
}
|
|
}
|
|
|
|
var app = await GetAppCoreAsync(appId);
|
|
|
|
if (app != null)
|
|
{
|
|
await CacheItAsync(app);
|
|
}
|
|
|
|
return app;
|
|
}
|
|
}
|
|
|
|
private async Task<IReadOnlyCollection<DomainId>> GetAppIdsByUserAsync(string userId)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppIdsByUserAsync"))
|
|
{
|
|
var result = await appRepository.QueryIdsAsync(userId);
|
|
|
|
return result.Values;
|
|
}
|
|
}
|
|
|
|
private async Task<IReadOnlyCollection<DomainId>> GetAppIdsAsync(string[] names)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppIdsAsync"))
|
|
{
|
|
var result = await Cache().GetAppIdsAsync(names);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private async Task<DomainId> GetAppIdAsync(string name)
|
|
{
|
|
using (Telemetry.Activities.StartActivity("AppsIndex/GetAppIdAsync"))
|
|
{
|
|
var result = await Cache().GetAppIdsAsync(new[] { name });
|
|
|
|
return result.FirstOrDefault();
|
|
}
|
|
}
|
|
|
|
public async Task HandleAsync(CommandContext context, NextDelegate next)
|
|
{
|
|
var command = context.Command;
|
|
|
|
if (command is CreateApp createApp)
|
|
{
|
|
var cache = Cache();
|
|
|
|
var token = await CheckAppAsync(cache, createApp);
|
|
try
|
|
{
|
|
await next(context);
|
|
}
|
|
finally
|
|
{
|
|
await cache.RemoveReservationAsync(token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await next(context);
|
|
}
|
|
|
|
if (context.IsCompleted)
|
|
{
|
|
switch (command)
|
|
{
|
|
case CreateApp create:
|
|
await OnCreateAsync(create);
|
|
break;
|
|
case DeleteApp delete:
|
|
await OnDeleteAsync(delete);
|
|
break;
|
|
case AppUpdateCommand update:
|
|
await OnUpdateAsync(update);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task<string?> CheckAppAsync(IAppsCacheGrain cache, CreateApp command)
|
|
{
|
|
var token = await cache.ReserveAsync(command.AppId, command.Name);
|
|
|
|
if (token == null)
|
|
{
|
|
throw new ValidationException(T.Get("apps.nameAlreadyExists"));
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
private async Task OnCreateAsync(CreateApp create)
|
|
{
|
|
await InvalidateItAsync(create.AppId, create.Name);
|
|
|
|
await Cache().AddAsync(create.AppId, create.Name);
|
|
}
|
|
|
|
private async Task OnDeleteAsync(DeleteApp delete)
|
|
{
|
|
await InvalidateItAsync(delete.AppId.Id, delete.AppId.Name);
|
|
|
|
await Cache().RemoveAsync(delete.AppId.Id);
|
|
}
|
|
|
|
private async Task OnUpdateAsync(AppUpdateCommand update)
|
|
{
|
|
await InvalidateItAsync(update.AppId.Id, update.AppId.Name);
|
|
}
|
|
|
|
private IAppsCacheGrain Cache()
|
|
{
|
|
return grainFactory.GetGrain<IAppsCacheGrain>(SingleGrain.Id);
|
|
}
|
|
|
|
private async Task<IAppEntity?> GetAppCoreAsync(DomainId id, bool allowArchived = false)
|
|
{
|
|
var app = await grainFactory.GetGrain<IAppGrain>(id.ToString()).GetStateAsync();
|
|
|
|
if (app.Version <= EtagVersion.Empty || (app.IsDeleted && !allowArchived))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return app;
|
|
}
|
|
|
|
private static string GetCacheKey(DomainId id)
|
|
{
|
|
return $"{typeof(AppsIndex)}_Apps_Id_{id}";
|
|
}
|
|
|
|
private static string GetCacheKey(string name)
|
|
{
|
|
return $"{typeof(AppsIndex)}_Apps_Name_{name}";
|
|
}
|
|
|
|
private Task InvalidateItAsync(DomainId id, string name)
|
|
{
|
|
return grainCache.RemoveAsync(
|
|
GetCacheKey(id),
|
|
GetCacheKey(name));
|
|
}
|
|
|
|
private Task CacheItAsync(IAppEntity app)
|
|
{
|
|
return Task.WhenAll(
|
|
grainCache.AddAsync(GetCacheKey(app.Id), app, CacheDuration),
|
|
grainCache.AddAsync(GetCacheKey(app.Name), app, CacheDuration));
|
|
}
|
|
}
|
|
}
|
|
|