diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs index 29d6b28e9..0dc427439 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets return await assetEnricher.EnrichAsync(assets); } - public async Task> QueryAsync(QueryContext context, Q query) + public async Task> QueryAsync(Context context, Q query) { Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); @@ -91,14 +91,14 @@ namespace Squidex.Domain.Apps.Entities.Assets return ResultList.Create(assets.Total, enriched); } - private async Task> QueryByQueryAsync(QueryContext context, Q query) + private async Task> QueryByQueryAsync(Context context, Q query) { var parsedQuery = ParseQuery(context, query.ODataQuery); return await assetRepository.QueryAsync(context.App.Id, parsedQuery); } - private async Task> QueryByIdsAsync(QueryContext context, Q query) + private async Task> QueryByIdsAsync(Context context, Q query) { var assets = await assetRepository.QueryAsync(context.App.Id, new HashSet(query.Ids)); @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets return assets.SortSet(x => x.Id, ids); } - private Query ParseQuery(QueryContext context, string query) + private Query ParseQuery(Context context, string query) { try { diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs index a186e376c..bec9309a6 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Assets Task> QueryByHashAsync(Guid appId, string hash); - Task> QueryAsync(QueryContext contex, Q query); + Task> QueryAsync(Context contex, Q query); Task FindAssetAsync(Guid id); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs index d744488e9..6f97880e7 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs @@ -21,10 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Contents { private const string DefaultColor = StatusColors.Draft; private readonly IContentWorkflow contentWorkflow; + private readonly IContextProvider contextProvider; - public ContentEnricher(IContentWorkflow contentWorkflow) + public ContentEnricher(IContentWorkflow contentWorkflow, IContextProvider contextProvider) { + Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); + Guard.NotNull(contextProvider, nameof(contextProvider)); + this.contentWorkflow = contentWorkflow; + this.contextProvider = contextProvider; } public async Task EnrichAsync(IContentEntity content) @@ -51,8 +56,12 @@ namespace Squidex.Domain.Apps.Entities.Contents var result = SimpleMapper.Map(content, new ContentEntity()); await ResolveColorAsync(content, result, cache); - await ResolveNextsAsync(content, result); - await ResolveCanUpdateAsync(content, result); + + if (ShouldEnrichWithStatuses()) + { + await ResolveNextsAsync(content, result); + await ResolveCanUpdateAsync(content, result); + } results.Add(result); } @@ -61,6 +70,11 @@ namespace Squidex.Domain.Apps.Entities.Contents } } + private bool ShouldEnrichWithStatuses() + { + return contextProvider.Context.IsFrontendClient || contextProvider.Context.IsResolveFlow(); + } + private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result) { result.CanUpdate = await contentWorkflow.CanUpdateAsync(content); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 88d8907be..0a50552c5 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -78,13 +78,13 @@ namespace Squidex.Domain.Apps.Entities.Contents this.scriptEngine = scriptEngine; } - public async Task FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = -1) + public async Task FindContentAsync(Context context, string schemaIdOrName, Guid id, long version = -1) { Guard.NotNull(context, nameof(context)); var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName); - CheckPermission(context.User, schema); + CheckPermission(context, schema); using (Profiler.TraceMethod()) { @@ -108,13 +108,13 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public async Task> QueryAsync(QueryContext context, string schemaIdOrName, Q query) + public async Task> QueryAsync(Context context, string schemaIdOrName, Q query) { Guard.NotNull(context, nameof(context)); var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName); - CheckPermission(context.User, schema); + CheckPermission(context, schema); using (Profiler.TraceMethod()) { @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public async Task> QueryAsync(QueryContext context, IReadOnlyList ids) + public async Task> QueryAsync(Context context, IReadOnlyList ids) { Guard.NotNull(context, nameof(context)); @@ -148,13 +148,11 @@ namespace Squidex.Domain.Apps.Entities.Contents var contents = await QueryCoreAsync(context, ids); - var permissions = context.User.Permissions(); - foreach (var group in contents.GroupBy(x => x.Schema.Id)) { var schema = group.First().Schema; - if (HasPermission(permissions, schema)) + if (HasPermission(context, schema)) { var enriched = await TransformCoreAsync(context, schema, group.Select(x => x.Content)); @@ -166,21 +164,21 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private async Task> TransformAsync(QueryContext context, ISchemaEntity schema, IResultList contents) + private async Task> TransformAsync(Context context, ISchemaEntity schema, IResultList contents) { var transformed = await TransformCoreAsync(context, schema, contents); return ResultList.Create(contents.Total, transformed); } - private async Task TransformAsync(QueryContext context, ISchemaEntity schema, IContentEntity content) + private async Task TransformAsync(Context context, ISchemaEntity schema, IContentEntity content) { var transformed = await TransformCoreAsync(context, schema, Enumerable.Repeat(content, 1)); return transformed[0]; } - private async Task> TransformCoreAsync(QueryContext context, ISchemaEntity schema, IEnumerable contents) + private async Task> TransformCoreAsync(Context context, ISchemaEntity schema, IEnumerable contents) { using (Profiler.TraceMethod()) { @@ -209,7 +207,7 @@ namespace Squidex.Domain.Apps.Entities.Contents result.Data = result.Data.ConvertName2Name(schema.SchemaDef, converters); } - if (result.DataDraft != null && (context.ApiStatus == StatusForApi.All || context.IsFrontendClient)) + if (result.DataDraft != null && (context.IsUnpublished() || context.IsFrontendClient)) { result.DataDraft = result.DataDraft.ConvertName2Name(schema.SchemaDef, converters); } @@ -225,7 +223,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private IEnumerable GenerateConverters(QueryContext context) + private IEnumerable GenerateConverters(Context context) { if (!context.IsFrontendClient) { @@ -243,19 +241,23 @@ namespace Squidex.Domain.Apps.Entities.Contents { yield return FieldConverters.ResolveFallbackLanguages(context.App.LanguagesConfig); - if (context.Languages?.Any() == true) + var languages = context.Languages(); + + if (languages.Any()) { - yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, context.Languages); + yield return FieldConverters.FilterLanguages(context.App.LanguagesConfig, languages); } - if (context.AssetUrlsToResolve?.Any() == true) + var assetUrls = context.AssetUrls(); + + if (assetUrls.Any() == true) { - yield return FieldConverters.ResolveAssetUrls(context.AssetUrlsToResolve, assetUrlGenerator); + yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), assetUrlGenerator); } } } - private Query ParseQuery(QueryContext context, string query, ISchemaEntity schema) + private Query ParseQuery(Context context, string query, ISchemaEntity schema) { using (Profiler.TraceMethod()) { @@ -292,7 +294,7 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public async Task GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName) + public async Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName) { ISchemaEntity schema = null; @@ -314,29 +316,27 @@ namespace Squidex.Domain.Apps.Entities.Contents return schema; } - private static void CheckPermission(ClaimsPrincipal user, params ISchemaEntity[] schemas) + private static void CheckPermission(Context context, params ISchemaEntity[] schemas) { - var permissions = user.Permissions(); - foreach (var schema in schemas) { - if (!HasPermission(permissions, schema)) + if (!HasPermission(context, schema)) { throw new DomainForbiddenException("You do not have permission for this schema."); } } } - private static bool HasPermission(PermissionSet permissions, ISchemaEntity schema) + private static bool HasPermission(Context context, ISchemaEntity schema) { var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.SchemaDef.Name); - return permissions.Allows(permission); + return context.Permissions.Allows(permission); } - private static Status[] GetStatus(QueryContext context) + private static Status[] GetStatus(Context context) { - if (context.IsFrontendClient || context.ApiStatus == StatusForApi.All) + if (context.IsFrontendClient || context.IsUnpublished()) { return null; } @@ -346,36 +346,36 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private async Task> QueryByQueryAsync(QueryContext context, Q query, ISchemaEntity schema) + private async Task> QueryByQueryAsync(Context context, Q query, ISchemaEntity schema) { var parsedQuery = ParseQuery(context, query.ODataQuery, schema); return await QueryCoreAsync(context, schema, parsedQuery); } - private async Task> QueryByIdsAsync(QueryContext context, Q query, ISchemaEntity schema) + private async Task> QueryByIdsAsync(Context context, Q query, ISchemaEntity schema) { var contents = await QueryCoreAsync(context, schema, query.Ids.ToHashSet()); return contents.SortSet(x => x.Id, query.Ids); } - private Task> QueryCoreAsync(QueryContext context, IReadOnlyList ids) + private Task> QueryCoreAsync(Context context, IReadOnlyList ids) { return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet(ids), WithDraft(context)); } - private Task> QueryCoreAsync(QueryContext context, ISchemaEntity schema, Query query) + private Task> QueryCoreAsync(Context context, ISchemaEntity schema, Query query) { return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context)); } - private Task> QueryCoreAsync(QueryContext context, ISchemaEntity schema, HashSet ids) + private Task> QueryCoreAsync(Context context, ISchemaEntity schema, HashSet ids) { return contentRepository.QueryAsync(context.App, schema, GetStatus(context), ids, WithDraft(context)); } - private Task FindCoreAsync(QueryContext context, Guid id, ISchemaEntity schema) + private Task FindCoreAsync(Context context, Guid id, ISchemaEntity schema) { return contentRepository.FindContentAsync(context.App, schema, GetStatus(context), id, WithDraft(context)); } @@ -385,9 +385,9 @@ namespace Squidex.Domain.Apps.Entities.Contents return contentVersionLoader.LoadAsync(id, version); } - private static bool WithDraft(QueryContext context) + private static bool WithDraft(Context context) { - return context.ApiStatus == StatusForApi.All || context.IsFrontendClient; + return context.IsUnpublished() || context.IsFrontendClient; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs new file mode 100644 index 000000000..78f208424 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContextExtensions.cs @@ -0,0 +1,139 @@ +// ========================================================================== +// 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 Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public static class ContextExtensions + { + private const string HeaderUnpublished = "X-Unpublished"; + private const string HeaderFlatten = "X-Flatten"; + private const string HeaderLanguages = "X-Languages"; + private const string HeaderResolveFlow = "X-ResolveFlow"; + private const string HeaderResolveAssetUrls = "X-Resolve-Urls"; + private static readonly char[] Separators = { ',', ';' }; + + public static bool IsUnpublished(this Context context) + { + return context.Headers.ContainsKey(HeaderUnpublished); + } + + public static Context WithUnpublished(this Context context, bool value = true) + { + if (value) + { + context.Headers[HeaderUnpublished] = "1"; + } + else + { + context.Headers.Remove(HeaderUnpublished); + } + + return context; + } + + public static bool IsFlatten(this Context context) + { + return context.Headers.ContainsKey(HeaderFlatten); + } + + public static Context WithFlatten(this Context context, bool value = true) + { + if (value) + { + context.Headers[HeaderFlatten] = "1"; + } + else + { + context.Headers.Remove(HeaderFlatten); + } + + return context; + } + + public static bool IsResolveFlow(this Context context) + { + return context.Headers.ContainsKey(HeaderResolveFlow); + } + + public static Context WithResolveFlow(this Context context, bool value = true) + { + if (value) + { + context.Headers[HeaderResolveFlow] = "1"; + } + else + { + context.Headers.Remove(HeaderResolveFlow); + } + + return context; + } + + public static IEnumerable AssetUrls(this Context context) + { + if (context.Headers.TryGetValue(HeaderResolveAssetUrls, out var value)) + { + return value.Split(Separators, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToHashSet(); + } + + return Enumerable.Empty(); + } + + public static Context WithAssetUrlsToResolve(this Context context, IEnumerable fieldNames) + { + if (fieldNames?.Any() == true) + { + context.Headers[HeaderResolveAssetUrls] = string.Join(",", fieldNames); + } + else + { + context.Headers.Remove(HeaderResolveAssetUrls); + } + + return context; + } + + public static IEnumerable Languages(this Context context) + { + if (context.Headers.TryGetValue(HeaderResolveAssetUrls, out var value)) + { + var languages = new HashSet(); + + foreach (var iso2Code in value.Split(Separators, StringSplitOptions.RemoveEmptyEntries)) + { + if (Language.TryGetLanguage(iso2Code.Trim(), out var language)) + { + languages.Add(language); + } + } + + return languages; + } + + return Enumerable.Empty(); + } + + public static Context WithLanguages(this Context context, IEnumerable fieldNames) + { + if (fieldNames?.Any() == true) + { + context.Headers[HeaderLanguages] = string.Join(",", fieldNames); + } + else + { + context.Headers.Remove(HeaderLanguages); + } + + return context; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index 1a01b229a..2d3d5e353 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL this.resolver = resolver; } - public async Task<(bool HasError, object Response)> QueryAsync(QueryContext context, params GraphQLQuery[] queries) + public async Task<(bool HasError, object Response)> QueryAsync(Context context, params GraphQLQuery[] queries) { Guard.NotNull(context, nameof(context)); Guard.NotNull(queries, nameof(queries)); @@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return (result.Any(x => x.HasError), result.ToArray(x => x.Response)); } - public async Task<(bool HasError, object Response)> QueryAsync(QueryContext context, GraphQLQuery query) + public async Task<(bool HasError, object Response)> QueryAsync(Context context, GraphQLQuery query) { Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index db7e2859c..3841ac148 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL public ISemanticLog Log { get; } - public GraphQLExecutionContext(QueryContext context, IDependencyResolver resolver) + public GraphQLExecutionContext(Context context, IDependencyResolver resolver) : base(context, resolver.Resolve(), resolver.Resolve()) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs index 693f1fabf..65760a321 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs @@ -11,8 +11,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public interface IGraphQLService { - Task<(bool HasError, object Response)> QueryAsync(QueryContext context, params GraphQLQuery[] queries); + Task<(bool HasError, object Response)> QueryAsync(Context context, params GraphQLQuery[] queries); - Task<(bool HasError, object Response)> QueryAsync(QueryContext context, GraphQLQuery query); + Task<(bool HasError, object Response)> QueryAsync(Context context, GraphQLQuery query); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 11b64a42f..de784f55d 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -17,12 +17,12 @@ namespace Squidex.Domain.Apps.Entities.Contents { int DefaultPageSizeGraphQl { get; } - Task> QueryAsync(QueryContext context, IReadOnlyList ids); + Task> QueryAsync(Context context, IReadOnlyList ids); - Task> QueryAsync(QueryContext context, string schemaIdOrName, Q query); + Task> QueryAsync(Context context, string schemaIdOrName, Q query); - Task FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); + Task FindContentAsync(Context context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); - Task GetSchemaOrThrowAsync(QueryContext context, string schemaIdOrName); + Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs index a40896a94..2c50a0d1e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryExecutionContext.cs @@ -21,9 +21,9 @@ namespace Squidex.Domain.Apps.Entities.Contents private readonly ConcurrentDictionary cachedAssets = new ConcurrentDictionary(); private readonly IContentQueryService contentQuery; private readonly IAssetQueryService assetQuery; - private readonly QueryContext context; + private readonly Context context; - public QueryExecutionContext(QueryContext context, IAssetQueryService assetQuery, IContentQueryService contentQuery) + public QueryExecutionContext(Context context, IAssetQueryService assetQuery, IContentQueryService contentQuery) { Guard.NotNull(assetQuery, nameof(assetQuery)); Guard.NotNull(contentQuery, nameof(contentQuery)); diff --git a/src/Squidex.Domain.Apps.Entities/Context.cs b/src/Squidex.Domain.Apps.Entities/Context.cs new file mode 100644 index 000000000..2dea67ac7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Context.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Security.Claims; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Infrastructure.Security; +using Squidex.Shared.Identity; + +namespace Squidex.Domain.Apps.Entities +{ + public sealed class Context + { + public IDictionary Headers { get; } = new Dictionary(); + + public IAppEntity App { get; set; } + + public ClaimsPrincipal User { get; set; } + + public PermissionSet Permissions + { + get { return User?.Permissions() ?? PermissionSet.Empty; } + } + + public Context() + { + } + + public Context(ClaimsPrincipal user, IAppEntity app) + { + User = user; + + App = app; + } + + public bool IsFrontendClient + { + get { return User != null && User.IsInClient("squidex-frontend"); } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/StatusForApi.cs b/src/Squidex.Domain.Apps.Entities/IContextProvider.cs similarity index 77% rename from src/Squidex.Domain.Apps.Entities/Contents/StatusForApi.cs rename to src/Squidex.Domain.Apps.Entities/IContextProvider.cs index e58b4c098..e6e2c67cf 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/StatusForApi.cs +++ b/src/Squidex.Domain.Apps.Entities/IContextProvider.cs @@ -5,11 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.Contents +namespace Squidex.Domain.Apps.Entities { - public enum StatusForApi + public interface IContextProvider { - PublishedOnly, - All, + Context Context { get; } } } diff --git a/src/Squidex.Domain.Apps.Entities/QueryContext.cs b/src/Squidex.Domain.Apps.Entities/QueryContext.cs deleted file mode 100644 index 094e56e3d..000000000 --- a/src/Squidex.Domain.Apps.Entities/QueryContext.cs +++ /dev/null @@ -1,116 +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.Security.Claims; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Security; - -namespace Squidex.Domain.Apps.Entities -{ - public sealed class QueryContext : Cloneable - { - private static readonly char[] Separators = { ',', ';' }; - - public ClaimsPrincipal User { get; private set; } - - public IAppEntity App { get; private set; } - - public bool Flatten { get; set; } - - public StatusForApi ApiStatus { get; private set; } - - public IReadOnlyCollection AssetUrlsToResolve { get; private set; } - - public IReadOnlyCollection Languages { get; private set; } - - private QueryContext() - { - } - - public static QueryContext Create(IAppEntity app, ClaimsPrincipal user) - { - return new QueryContext { App = app, User = user }; - } - - public QueryContext WithFlatten(bool flatten) - { - return Clone(c => c.Flatten = flatten); - } - - public QueryContext WithUnpublished(bool unpublished) - { - return WithApiStatus(unpublished ? StatusForApi.All : StatusForApi.PublishedOnly); - } - - public QueryContext WithApiStatus(StatusForApi status) - { - return Clone(c => c.ApiStatus = status); - } - - public QueryContext WithAssetUrlsToResolve(IEnumerable fieldNames) - { - if (fieldNames != null) - { - return Clone(c => - { - var fields = new HashSet(StringComparer.OrdinalIgnoreCase); - - c.AssetUrlsToResolve?.Foreach(x => fields.Add(x)); - - foreach (var part in fieldNames) - { - foreach (var fieldName in part.Split(Separators, StringSplitOptions.RemoveEmptyEntries)) - { - fields.Add(fieldName.Trim()); - } - } - - c.AssetUrlsToResolve = fields; - }); - } - - return this; - } - - public QueryContext WithLanguages(IEnumerable languageCodes) - { - if (languageCodes != null) - { - return Clone(c => - { - var languages = new HashSet(); - - c.Languages?.Foreach(x => languages.Add(x)); - - foreach (var part in languageCodes) - { - foreach (var iso2Code in part.Split(Separators, StringSplitOptions.RemoveEmptyEntries)) - { - if (Language.TryGetLanguage(iso2Code.Trim(), out var language)) - { - languages.Add(language); - } - } - } - - c.Languages = languages; - }); - } - - return this; - } - - public bool IsFrontendClient - { - get { return User.IsInClient("squidex-frontend"); } - } - } -} diff --git a/src/Squidex.Web/ApiController.cs b/src/Squidex.Web/ApiController.cs index 2d4a8d517..a7ead50d9 100644 --- a/src/Squidex.Web/ApiController.cs +++ b/src/Squidex.Web/ApiController.cs @@ -8,6 +8,7 @@ using System; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -26,17 +27,22 @@ namespace Squidex.Web { get { - var appFeature = HttpContext.Features.Get(); + var app = HttpContext.Context().App; - if (appFeature == null) + if (app == null) { throw new InvalidOperationException("Not in a app context."); } - return appFeature.App; + return app; } } + protected Context Context + { + get { return HttpContext.Context(); } + } + protected Guid AppId { get { return App.Id; } diff --git a/src/Squidex.Web/ApiPermissionAttribute.cs b/src/Squidex.Web/ApiPermissionAttribute.cs index d515c76ce..e93e1fed2 100644 --- a/src/Squidex.Web/ApiPermissionAttribute.cs +++ b/src/Squidex.Web/ApiPermissionAttribute.cs @@ -36,23 +36,26 @@ namespace Squidex.Web { if (permissionIds.Length > 0) { - var set = context.HttpContext.User.Permissions(); + var permissions = context.HttpContext.Context().Permissions; var hasPermission = false; - foreach (var permissionId in permissionIds) + if (permissions != null) { - var id = permissionId; - - foreach (var routeParam in context.RouteData.Values) + foreach (var permissionId in permissionIds) { - id = id.Replace($"{{{routeParam.Key}}}", routeParam.Value?.ToString()); - } + var id = permissionId; - if (set.Allows(new Permission(id))) - { - hasPermission = true; - break; + foreach (var routeParam in context.RouteData.Values) + { + id = id.Replace($"{{{routeParam.Key}}}", routeParam.Value?.ToString()); + } + + if (permissions.Allows(new Permission(id))) + { + hasPermission = true; + break; + } } } diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 0b7d872e5..394ee4aa7 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -7,7 +7,6 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; @@ -17,20 +16,15 @@ namespace Squidex.Web.CommandMiddlewares { public sealed class EnrichWithAppIdCommandMiddleware : ICommandMiddleware { - private readonly IHttpContextAccessor httpContextAccessor; + private readonly IContextProvider contextProvider; - public EnrichWithAppIdCommandMiddleware(IHttpContextAccessor httpContextAccessor) + public EnrichWithAppIdCommandMiddleware(IContextProvider contextProvider) { - this.httpContextAccessor = httpContextAccessor; + this.contextProvider = contextProvider; } public Task HandleAsync(CommandContext context, Func next) { - if (httpContextAccessor.HttpContext == null) - { - return next(); - } - if (context.Command is IAppCommand appCommand && appCommand.AppId == null) { var appId = GetAppId(); @@ -50,14 +44,14 @@ namespace Squidex.Web.CommandMiddlewares private NamedId GetAppId() { - var appFeature = httpContextAccessor.HttpContext.Features.Get(); + var context = contextProvider.Context; - if (appFeature?.App == null) + if (context.App == null) { throw new InvalidOperationException("Cannot resolve app."); } - return appFeature.App.NamedId(); + return context.App.NamedId(); } } } \ No newline at end of file diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index a64798783..c837b5dd0 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -65,12 +65,7 @@ namespace Squidex.Web.CommandMiddlewares if (appId == null) { - var appFeature = actionContextAccessor.ActionContext.HttpContext.Features.Get(); - - if (appFeature?.App != null) - { - appId = appFeature.App.NamedId(); - } + appId = actionContextAccessor.ActionContext.HttpContext.Context().App?.NamedId(); } if (appId != null) diff --git a/src/Squidex.Web/ContextExtensions.cs b/src/Squidex.Web/ContextExtensions.cs new file mode 100644 index 000000000..7548e3c98 --- /dev/null +++ b/src/Squidex.Web/ContextExtensions.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Entities; + +namespace Squidex.Web +{ + public static class ContextExtensions + { + public static Context Context(this HttpContext httpContext) + { + var context = httpContext.Features.Get(); + + if (context == null) + { + context = new Context { User = httpContext.User }; + + foreach (var header in httpContext.Request.Headers) + { + if (header.Key.StartsWith("X-", System.StringComparison.Ordinal)) + { + context.Headers.Add(header.Key, header.Value.ToString()); + } + } + + httpContext.Features.Set(context); + } + + return context; + } + } +} diff --git a/src/Squidex.Web/ContextProvider.cs b/src/Squidex.Web/ContextProvider.cs new file mode 100644 index 000000000..44f1f0a08 --- /dev/null +++ b/src/Squidex.Web/ContextProvider.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Entities; +using Squidex.Infrastructure; + +namespace Squidex.Web +{ + public sealed class ContextProvider : IContextProvider + { + private readonly IHttpContextAccessor httpContextAccessor; + + public Context Context + { + get { return httpContextAccessor.HttpContext.Context(); } + } + + public ContextProvider(IHttpContextAccessor httpContextAccessor) + { + Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + + this.httpContextAccessor = httpContextAccessor; + } + } +} diff --git a/src/Squidex.Web/EntityCreatedDto.cs b/src/Squidex.Web/EntityCreatedDto.cs index 95738823d..754f33f77 100644 --- a/src/Squidex.Web/EntityCreatedDto.cs +++ b/src/Squidex.Web/EntityCreatedDto.cs @@ -15,7 +15,7 @@ namespace Squidex.Web [Required] [Display(Description = "Id of the created entity.")] public string Id { get; set; } - + [Display(Description = "The new version of the entity.")] public long Version { get; set; } diff --git a/src/Squidex.Web/ErrorDto.cs b/src/Squidex.Web/ErrorDto.cs index 2d3e8f6be..2a8bda377 100644 --- a/src/Squidex.Web/ErrorDto.cs +++ b/src/Squidex.Web/ErrorDto.cs @@ -14,10 +14,10 @@ namespace Squidex.Web [Required] [Display(Description = "Error message.")] public string Message { get; set; } - + [Display(Description = "Detailed error messages.")] public string[] Details { get; set; } - + [Display(Description = "Status code of the http response.")] public int? StatusCode { get; set; } = 400; } diff --git a/src/Squidex.Web/IAppFeature.cs b/src/Squidex.Web/IAppFeature.cs deleted file mode 100644 index a798da598..000000000 --- a/src/Squidex.Web/IAppFeature.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Apps; - -namespace Squidex.Web -{ - public interface IAppFeature - { - IAppEntity App { get; } - } -} diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index ab63f14f8..5001c8838 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -7,34 +7,14 @@ using Microsoft.AspNetCore.Http; using Squidex.Infrastructure.Security; -using Squidex.Shared.Identity; namespace Squidex.Web { public static class PermissionExtensions { - private sealed class PermissionFeature - { - public PermissionSet Permissions { get; } - - public PermissionFeature(PermissionSet permissions) - { - Permissions = permissions; - } - } - public static PermissionSet Permissions(this HttpContext httpContext) { - var feature = httpContext.Features.Get(); - - if (feature == null) - { - feature = new PermissionFeature(httpContext.User.Permissions()); - - httpContext.Features.Set(feature); - } - - return feature.Permissions; + return httpContext.Context().Permissions; } public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) diff --git a/src/Squidex.Web/Pipeline/ApiCostsFilter.cs b/src/Squidex.Web/Pipeline/ApiCostsFilter.cs index 0859a26c3..9f1241dad 100644 --- a/src/Squidex.Web/Pipeline/ApiCostsFilter.cs +++ b/src/Squidex.Web/Pipeline/ApiCostsFilter.cs @@ -47,15 +47,15 @@ namespace Squidex.Web.Pipeline { context.HttpContext.Features.Set(FilterDefinition); - var appFeature = context.HttpContext.Features.Get(); + var app = context.HttpContext.Context().App; - if (appFeature?.App != null && FilterDefinition.Weight > 0) + if (app != null && FilterDefinition.Weight > 0) { - var appId = appFeature.App.Id.ToString(); + var appId = app.Id.ToString(); using (Profiler.Trace("CheckUsage")) { - var plan = appPlansProvider.GetPlanForApp(appFeature.App); + var plan = appPlansProvider.GetPlanForApp(app); var usage = await usageTracker.GetMonthlyCallsAsync(appId, DateTime.Today); diff --git a/src/Squidex.Web/Pipeline/AppResolver.cs b/src/Squidex.Web/Pipeline/AppResolver.cs index 97e4e8f1e..25331c6f7 100644 --- a/src/Squidex.Web/Pipeline/AppResolver.cs +++ b/src/Squidex.Web/Pipeline/AppResolver.cs @@ -23,16 +23,6 @@ namespace Squidex.Web.Pipeline { private readonly IAppProvider appProvider; - public class AppFeature : IAppFeature - { - public IAppEntity App { get; } - - public AppFeature(IAppEntity app) - { - App = app; - } - } - public AppResolver(IAppProvider appProvider) { this.appProvider = appProvider; @@ -76,15 +66,15 @@ namespace Squidex.Web.Pipeline } } - var set = user.Permissions(); + var permissionSet = user.Permissions(); - if (!set.Includes(Permissions.ForApp(Permissions.App, appName))&& !AllowAnonymous(context)) + context.HttpContext.Context().App = app; + + if (!permissionSet.Includes(Permissions.ForApp(Permissions.App, appName)) && !AllowAnonymous(context)) { context.Result = new NotFoundResult(); return; } - - context.HttpContext.Features.Set(new AppFeature(app)); } await next(); diff --git a/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs b/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs index c2e3c7006..bb754447f 100644 --- a/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs +++ b/src/Squidex.Web/Pipeline/RequestLogPerformanceMiddleware.cs @@ -47,9 +47,9 @@ namespace Squidex.Web.Pipeline } } - private static void LogFilters(HttpContext context, IObjectWriter c) + private static void LogFilters(HttpContext httpContext, IObjectWriter c) { - var app = context.Features.Get()?.App; + var app = httpContext.Context().App; if (app != null) { @@ -57,21 +57,21 @@ namespace Squidex.Web.Pipeline c.WriteProperty("appName", app.Name); } - var userId = context.User.OpenIdSubject(); + var userId = httpContext.User.OpenIdSubject(); if (!string.IsNullOrWhiteSpace(userId)) { c.WriteProperty(nameof(userId), userId); } - var clientId = context.User.OpenIdClientId(); + var clientId = httpContext.User.OpenIdClientId(); if (!string.IsNullOrWhiteSpace(clientId)) { c.WriteProperty(nameof(clientId), clientId); } - var costs = context.Features.Get()?.Weight ?? 0; + var costs = httpContext.Features.Get()?.Weight ?? 0; c.WriteProperty(nameof(costs), costs); } diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs index 31602a124..9719dfd1d 100644 --- a/src/Squidex.Web/Resource.cs +++ b/src/Squidex.Web/Resource.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; -using Squidex.Infrastructure; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Squidex.Infrastructure; namespace Squidex.Web { diff --git a/src/Squidex.Web/ResourceLink.cs b/src/Squidex.Web/ResourceLink.cs index 2d2b4c0c5..ef54bfa98 100644 --- a/src/Squidex.Web/ResourceLink.cs +++ b/src/Squidex.Web/ResourceLink.cs @@ -17,7 +17,7 @@ namespace Squidex.Web [Required] [Display(Description = "The link method.")] - public string Method { get; set; } + public string Method { get; set; } [Required] [Display(Description = "Additional data about the link.")] diff --git a/src/Squidex.Web/Squidex.Web.csproj b/src/Squidex.Web/Squidex.Web.csproj index bbe645f13..2c9404cb7 100644 --- a/src/Squidex.Web/Squidex.Web.csproj +++ b/src/Squidex.Web/Squidex.Web.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs index a4bc9280d..8d59cba5f 100644 --- a/src/Squidex.Web/UrlHelperExtensions.cs +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -5,8 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.AspNetCore.Mvc; using System; +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable RECS0108 // Warns about static fields in generic types namespace Squidex.Web { @@ -22,7 +24,7 @@ namespace Squidex.Web var name = typeof(T).Name; - if (name.EndsWith(suffix)) + if (name.EndsWith(suffix, StringComparison.Ordinal)) { name = name.Substring(0, name.Length - suffix.Length); } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 75b9867a7..38faed8f3 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -101,9 +101,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiCosts(1)] public async Task GetAssets(string app, [FromQuery] string ids = null) { - var context = Context(); - - var assets = await assetQuery.QueryAsync(context, Q.Empty.WithODataQuery(Request.QueryString.ToString()).WithIds(ids)); + var assets = await assetQuery.QueryAsync(Context, Q.Empty.WithODataQuery(Request.QueryString.ToString()).WithIds(ids)); var response = AssetsDto.FromAssets(assets, this, app); @@ -304,10 +302,5 @@ namespace Squidex.Areas.Api.Controllers.Assets return assetFile; } - - private QueryContext Context() - { - return QueryContext.Create(App, User); - } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 6885ce6cf..97fca4721 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -62,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(2)] public async Task PostGraphQL(string app, [FromBody] GraphQLQuery query) { - var result = await graphQl.QueryAsync(Context(), query); + var result = await graphQl.QueryAsync(Context, query); if (result.HasError) { @@ -93,7 +93,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(2)] public async Task PostGraphQLBatch(string app, [FromBody] GraphQLQuery[] batch) { - var result = await graphQl.QueryAsync(Context(), batch); + var result = await graphQl.QueryAsync(Context, batch); if (result.HasError) { @@ -124,10 +124,9 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetAllContents(string app, [FromQuery] string ids) { - var context = Context(); - var contents = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids); + var contents = await contentQuery.QueryAsync(Context, Q.Empty.WithIds(ids).Ids); - var response = await ContentsDto.FromContentsAsync(contents, context, this, null, contentWorkflow); + var response = await ContentsDto.FromContentsAsync(contents, Context, this, null, contentWorkflow); if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys) { @@ -159,12 +158,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContents(string app, string name, [FromQuery] string ids = null) { - var context = Context(); - var contents = await contentQuery.QueryAsync(context, name, Q.Empty.WithIds(ids).WithODataQuery(Request.QueryString.ToString())); + var contents = await contentQuery.QueryAsync(Context, name, Q.Empty.WithIds(ids).WithODataQuery(Request.QueryString.ToString())); - var schema = await contentQuery.GetSchemaOrThrowAsync(context, name); + var schema = await contentQuery.GetSchemaOrThrowAsync(Context, name); - var response = await ContentsDto.FromContentsAsync(contents, context, this, schema, contentWorkflow); + var response = await ContentsDto.FromContentsAsync(contents, Context, this, schema, contentWorkflow); if (ShouldProvideSurrogateKeys(response)) { @@ -196,10 +194,9 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContent(string app, string name, Guid id) { - var context = Context(); - var content = await contentQuery.FindContentAsync(context, name, id); + var content = await contentQuery.FindContentAsync(Context, name, id); - var response = ContentDto.FromContent(context, content, this); + var response = ContentDto.FromContent(Context, content, this); if (controllerOptions.Value.EnableSurrogateKeys) { @@ -232,10 +229,9 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContentVersion(string app, string name, Guid id, int version) { - var context = Context(); - var content = await contentQuery.FindContentAsync(context, name, id, version); + var content = await contentQuery.FindContentAsync(Context, name, id, version); - var response = ContentDto.FromContent(context, content, this); + var response = ContentDto.FromContent(Context, content, this); if (controllerOptions.Value.EnableSurrogateKeys) { @@ -269,7 +265,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); if (publish && !this.HasPermission(Helper.StatusPermission(app, name, Status.Published))) { @@ -306,7 +302,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); var command = new UpdateContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft }; @@ -338,7 +334,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); var command = new PatchContent { ContentId = id, Data = request.ToCleaned(), AsDraft = asDraft }; @@ -369,7 +365,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task PutContentStatus(string app, string name, Guid id, ChangeStatusDto request) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); if (!this.HasPermission(Helper.StatusPermission(app, name, Status.Published))) { @@ -404,7 +400,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DiscardDraft(string app, string name, Guid id) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); var command = new DiscardChanges { ContentId = id }; @@ -432,7 +428,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task DeleteContent(string app, string name, Guid id) { - await contentQuery.GetSchemaOrThrowAsync(Context(), name); + await contentQuery.GetSchemaOrThrowAsync(Context, name); var command = new DeleteContent { ContentId = id }; @@ -451,15 +447,6 @@ namespace Squidex.Areas.Api.Controllers.Contents return response; } - private QueryContext Context() - { - return QueryContext.Create(App, User) - .WithAssetUrlsToResolve(Request.Headers["X-Resolve-Urls"]) - .WithFlatten(Request.Headers.ContainsKey("X-Flatten")) - .WithLanguages(Request.Headers["X-Languages"]) - .WithUnpublished(Request.Headers.ContainsKey("X-Unpublished")); - } - private bool ShouldProvideSurrogateKeys(ContentsDto response) { return controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys; diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 26440c83f..f7f20acde 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -84,11 +84,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models /// public long Version { get; set; } - public static ContentDto FromContent(QueryContext context, IEnrichedContentEntity content, ApiController controller) + public static ContentDto FromContent(Context context, IEnrichedContentEntity content, ApiController controller) { var response = SimpleMapper.Map(content, new ContentDto()); - if (context?.Flatten == true) + if (context.IsFlatten()) { response.Data = content.Data?.ToFlatten(); response.DataDraft = content.DataDraft?.ToFlatten(); diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index c81ec7b0e..749e662d1 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models } public static async Task FromContentsAsync(IResultList contents, - QueryContext context, ApiController controller, ISchemaEntity schema, IContentWorkflow contentWorkflow) + Context context, ApiController controller, ISchemaEntity schema, IContentWorkflow contentWorkflow) { var result = new ContentsDto { diff --git a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs index e07d9f23e..3ca2f79fb 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs @@ -16,7 +16,6 @@ using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Security; using Squidex.Shared; -using Squidex.Shared.Identity; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.UI @@ -53,7 +52,7 @@ namespace Squidex.Areas.Api.Controllers.UI MapKey = uiOptions.Map?.GoogleMaps?.Key }; - var canCreateApps = !uiOptions.OnlyAdminsCanCreateApps || User.Permissions().Includes(CreateAppPermission); + var canCreateApps = !uiOptions.OnlyAdminsCanCreateApps || Context.Permissions.Includes(CreateAppPermission); result.CanCreateApps = canCreateApps; diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index ec55d161a..580b214ad 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Config.Domain; +using Squidex.Domain.Apps.Entities; using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; using Squidex.Web; @@ -41,6 +42,9 @@ namespace Squidex.Config.Web services.AddSingletonAs() .AsSelf(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/src/Squidex/app/features/administration/services/event-consumers.service.ts b/src/Squidex/app/features/administration/services/event-consumers.service.ts index 18ebdc7ca..2aa592765 100644 --- a/src/Squidex/app/features/administration/services/event-consumers.service.ts +++ b/src/Squidex/app/features/administration/services/event-consumers.service.ts @@ -62,12 +62,12 @@ export class EventConsumersService { const url = this.apiUrl.buildUrl('/api/event-consumers'); return this.http.get<{ items: any[] } & Resource>(url).pipe( - map(({ items, _links }) => { - const eventConsumers = items.map(item => parseEventConsumer(item)); + map(({ items, _links }) => { + const eventConsumers = items.map(item => parseEventConsumer(item)); - return new EventConsumersDto(eventConsumers, _links); - }), - pretifyError('Failed to load event consumers. Please reload.')); + return new EventConsumersDto(eventConsumers, _links); + }), + pretifyError('Failed to load event consumers. Please reload.')); } public putStart(eventConsumer: Resource): Observable { @@ -76,10 +76,10 @@ export class EventConsumersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - map(body => { - return parseEventConsumer(body); - }), - pretifyError('Failed to start event consumer. Please reload.')); + map(body => { + return parseEventConsumer(body); + }), + pretifyError('Failed to start event consumer. Please reload.')); } public putStop(eventConsumer: Resource): Observable { @@ -88,10 +88,10 @@ export class EventConsumersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - map(body => { - return parseEventConsumer(body); - }), - pretifyError('Failed to stop event consumer. Please reload.')); + map(body => { + return parseEventConsumer(body); + }), + pretifyError('Failed to stop event consumer. Please reload.')); } public putReset(eventConsumer: Resource): Observable { @@ -100,10 +100,10 @@ export class EventConsumersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - map(body => { - return parseEventConsumer(body); - }), - pretifyError('Failed to reset event consumer. Please reload.')); + map(body => { + return parseEventConsumer(body); + }), + pretifyError('Failed to reset event consumer. Please reload.')); } } diff --git a/src/Squidex/app/features/administration/services/users.service.ts b/src/Squidex/app/features/administration/services/users.service.ts index c7975aa9b..3d6cc1a05 100644 --- a/src/Squidex/app/features/administration/services/users.service.ts +++ b/src/Squidex/app/features/administration/services/users.service.ts @@ -73,32 +73,32 @@ export class UsersService { const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`); return this.http.get<{ total: number, items: any[] } & Resource>(url).pipe( - map(({ total, items, _links }) => { - const users = items.map(item => parseUser(item)); + map(({ total, items, _links }) => { + const users = items.map(item => parseUser(item)); - return new UsersDto(total, users, _links); - }), - pretifyError('Failed to load users. Please reload.')); + return new UsersDto(total, users, _links); + }), + pretifyError('Failed to load users. Please reload.')); } public getUser(id: string): Observable { const url = this.apiUrl.buildUrl(`api/user-management/${id}`); return this.http.get(url).pipe( - map(body => { - return parseUser(body); - }), - pretifyError('Failed to load user. Please reload.')); + map(body => { + return parseUser(body); + }), + pretifyError('Failed to load user. Please reload.')); } public postUser(dto: CreateUserDto): Observable { const url = this.apiUrl.buildUrl('api/user-management'); return this.http.post(url, dto).pipe( - map(body => { - return parseUser(body); - }), - pretifyError('Failed to create user. Please reload.')); + map(body => { + return parseUser(body); + }), + pretifyError('Failed to create user. Please reload.')); } public putUser(user: Resource, dto: UpdateUserDto): Observable { @@ -107,10 +107,10 @@ export class UsersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url, { body: dto }).pipe( - map(body => { - return parseUser(body); - }), - pretifyError('Failed to update user. Please reload.')); + map(body => { + return parseUser(body); + }), + pretifyError('Failed to update user. Please reload.')); } public lockUser(user: Resource): Observable { @@ -119,10 +119,10 @@ export class UsersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - map(body => { - return parseUser(body); - }), - pretifyError('Failed to load users. Please retry.')); + map(body => { + return parseUser(body); + }), + pretifyError('Failed to load users. Please retry.')); } public unlockUser(user: Resource): Observable { @@ -131,10 +131,10 @@ export class UsersService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - map(body => { - return parseUser(body); - }), - pretifyError('Failed to load users. Please retry.')); + map(body => { + return parseUser(body); + }), + pretifyError('Failed to load users. Please retry.')); } } diff --git a/src/Squidex/app/shared/services/app-languages.service.ts b/src/Squidex/app/shared/services/app-languages.service.ts index 23a7a486e..eb13fb708 100644 --- a/src/Squidex/app/shared/services/app-languages.service.ts +++ b/src/Squidex/app/shared/services/app-languages.service.ts @@ -74,23 +74,23 @@ export class AppLanguagesService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - return parseLanguages(body); - }), - pretifyError('Failed to load languages. Please reload.')); + mapVersioned(({ body }) => { + return parseLanguages(body); + }), + pretifyError('Failed to load languages. Please reload.')); } public postLanguage(appName: string, dto: AddAppLanguageDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`); return HTTP.postVersioned(this.http, url, dto, version).pipe( - mapVersioned(({ body }) => { - return parseLanguages(body); - }), - tap(() => { - this.analytics.trackEvent('Language', 'Added', appName); - }), - pretifyError('Failed to add language. Please reload.')); + mapVersioned(({ body }) => { + return parseLanguages(body); + }), + tap(() => { + this.analytics.trackEvent('Language', 'Added', appName); + }), + pretifyError('Failed to add language. Please reload.')); } public putLanguage(appName: string, resource: Resource, dto: UpdateAppLanguageDto, version: Version): Observable { @@ -99,13 +99,13 @@ export class AppLanguagesService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( - mapVersioned(({ body }) => { - return parseLanguages(body); - }), - tap(() => { - this.analytics.trackEvent('Language', 'Updated', appName); - }), - pretifyError('Failed to change language. Please reload.')); + mapVersioned(({ body }) => { + return parseLanguages(body); + }), + tap(() => { + this.analytics.trackEvent('Language', 'Updated', appName); + }), + pretifyError('Failed to change language. Please reload.')); } public deleteLanguage(appName: string, resource: Resource, version: Version): Observable { @@ -114,13 +114,13 @@ export class AppLanguagesService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - mapVersioned(({ body }) => { - return parseLanguages(body); - }), - tap(() => { - this.analytics.trackEvent('Language', 'Deleted', appName); - }), - pretifyError('Failed to add language. Please reload.')); + mapVersioned(({ body }) => { + return parseLanguages(body); + }), + tap(() => { + this.analytics.trackEvent('Language', 'Deleted', appName); + }), + pretifyError('Failed to add language. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/apps.service.ts b/src/Squidex/app/shared/services/apps.service.ts index ea2c049b0..878bed2ad 100644 --- a/src/Squidex/app/shared/services/apps.service.ts +++ b/src/Squidex/app/shared/services/apps.service.ts @@ -86,25 +86,25 @@ export class AppsService { const url = this.apiUrl.buildUrl('/api/apps'); return this.http.get(url).pipe( - map(body => { - const apps = body.map(item => parseApp(item)); + map(body => { + const apps = body.map(item => parseApp(item)); - return apps; - }), - pretifyError('Failed to load apps. Please reload.')); + return apps; + }), + pretifyError('Failed to load apps. Please reload.')); } public postApp(dto: CreateAppDto): Observable { const url = this.apiUrl.buildUrl('api/apps'); return this.http.post(url, dto).pipe( - map(body => { - return parseApp(body); - }), - tap(() => { - this.analytics.trackEvent('App', 'Created', dto.name); - }), - pretifyError('Failed to create app. Please reload.')); + map(body => { + return parseApp(body); + }), + tap(() => { + this.analytics.trackEvent('App', 'Created', dto.name); + }), + pretifyError('Failed to create app. Please reload.')); } public deleteApp(resource: Resource): Observable { @@ -113,10 +113,10 @@ export class AppsService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - tap(() => { - this.analytics.trackEvent('App', 'Archived'); - }), - pretifyError('Failed to archive app. Please reload.')); + tap(() => { + this.analytics.trackEvent('App', 'Archived'); + }), + pretifyError('Failed to archive app. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index 8fd9e0fbd..b058e8a2d 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -138,12 +138,12 @@ export class AssetsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`); return this.http.get<{ total: number, items: any[] } & Resource>(url).pipe( - map(({ total, items, _links }) => { - const assets = items.map(item => parseAsset(item)); + map(({ total, items, _links }) => { + const assets = items.map(item => parseAsset(item)); - return new AssetsDto(total, assets, _links); - }), - pretifyError('Failed to load assets. Please reload.')); + return new AssetsDto(total, assets, _links); + }), + pretifyError('Failed to load assets. Please reload.')); } public uploadFile(appName: string, file: File): Observable { @@ -152,45 +152,45 @@ export class AssetsService { const req = new HttpRequest('POST', url, getFormData(file), { reportProgress: true }); return this.http.request(req).pipe( - filter(event => - event.type === HttpEventType.UploadProgress || - event.type === HttpEventType.Response), - map(event => { - if (event.type === HttpEventType.UploadProgress) { - const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0; - - return percentDone; - } else if (Types.is(event, HttpResponse)) { - return parseAsset(event.body); - } else { - throw 'Invalid'; - } - }), - catchError((error: any) => { - if (Types.is(error, HttpErrorResponse) && error.status === 413) { - return throwError(new ErrorDto(413, 'Asset is too big.')); - } else { - return throwError(error); - } - }), - tap(value => { - if (!Types.isNumber(value)) { - this.analytics.trackEvent('Asset', 'Uploaded', appName); - } - }), - pretifyError('Failed to upload asset. Please reload.')); + filter(event => + event.type === HttpEventType.UploadProgress || + event.type === HttpEventType.Response), + map(event => { + if (event.type === HttpEventType.UploadProgress) { + const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0; + + return percentDone; + } else if (Types.is(event, HttpResponse)) { + return parseAsset(event.body); + } else { + throw 'Invalid'; + } + }), + catchError((error: any) => { + if (Types.is(error, HttpErrorResponse) && error.status === 413) { + return throwError(new ErrorDto(413, 'Asset is too big.')); + } else { + return throwError(error); + } + }), + tap(value => { + if (!Types.isNumber(value)) { + this.analytics.trackEvent('Asset', 'Uploaded', appName); + } + }), + pretifyError('Failed to upload asset. Please reload.')); } public getAsset(appName: string, id: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`); return HTTP.getVersioned(this.http, url).pipe( - map(({ payload }) => { - const body = payload.body; + map(({ payload }) => { + const body = payload.body; - return parseAsset(body); - }), - pretifyError('Failed to load assets. Please reload.')); + return parseAsset(body); + }), + pretifyError('Failed to load assets. Please reload.')); } public replaceFile(appName: string, asset: Resource, file: File, version: Version): Observable { @@ -201,33 +201,33 @@ export class AssetsService { const req = new HttpRequest(link.method, url, getFormData(file), { headers: new HttpHeaders().set('If-Match', version.value), reportProgress: true }); return this.http.request(req).pipe( - filter(event => - event.type === HttpEventType.UploadProgress || - event.type === HttpEventType.Response), - map(event => { - if (event.type === HttpEventType.UploadProgress) { - const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0; - - return percentDone; - } else if (Types.is(event, HttpResponse)) { - return parseAsset(event.body); - } else { - throw 'Invalid'; - } - }), - catchError(error => { - if (Types.is(error, HttpErrorResponse) && error.status === 413) { - return throwError(new ErrorDto(413, 'Asset is too big.')); - } else { - return throwError(error); - } - }), - tap(value => { - if (!Types.isNumber(value)) { - this.analytics.trackEvent('Analytics', 'Replaced', appName); - } - }), - pretifyError('Failed to replace asset. Please reload.')); + filter(event => + event.type === HttpEventType.UploadProgress || + event.type === HttpEventType.Response), + map(event => { + if (event.type === HttpEventType.UploadProgress) { + const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0; + + return percentDone; + } else if (Types.is(event, HttpResponse)) { + return parseAsset(event.body); + } else { + throw 'Invalid'; + } + }), + catchError(error => { + if (Types.is(error, HttpErrorResponse) && error.status === 413) { + return throwError(new ErrorDto(413, 'Asset is too big.')); + } else { + return throwError(error); + } + }), + tap(value => { + if (!Types.isNumber(value)) { + this.analytics.trackEvent('Analytics', 'Replaced', appName); + } + }), + pretifyError('Failed to replace asset. Please reload.')); } public putAsset(appName: string, asset: Resource, dto: AnnotateAssetDto, version: Version): Observable { @@ -236,13 +236,13 @@ export class AssetsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( - map(({ payload }) => { - return parseAsset(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Analytics', 'Updated', appName); - }), - pretifyError('Failed to update asset. Please reload.')); + map(({ payload }) => { + return parseAsset(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Analytics', 'Updated', appName); + }), + pretifyError('Failed to update asset. Please reload.')); } public deleteAsset(appName: string, asset: Resource, version: Version): Observable> { @@ -251,10 +251,10 @@ export class AssetsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - tap(() => { - this.analytics.trackEvent('Analytics', 'Deleted', appName); - }), - pretifyError('Failed to delete asset. Please reload.')); + tap(() => { + this.analytics.trackEvent('Analytics', 'Deleted', appName); + }), + pretifyError('Failed to delete asset. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/auth.service.ts b/src/Squidex/app/shared/services/auth.service.ts index d168850a4..ea05e730a 100644 --- a/src/Squidex/app/shared/services/auth.service.ts +++ b/src/Squidex/app/shared/services/auth.service.ts @@ -75,15 +75,15 @@ export class AuthService { Log.logger = console; this.userManager = new UserManager({ - client_id: 'squidex-frontend', - scope: 'squidex-api openid profile email squidex-profile role permissions', - response_type: 'id_token token', - redirect_uri: apiUrl.buildUrl('login;'), - post_logout_redirect_uri: apiUrl.buildUrl('logout'), - silent_redirect_uri: apiUrl.buildUrl('client-callback-silent'), - popup_redirect_uri: apiUrl.buildUrl('client-callback-popup'), - authority: apiUrl.buildUrl('identity-server/'), - userStore: new WebStorageStateStore({ store: window.localStorage || window.sessionStorage }), + client_id: 'squidex-frontend', + scope: 'squidex-api openid profile email squidex-profile role permissions', + response_type: 'id_token token', + redirect_uri: apiUrl.buildUrl('login;'), + post_logout_redirect_uri: apiUrl.buildUrl('logout'), + silent_redirect_uri: apiUrl.buildUrl('client-callback-silent'), + popup_redirect_uri: apiUrl.buildUrl('client-callback-popup'), + authority: apiUrl.buildUrl('identity-server/'), + userStore: new WebStorageStateStore({ store: window.localStorage || window.sessionStorage }), automaticSilentRenew: true }); @@ -103,7 +103,7 @@ export class AuthService { } public logoutRedirect() { - this.userManager.signoutRedirect(); + this.userManager.signoutRedirect(); } public loginRedirect() { diff --git a/src/Squidex/app/shared/services/backups.service.ts b/src/Squidex/app/shared/services/backups.service.ts index 492290d84..3af49a2c7 100644 --- a/src/Squidex/app/shared/services/backups.service.ts +++ b/src/Squidex/app/shared/services/backups.service.ts @@ -85,51 +85,51 @@ export class BackupsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/backups`); return this.http.get<{ items: any[], _links: {} } & Resource>(url).pipe( - map(({ items, _links }) => { - const backups = items.map(item => parseBackup(item)); + map(({ items, _links }) => { + const backups = items.map(item => parseBackup(item)); - return new BackupsDto(backups, _links); - }), - pretifyError('Failed to load backups.')); + return new BackupsDto(backups, _links); + }), + pretifyError('Failed to load backups.')); } public getRestore(): Observable { const url = this.apiUrl.buildUrl(`api/apps/restore`); return this.http.get(url).pipe( - map(body => { - const restore = parseRestore(body); - - return restore; - }), - catchError(error => { - if (Types.is(error, HttpErrorResponse) && error.status === 404) { - return of(null); - } else { - return throwError(error); - } - }), - pretifyError('Failed to load backups.')); + map(body => { + const restore = parseRestore(body); + + return restore; + }), + catchError(error => { + if (Types.is(error, HttpErrorResponse) && error.status === 404) { + return of(null); + } else { + return throwError(error); + } + }), + pretifyError('Failed to load backups.')); } public postBackup(appName: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/backups`); return this.http.post(url, {}).pipe( - tap(() => { - this.analytics.trackEvent('Backup', 'Started', appName); - }), - pretifyError('Failed to start backup.')); + tap(() => { + this.analytics.trackEvent('Backup', 'Started', appName); + }), + pretifyError('Failed to start backup.')); } public postRestore(dto: StartRestoreDto): Observable { const url = this.apiUrl.buildUrl(`api/apps/restore`); return this.http.post(url, dto).pipe( - tap(() => { - this.analytics.trackEvent('Restore', 'Started'); - }), - pretifyError('Failed to start restore.')); + tap(() => { + this.analytics.trackEvent('Restore', 'Started'); + }), + pretifyError('Failed to start restore.')); } public deleteBackup(appName: string, resource: Resource): Observable { @@ -138,10 +138,10 @@ export class BackupsService { const url = this.apiUrl.buildUrl(link.href); return this.http.request(link.method, url).pipe( - tap(() => { - this.analytics.trackEvent('Backup', 'Deleted', appName); - }), - pretifyError('Failed to delete backup.')); + tap(() => { + this.analytics.trackEvent('Backup', 'Deleted', appName); + }), + pretifyError('Failed to delete backup.')); } } diff --git a/src/Squidex/app/shared/services/clients.service.ts b/src/Squidex/app/shared/services/clients.service.ts index 8bb895877..39a0473c2 100644 --- a/src/Squidex/app/shared/services/clients.service.ts +++ b/src/Squidex/app/shared/services/clients.service.ts @@ -80,23 +80,23 @@ export class ClientsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - return parseClients(body); - }), - pretifyError('Failed to load clients. Please reload.')); + mapVersioned(({ body }) => { + return parseClients(body); + }), + pretifyError('Failed to load clients. Please reload.')); } public postClient(appName: string, dto: CreateClientDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`); return HTTP.postVersioned(this.http, url, dto, version).pipe( - mapVersioned(({ body }) => { - return parseClients(body); - }), - tap(() => { - this.analytics.trackEvent('Client', 'Created', appName); - }), - pretifyError('Failed to add client. Please reload.')); + mapVersioned(({ body }) => { + return parseClients(body); + }), + tap(() => { + this.analytics.trackEvent('Client', 'Created', appName); + }), + pretifyError('Failed to add client. Please reload.')); } public putClient(appName: string, resource: Resource, dto: UpdateClientDto, version: Version): Observable { @@ -105,13 +105,13 @@ export class ClientsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( - mapVersioned(({ body }) => { - return parseClients(body); - }), - tap(() => { - this.analytics.trackEvent('Client', 'Updated', appName); - }), - pretifyError('Failed to revoke client. Please reload.')); + mapVersioned(({ body }) => { + return parseClients(body); + }), + tap(() => { + this.analytics.trackEvent('Client', 'Updated', appName); + }), + pretifyError('Failed to revoke client. Please reload.')); } public deleteClient(appName: string, resource: Resource, version: Version): Observable { @@ -120,13 +120,13 @@ export class ClientsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - mapVersioned(({ body }) => { - return parseClients(body); - }), - tap(() => { - this.analytics.trackEvent('Client', 'Deleted', appName); - }), - pretifyError('Failed to revoke client. Please reload.')); + mapVersioned(({ body }) => { + return parseClients(body); + }), + tap(() => { + this.analytics.trackEvent('Client', 'Deleted', appName); + }), + pretifyError('Failed to revoke client. Please reload.')); } public createToken(appName: string, client: ClientDto): Observable { @@ -141,10 +141,10 @@ export class ClientsService { const url = this.apiUrl.buildUrl('identity-server/connect/token'); return this.http.post(url, body, options).pipe( - map((response: any) => { - return new AccessTokenDto(response.access_token, response.token_type); - }), - pretifyError('Failed to create token. Please retry.')); + map((response: any) => { + return new AccessTokenDto(response.access_token, response.token_type); + }), + pretifyError('Failed to create token. Please retry.')); } } diff --git a/src/Squidex/app/shared/services/comments.service.ts b/src/Squidex/app/shared/services/comments.service.ts index 36694e8da..6fb4d4ba5 100644 --- a/src/Squidex/app/shared/services/comments.service.ts +++ b/src/Squidex/app/shared/services/comments.service.ts @@ -56,58 +56,58 @@ export class CommentsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}?version=${version.value}`); return this.http.get(url).pipe( - map(body => { - const comments = new CommentsDto( - body.createdComments.map((item: any) => { - return new CommentDto( - item.id, - DateTime.parseISO_UTC(item.time), - item.text, - item.user); - }), - body.updatedComments.map((item: any) => { - return new CommentDto( - item.id, - DateTime.parseISO_UTC(item.time), - item.text, - item.user); - }), - body.deletedComments, - new Version(body.version) - ); - - return comments; - }), - pretifyError('Failed to load comments.')); + map(body => { + const comments = new CommentsDto( + body.createdComments.map((item: any) => { + return new CommentDto( + item.id, + DateTime.parseISO_UTC(item.time), + item.text, + item.user); + }), + body.updatedComments.map((item: any) => { + return new CommentDto( + item.id, + DateTime.parseISO_UTC(item.time), + item.text, + item.user); + }), + body.deletedComments, + new Version(body.version) + ); + + return comments; + }), + pretifyError('Failed to load comments.')); } public postComment(appName: string, commentsId: string, dto: UpsertCommentDto): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}`); return this.http.post(url, dto).pipe( - map(body => { - const comment = new CommentDto( - body.id, - DateTime.parseISO_UTC(body.time), - body.text, - body.user); - - return comment; - }), - pretifyError('Failed to create comment.')); + map(body => { + const comment = new CommentDto( + body.id, + DateTime.parseISO_UTC(body.time), + body.text, + body.user); + + return comment; + }), + pretifyError('Failed to create comment.')); } public putComment(appName: string, commentsId: string, commentId: string, dto: UpsertCommentDto): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}/${commentId}`); return this.http.put(url, dto).pipe( - pretifyError('Failed to update comment.')); + pretifyError('Failed to update comment.')); } public deleteComment(appName: string, commentsId: string, commentId: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}/${commentId}`); return this.http.delete(url).pipe( - pretifyError('Failed to delete comment.')); + pretifyError('Failed to delete comment.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index bed10aeca..1096e3679 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -137,45 +137,45 @@ export class ContentsService { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`); return this.http.get<{ total: number, items: [], statuses: StatusInfo[] } & Resource>(url).pipe( - map(({ total, items, statuses, _links }) => { - const contents = items.map(x => parseContent(x)); + map(({ total, items, statuses, _links }) => { + const contents = items.map(x => parseContent(x)); - return new ContentsDto(statuses, total, contents, _links); - }), - pretifyError('Failed to load contents. Please reload.')); + return new ContentsDto(statuses, total, contents, _links); + }), + pretifyError('Failed to load contents. Please reload.')); } public getContent(appName: string, schemaName: string, id: string): Observable { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); return HTTP.getVersioned(this.http, url).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - pretifyError('Failed to load content. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + pretifyError('Failed to load content. Please reload.')); } public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable> { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - return body; - }), - pretifyError('Failed to load data. Please reload.')); + mapVersioned(({ body }) => { + return body; + }), + pretifyError('Failed to load data. Please reload.')); } public postContent(appName: string, schemaName: string, dto: any, publish: boolean): Observable { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`); return HTTP.postVersioned(this.http, url, dto).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Created', appName); - }), - pretifyError('Failed to create content. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Created', appName); + }), + pretifyError('Failed to create content. Please reload.')); } public putContent(appName: string, resource: Resource, dto: any, version: Version): Observable { @@ -184,13 +184,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.putVersioned(this.http, url, dto, version).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Updated', appName); - }), - pretifyError('Failed to update content. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Updated', appName); + }), + pretifyError('Failed to update content. Please reload.')); } public patchContent(appName: string, resource: Resource, dto: any, version: Version): Observable { @@ -199,13 +199,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Updated', appName); - }), - pretifyError('Failed to update content. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Updated', appName); + }), + pretifyError('Failed to update content. Please reload.')); } public discardDraft(appName: string, resource: Resource, version: Version): Observable { @@ -214,13 +214,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, {}).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Discarded', appName); - }), - pretifyError('Failed to discard draft. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Discarded', appName); + }), + pretifyError('Failed to discard draft. Please reload.')); } public proposeDraft(appName: string, resource: Resource, dto: any, version: Version): Observable { @@ -229,13 +229,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.putVersioned(this.http, url, dto, version).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Updated', appName); - }), - pretifyError('Failed to propose draft. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Updated', appName); + }), + pretifyError('Failed to propose draft. Please reload.')); } public publishDraft(appName: string, resource: Resource, dueTime: string | null, version: Version): Observable { @@ -244,13 +244,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, { status: 'Published', dueTime }).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Discarded', appName); - }), - pretifyError('Failed to publish draft. Please reload.')); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Discarded', appName); + }), + pretifyError('Failed to publish draft. Please reload.')); } public putStatus(appName: string, resource: Resource, status: string, dueTime: string | null, version: Version): Observable { @@ -259,13 +259,13 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, { status, dueTime }).pipe( - map(({ payload }) => { - return parseContent(payload.body); - }), - tap(() => { - this.analytics.trackEvent('Content', 'Archived', appName); - }), - pretifyError(`Failed to ${status} content. Please reload.`)); + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Archived', appName); + }), + pretifyError(`Failed to ${status} content. Please reload.`)); } public deleteContent(appName: string, resource: Resource, version: Version): Observable> { @@ -274,10 +274,10 @@ export class ContentsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - tap(() => { - this.analytics.trackEvent('Content', 'Deleted', appName); - }), - pretifyError('Failed to delete content. Please reload.')); + tap(() => { + this.analytics.trackEvent('Content', 'Deleted', appName); + }), + pretifyError('Failed to delete content. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/contributors.service.ts b/src/Squidex/app/shared/services/contributors.service.ts index def17609a..452116a9b 100644 --- a/src/Squidex/app/shared/services/contributors.service.ts +++ b/src/Squidex/app/shared/services/contributors.service.ts @@ -50,7 +50,7 @@ export class ContributorDto { } } -export interface AssignContributorDto { +export interface AssignContributorDto { readonly contributorId: string; readonly role: string; readonly invite?: boolean; @@ -69,23 +69,23 @@ export class ContributorsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - return parseContributors(body); - }), - pretifyError('Failed to load contributors. Please reload.')); + mapVersioned(({ body }) => { + return parseContributors(body); + }), + pretifyError('Failed to load contributors. Please reload.')); } public postContributor(appName: string, dto: AssignContributorDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`); return HTTP.postVersioned(this.http, url, dto, version).pipe( - mapVersioned(({ body }) => { - return parseContributors(body); - }), - tap(() => { - this.analytics.trackEvent('Contributor', 'Configured', appName); - }), - pretifyError('Failed to add contributors. Please reload.')); + mapVersioned(({ body }) => { + return parseContributors(body); + }), + tap(() => { + this.analytics.trackEvent('Contributor', 'Configured', appName); + }), + pretifyError('Failed to add contributors. Please reload.')); } public deleteContributor(appName: string, resource: Resource, version: Version): Observable { @@ -94,15 +94,15 @@ export class ContributorsService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - mapVersioned(payload => { - const body = payload.body; - - return parseContributors(body); - }), - tap(() => { - this.analytics.trackEvent('Contributor', 'Deleted', appName); - }), - pretifyError('Failed to delete contributors. Please reload.')); + mapVersioned(payload => { + const body = payload.body; + + return parseContributors(body); + }), + tap(() => { + this.analytics.trackEvent('Contributor', 'Deleted', appName); + }), + pretifyError('Failed to delete contributors. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/help.service.ts b/src/Squidex/app/shared/services/help.service.ts index b37528fd7..7abb21adf 100644 --- a/src/Squidex/app/shared/services/help.service.ts +++ b/src/Squidex/app/shared/services/help.service.ts @@ -21,6 +21,6 @@ export class HelpService { const url = `https://raw.githubusercontent.com/Squidex/squidex-docs/master/${helpPage}.md`; return this.http.get(url, { responseType: 'text' }).pipe( - catchError(() => of(''))); + catchError(() => of(''))); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/history.service.ts b/src/Squidex/app/shared/services/history.service.ts index d79add475..95fef8ede 100644 --- a/src/Squidex/app/shared/services/history.service.ts +++ b/src/Squidex/app/shared/services/history.service.ts @@ -82,17 +82,17 @@ export class HistoryService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`); return this.http.get(url).pipe( - map(body => { - const history = body.map(item => - new HistoryEventDto( - item.eventId, - item.actor, - item.message, - item.version, - DateTime.parseISO_UTC(item.created))); - - return history; - }), - pretifyError('Failed to load history. Please reload.')); + map(body => { + const history = body.map(item => + new HistoryEventDto( + item.eventId, + item.actor, + item.message, + item.version, + DateTime.parseISO_UTC(item.created))); + + return history; + }), + pretifyError('Failed to load history. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/languages.service.ts b/src/Squidex/app/shared/services/languages.service.ts index ea76d2936..d905f63e2 100644 --- a/src/Squidex/app/shared/services/languages.service.ts +++ b/src/Squidex/app/shared/services/languages.service.ts @@ -32,14 +32,14 @@ export class LanguagesService { const url = this.apiUrl.buildUrl('api/languages'); return this.http.get(url).pipe( - map(body => { - const languages = body.map(item => - new LanguageDto( - item.iso2Code, - item.englishName)); + map(body => { + const languages = body.map(item => + new LanguageDto( + item.iso2Code, + item.englishName)); - return languages; - }), - pretifyError('Failed to load languages. Please reload.')); + return languages; + }), + pretifyError('Failed to load languages. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/news.service.ts b/src/Squidex/app/shared/services/news.service.ts index db7e02a5f..2c3be90fc 100644 --- a/src/Squidex/app/shared/services/news.service.ts +++ b/src/Squidex/app/shared/services/news.service.ts @@ -40,19 +40,19 @@ export class NewsService { const url = this.apiUrl.buildUrl(`api/news/features?version=${version}`); return this.http.get(url).pipe( - map(body => { - const items: any[] = body.features; - - const features = new FeaturesDto( - items.map(item => - new FeatureDto( - item.name, - item.text) - ), - body.version); - - return features; - }), - pretifyError('Failed to load features. Please reload.')); + map(body => { + const items: any[] = body.features; + + const features = new FeaturesDto( + items.map(item => + new FeatureDto( + item.name, + item.text) + ), + body.version); + + return features; + }), + pretifyError('Failed to load features. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/plans.service.ts b/src/Squidex/app/shared/services/plans.service.ts index 31e1ace9a..6410cc22a 100644 --- a/src/Squidex/app/shared/services/plans.service.ts +++ b/src/Squidex/app/shared/services/plans.service.ts @@ -62,42 +62,42 @@ export class PlansService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - const items: any[] = body.plans; + mapVersioned(({ body }) => { + const items: any[] = body.plans; - const { hasPortal, currentPlanId, planOwner } = body; + const { hasPortal, currentPlanId, planOwner } = body; - const plans = { - currentPlanId, - planOwner, - plans: items.map(item => - new PlanDto( - item.id, - item.name, - item.costs, - item.yearlyId, - item.yearlyCosts, - item.maxApiCalls, - item.maxAssetSize, - item.maxContributors)), - hasPortal - }; + const plans = { + currentPlanId, + planOwner, + plans: items.map(item => + new PlanDto( + item.id, + item.name, + item.costs, + item.yearlyId, + item.yearlyCosts, + item.maxApiCalls, + item.maxAssetSize, + item.maxContributors)), + hasPortal + }; - return plans; - }), - pretifyError('Failed to load plans. Please reload.')); + return plans; + }), + pretifyError('Failed to load plans. Please reload.')); } public putPlan(appName: string, dto: ChangePlanDto, version: Version): Observable> { const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`); return HTTP.putVersioned(this.http, url, dto, version).pipe( - mapVersioned(payload => { - return payload.body; - }), - tap(() => { - this.analytics.trackEvent('Plan', 'Changed', appName); - }), - pretifyError('Failed to change plan. Please reload.')); + mapVersioned(payload => { + return payload.body; + }), + tap(() => { + this.analytics.trackEvent('Plan', 'Changed', appName); + }), + pretifyError('Failed to change plan. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/roles.service.ts b/src/Squidex/app/shared/services/roles.service.ts index 7fa11d194..bc5a72154 100644 --- a/src/Squidex/app/shared/services/roles.service.ts +++ b/src/Squidex/app/shared/services/roles.service.ts @@ -72,23 +72,23 @@ export class RolesService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`); return HTTP.getVersioned(this.http, url).pipe( - mapVersioned(({ body }) => { - return parseRoles(body); - }), - pretifyError('Failed to load roles. Please reload.')); + mapVersioned(({ body }) => { + return parseRoles(body); + }), + pretifyError('Failed to load roles. Please reload.')); } public postRole(appName: string, dto: CreateRoleDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`); return HTTP.postVersioned(this.http, url, dto, version).pipe( - mapVersioned(({ body }) => { - return parseRoles(body); - }), - tap(() => { - this.analytics.trackEvent('Role', 'Created', appName); - }), - pretifyError('Failed to add role. Please reload.')); + mapVersioned(({ body }) => { + return parseRoles(body); + }), + tap(() => { + this.analytics.trackEvent('Role', 'Created', appName); + }), + pretifyError('Failed to add role. Please reload.')); } public putRole(appName: string, resource: Resource, dto: UpdateRoleDto, version: Version): Observable { @@ -97,13 +97,13 @@ export class RolesService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( - mapVersioned(({ body }) => { - return parseRoles(body); - }), - tap(() => { - this.analytics.trackEvent('Role', 'Updated', appName); - }), - pretifyError('Failed to revoke role. Please reload.')); + mapVersioned(({ body }) => { + return parseRoles(body); + }), + tap(() => { + this.analytics.trackEvent('Role', 'Updated', appName); + }), + pretifyError('Failed to revoke role. Please reload.')); } public deleteRole(appName: string, resource: Resource, version: Version): Observable { @@ -112,20 +112,20 @@ export class RolesService { const url = this.apiUrl.buildUrl(link.href); return HTTP.requestVersioned(this.http, link.method, url, version).pipe( - mapVersioned(({ body }) => { - return parseRoles(body); - }), - tap(() => { - this.analytics.trackEvent('Role', 'Deleted', appName); - }), - pretifyError('Failed to revoke role. Please reload.')); + mapVersioned(({ body }) => { + return parseRoles(body); + }), + tap(() => { + this.analytics.trackEvent('Role', 'Deleted', appName); + }), + pretifyError('Failed to revoke role. Please reload.')); } public getPermissions(appName: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles/permissions`); return this.http.get(url).pipe( - pretifyError('Failed to load permissions. Please reload.')); + pretifyError('Failed to load permissions. Please reload.')); } } diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 5c42f7dec..b4f6c8350 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -41,7 +41,8 @@ export const ALL_TRIGGERS = { description: 'When a schema definition has been created, updated, published or deleted...', display: 'Schema changed', iconColor: '#3389ff', - iconCode: 'schemas'}, + iconCode: 'schemas' + }, 'Usage': { description: 'When monthly API calls exceed a specified limit for one time a month...', display: 'Usage exceeded', diff --git a/src/Squidex/app/shared/services/translations.service.ts b/src/Squidex/app/shared/services/translations.service.ts index b5b91897d..30d8ee52b 100644 --- a/src/Squidex/app/shared/services/translations.service.ts +++ b/src/Squidex/app/shared/services/translations.service.ts @@ -38,9 +38,9 @@ export class TranslationsService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/translations`); return this.http.post(url, request).pipe( - map(body => { - return new TranslationDto(body.result, body.text); - }), - pretifyError('Failed to translate text. Please reload.')); + map(body => { + return new TranslationDto(body.result, body.text); + }), + pretifyError('Failed to translate text. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/usages.service.ts b/src/Squidex/app/shared/services/usages.service.ts index dbd004aae..e97b99106 100644 --- a/src/Squidex/app/shared/services/usages.service.ts +++ b/src/Squidex/app/shared/services/usages.service.ts @@ -72,55 +72,55 @@ export class UsagesService { const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/month`); return this.http.get(url).pipe( - map(body => { - return new CurrentCallsDto(body.count, body.maxAllowed); - }), - pretifyError('Failed to load monthly api calls. Please reload.')); + map(body => { + return new CurrentCallsDto(body.count, body.maxAllowed); + }), + pretifyError('Failed to load monthly api calls. Please reload.')); } public getTodayStorage(app: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/today`); return this.http.get(url).pipe( - map(body => { - return new CurrentStorageDto(body.size, body.maxAllowed); - }), - pretifyError('Failed to load todays storage size. Please reload.')); + map(body => { + return new CurrentStorageDto(body.size, body.maxAllowed); + }), + pretifyError('Failed to load todays storage size. Please reload.')); } public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<{ [category: string]: CallsUsageDto[] }> { const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`); return this.http.get(url).pipe( - map(body => { - const usages: { [category: string]: CallsUsageDto[] } = {}; - - for (let category of Object.keys(body)) { - usages[category] = body[category].map((item: any) => - new CallsUsageDto( - DateTime.parseISO_UTC(item.date), - item.count, - item.averageMs)); - } - - return usages; - }), - pretifyError('Failed to load calls usage. Please reload.')); + map(body => { + const usages: { [category: string]: CallsUsageDto[] } = {}; + + for (let category of Object.keys(body)) { + usages[category] = body[category].map((item: any) => + new CallsUsageDto( + DateTime.parseISO_UTC(item.date), + item.count, + item.averageMs)); + } + + return usages; + }), + pretifyError('Failed to load calls usage. Please reload.')); } public getStorageUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable { const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`); return this.http.get(url).pipe( - map(body => { - const usages = body.map(item => - new StorageUsageDto( - DateTime.parseISO_UTC(item.date), - item.count, - item.size)); + map(body => { + const usages = body.map(item => + new StorageUsageDto( + DateTime.parseISO_UTC(item.date), + item.count, + item.size)); - return usages; - }), - pretifyError('Failed to load storage usage. Please reload.')); + return usages; + }), + pretifyError('Failed to load storage usage. Please reload.')); } } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs index 1f2a4fc00..be9ad655e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Assets private readonly IAssetRepository assetRepository = A.Fake(); private readonly IAppEntity app = A.Fake(); private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); - private readonly QueryContext context; + private readonly Context context; private readonly AssetQueryService sut; public AssetQueryServiceTests() @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Assets A.CallTo(() => app.Name).Returns(appId.Name); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); - context = QueryContext.Create(app, user); + context = new Context(user, app); var options = Options.Create(new AssetOptions { DefaultPageSize = 30 }); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs index cbdd86c98..3aa4f0f81 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs @@ -16,13 +16,18 @@ namespace Squidex.Domain.Apps.Entities.Contents { public class ContentEnricherTests { - private readonly IContentWorkflow workflow = A.Fake(); + private readonly IContentWorkflow contentWorkflow = A.Fake(); + private readonly IContextProvider contextProvider = A.Fake(); + private readonly Context context = new Context(); private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly ContentEnricher sut; public ContentEnricherTests() { - sut = new ContentEnricher(workflow); + A.CallTo(() => contextProvider.Context) + .Returns(context); + + sut = new ContentEnricher(contentWorkflow, contextProvider); } [Fact] @@ -30,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId }; - A.CallTo(() => workflow.GetInfoAsync(source)) + A.CallTo(() => contentWorkflow.GetInfoAsync(source)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); var result = await sut.EnrichAsync(source); @@ -43,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var source = new ContentEntity { Status = Status.Published, SchemaId = schemaId }; - A.CallTo(() => workflow.GetInfoAsync(source)) + A.CallTo(() => contentWorkflow.GetInfoAsync(source)) .Returns(Task.FromResult(null)); var result = await sut.EnrichAsync(source); @@ -54,9 +59,11 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_enrich_content_with_can_update() { + context.WithResolveFlow(true); + var source = new ContentEntity { SchemaId = schemaId }; - A.CallTo(() => workflow.CanUpdateAsync(source)) + A.CallTo(() => contentWorkflow.CanUpdateAsync(source)) .Returns(true); var result = await sut.EnrichAsync(source); @@ -64,13 +71,28 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.True(result.CanUpdate); } + [Fact] + public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context() + { + context.WithResolveFlow(false); + + var source = new ContentEntity { SchemaId = schemaId }; + + var result = await sut.EnrichAsync(source); + + Assert.False(result.CanUpdate); + + A.CallTo(() => contentWorkflow.CanUpdateAsync(source)) + .MustNotHaveHappened(); + } + [Fact] public async Task Should_enrich_multiple_contents_and_cache_color() { var source1 = new ContentEntity { Status = Status.Published, SchemaId = schemaId }; var source2 = new ContentEntity { Status = Status.Published, SchemaId = schemaId }; - A.CallTo(() => workflow.GetInfoAsync(source1)) + A.CallTo(() => contentWorkflow.GetInfoAsync(source1)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); var result = await sut.EnrichAsync(new[] { source1, source2 }); @@ -78,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Equal(StatusColors.Published, result[0].StatusColor); Assert.Equal(StatusColors.Published, result[1].StatusColor); - A.CallTo(() => workflow.GetInfoAsync(A.Ignored)) + A.CallTo(() => contentWorkflow.GetInfoAsync(A.Ignored)) .MustHaveHappenedOnceExactly(); } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 52117c964..9f6e78aff 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -53,13 +53,13 @@ namespace Squidex.Domain.Apps.Entities.Contents private readonly ClaimsPrincipal user; private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly EdmModelBuilder modelBuilder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions()))); - private readonly QueryContext context; + private readonly Context context; private readonly ContentQueryService sut; public static IEnumerable ApiStatusTests = new[] { - new object[] { StatusForApi.PublishedOnly, 0, new[] { Status.Published } }, - new object[] { StatusForApi.All, 1, null } + new object[] { 0, new[] { Status.Published } }, + new object[] { 1, null } }; public ContentQueryServiceTests() @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents SetupEnricher(); - context = QueryContext.Create(app, user); + context = new Context(user, app); var options = Options.Create(new ContentOptions { DefaultPageSize = 30 }); @@ -221,16 +221,16 @@ namespace Squidex.Domain.Apps.Entities.Contents [Theory] [MemberData(nameof(ApiStatusTests))] - public async Task Should_return_single_content_for_api_with_transform(StatusForApi request, int includeDraft, Status[] status) + public async Task Should_return_single_content_for_api_with_transform(int unpublished, Status[] status) { var content = CreateContent(contentId); SetupClaims(isFrontend: false); SetupSchemaFound(); SetupScripting(contentId); - SetupContent(status, content, includeDraft == 1); + SetupContent(status, content, unpublished == 1); - var ctx = context.WithApiStatus(request); + var ctx = context.WithUnpublished(unpublished == 1); var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId); @@ -299,7 +299,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Theory] [MemberData(nameof(ApiStatusTests))] - public async Task Should_query_contents_by_query_for_api_and_transform(StatusForApi request, int includeDraft, Status[] status) + public async Task Should_query_contents_by_query_for_api_and_transform(int unpublished, Status[] status) { const int count = 5, total = 200; @@ -308,9 +308,9 @@ namespace Squidex.Domain.Apps.Entities.Contents SetupClaims(isFrontend: false); SetupSchemaFound(); SetupScripting(contentId); - SetupContents(status, count, total, content, inDraft: false, includeDraft == 1); + SetupContents(status, count, total, content, inDraft: false, unpublished == 1); - var ctx = context.WithApiStatus(request); + var ctx = context.WithUnpublished(unpublished == 1); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty); @@ -359,7 +359,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Theory] [MemberData(nameof(ApiStatusTests))] - public async Task Should_query_contents_by_id_for_api_and_transform(StatusForApi request, int includeDraft, Status[] status) + public async Task Should_query_contents_by_id_for_api_and_transform(int unpublished, Status[] status) { const int count = 5, total = 200; @@ -368,9 +368,9 @@ namespace Squidex.Domain.Apps.Entities.Contents SetupClaims(isFrontend: false); SetupSchemaFound(); SetupScripting(ids.ToArray()); - SetupContents(status, total, ids, includeDraft == 1); + SetupContents(status, total, ids, unpublished == 1); - var ctx = context.WithApiStatus(request); + var ctx = context.WithUnpublished(unpublished == 1); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids)); @@ -405,7 +405,7 @@ namespace Squidex.Domain.Apps.Entities.Contents [Theory] [MemberData(nameof(ApiStatusTests))] - public async Task Should_query_all_contents_by_id_for_api_and_transform(StatusForApi request, int includeDraft, Status[] status) + public async Task Should_query_all_contents_by_id_for_api_and_transform(int unpublished, Status[] status) { const int count = 5; @@ -414,9 +414,9 @@ namespace Squidex.Domain.Apps.Entities.Contents SetupClaims(isFrontend: false); SetupSchemaFound(); SetupScripting(ids.ToArray()); - SetupContents(status, ids, includeDraft == 1); + SetupContents(status, ids, unpublished == 1); - var ctx = context.WithApiStatus(request); + var ctx = context.WithUnpublished(unpublished == 1); var result = await sut.QueryAsync(ctx, ids); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 82cd8b87e..f07d13263 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -1017,14 +1017,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return A.That.Matches(x => x.Ids.Count == 1 && x.Ids[0] == contentId); } - private QueryContext MatchsAssetContext() + private Context MatchsAssetContext() { - return A.That.Matches(x => x.App == app && x.User == user); + return A.That.Matches(x => x.App == app && x.User == user); } - private QueryContext MatchsContentContext() + private Context MatchsContentContext() { - return A.That.Matches(x => x.App == app && x.User == user); + return A.That.Matches(x => x.App == app && x.User == user); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 89f343e23..d14332136 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL protected readonly IJsonSerializer serializer = TestUtils.CreateSerializer(TypeNameHandling.None); protected readonly IDependencyResolver dependencyResolver; protected readonly IAppEntity app = A.Dummy(); - protected readonly QueryContext context; + protected readonly Context context; protected readonly ClaimsPrincipal user = new ClaimsPrincipal(); protected readonly IGraphQLService sut; @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE, Language.GermanGermany)); - context = QueryContext.Create(app, user); + context = new Context(user, app); A.CallTo(() => schema.Id).Returns(schemaId); A.CallTo(() => schema.SchemaDef).Returns(schemaDef); diff --git a/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs b/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs index 9b95902d9..a07f0cfb7 100644 --- a/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs +++ b/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs @@ -38,7 +38,7 @@ namespace Squidex.Web actionExecutingContext = new ActionExecutingContext(actionContext, new List(), new Dictionary(), this); actionExecutingContext.HttpContext = httpContext; - actionExecutingContext.HttpContext.User = new ClaimsPrincipal(user); + actionExecutingContext.HttpContext.Context().User = new ClaimsPrincipal(user); next = () => { diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs index a249eacf5..8ad1aa5de 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs @@ -8,44 +8,43 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Web.Pipeline; using Xunit; namespace Squidex.Web.CommandMiddlewares { public class EnrichWithAppIdCommandMiddlewareTests { - private readonly IHttpContextAccessor httpContextAccessor = A.Fake(); + private readonly IContextProvider contextProvider = A.Fake(); private readonly ICommandBus commandBus = A.Fake(); private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); - private readonly HttpContext httpContext = new DefaultHttpContext(); + private readonly Context appContext = new Context(); private readonly EnrichWithAppIdCommandMiddleware sut; public EnrichWithAppIdCommandMiddlewareTests() { - A.CallTo(() => httpContextAccessor.HttpContext) - .Returns(httpContext); + A.CallTo(() => contextProvider.Context) + .Returns(appContext); var app = A.Fake(); A.CallTo(() => app.Id).Returns(appId.Id); A.CallTo(() => app.Name).Returns(appId.Name); - httpContext.Features.Set(new AppResolver.AppFeature(app)); + appContext.App = app; - sut = new EnrichWithAppIdCommandMiddleware(httpContextAccessor); + sut = new EnrichWithAppIdCommandMiddleware(contextProvider); } [Fact] public async Task Should_throw_exception_if_app_not_found() { - httpContext.Features.Set(new AppResolver.AppFeature(null)); + appContext.App = null; var command = new CreateContent(); var context = new CommandContext(command, commandBus); @@ -53,20 +52,6 @@ namespace Squidex.Web.CommandMiddlewares await Assert.ThrowsAsync(() => sut.HandleAsync(context)); } - [Fact] - public async Task Should_do_nothing_when_context_is_null() - { - A.CallTo(() => httpContextAccessor.HttpContext) - .Returns(null); - - var command = new CreateContent(); - var context = new CommandContext(command, commandBus); - - await sut.HandleAsync(context); - - Assert.Null(command.Actor); - } - [Fact] public async Task Should_assign_app_id_and_name_to_app_command() { diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs index bf5b185d1..e3959732f 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs @@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Web.Pipeline; using Xunit; namespace Squidex.Web.CommandMiddlewares @@ -49,7 +48,7 @@ namespace Squidex.Web.CommandMiddlewares A.CallTo(() => app.Id).Returns(appId.Id); A.CallTo(() => app.Name).Returns(appId.Name); - httpContext.Features.Set(new AppResolver.AppFeature(app)); + httpContext.Context().App = app; var schema = A.Fake(); @@ -65,33 +64,19 @@ namespace Squidex.Web.CommandMiddlewares } [Fact] - public async Task Should_throw_exception_if_app_not_found() + public async Task Should_throw_exception_if_schema_not_found() { A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, "other-schema")) .Returns(Task.FromResult(null)); actionContext.RouteData.Values["name"] = "other-schema"; - var command = new CreateContent(); + var command = new CreateContent { AppId = appId }; var context = new CommandContext(command, commandBus); await Assert.ThrowsAsync(() => sut.HandleAsync(context)); } - [Fact] - public async Task Should_do_nothing_when_context_is_null() - { - A.CallTo(() => actionContextAccessor.ActionContext) - .Returns(null); - - var command = new CreateContent(); - var context = new CommandContext(command, commandBus); - - await sut.HandleAsync(context); - - Assert.Null(command.Actor); - } - [Fact] public async Task Should_do_nothing_when_route_has_no_parameter() { @@ -108,7 +93,7 @@ namespace Squidex.Web.CommandMiddlewares { actionContext.RouteData.Values["name"] = schemaId.Name; - var command = new CreateContent(); + var command = new CreateContent { AppId = appId }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); @@ -119,9 +104,9 @@ namespace Squidex.Web.CommandMiddlewares [Fact] public async Task Should_assign_schema_id_and_name_from_id() { - actionContext.RouteData.Values["name"] = schemaId.Name; + actionContext.RouteData.Values["name"] = schemaId.Id; - var command = new CreateContent(); + var command = new CreateContent { AppId = appId }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); @@ -142,19 +127,6 @@ namespace Squidex.Web.CommandMiddlewares Assert.Equal(schemaId.Id, command.SchemaId); } - [Fact] - public async Task Should_use_app_id_from_command() - { - actionContext.RouteData.Values["name"] = schemaId.Name; - - var command = new CreateContent { AppId = appId }; - var context = new CommandContext(command, commandBus); - - await sut.HandleAsync(context); - - Assert.Equal(schemaId, command.SchemaId); - } - [Fact] public async Task Should_not_override_schema_id() { diff --git a/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs b/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs index 2ee909d51..eb0ddb4eb 100644 --- a/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs +++ b/tests/Squidex.Web.Tests/Pipeline/ApiCostsFilterTests.cs @@ -161,7 +161,7 @@ namespace Squidex.Web.Pipeline private void SetupApp() { - httpContext.Features.Set(new AppResolver.AppFeature(appEntity)); + httpContext.Context().App = appEntity; } } } \ No newline at end of file diff --git a/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs b/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs index b7f7b00b5..cf2ff94ec 100644 --- a/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs +++ b/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs @@ -86,7 +86,7 @@ namespace Squidex.Web.Pipeline await sut.OnActionExecutionAsync(actionExecutingContext, next); - Assert.Same(app, httpContext.Features.Get()?.App); + Assert.Same(app, httpContext.Context().App); Assert.True(user.Claims.Count() > 2); Assert.True(isNextCalled); } @@ -104,7 +104,7 @@ namespace Squidex.Web.Pipeline await sut.OnActionExecutionAsync(actionExecutingContext, next); - Assert.Same(app, httpContext.Features.Get()?.App); + Assert.Same(app, httpContext.Context().App); Assert.True(user.Claims.Count() > 2); Assert.True(isNextCalled); } @@ -124,7 +124,7 @@ namespace Squidex.Web.Pipeline await sut.OnActionExecutionAsync(actionExecutingContext, next); - Assert.Same(app, httpContext.Features.Get()?.App); + Assert.Same(app, httpContext.Context().App); Assert.Equal(2, user.Claims.Count()); Assert.True(isNextCalled); }