mirror of https://github.com/Squidex/squidex.git
Browse Source
# Conflicts: # src/Squidex/app/features/api/pages/graphql/graphql-page.component.scss # src/Squidex/app/features/apps/pages/news-dialog.component.scss # src/Squidex/app/features/apps/pages/onboarding-dialog.component.scss # src/Squidex/app/features/assets/pages/assets-page.component.scss # src/Squidex/app/features/content/pages/content/content-history-page.component.scss # src/Squidex/app/features/content/pages/contents/contents-page.component.scss # src/Squidex/app/features/content/shared/array-item.component.scss # src/Squidex/app/features/content/shared/contents-selector.component.scss # src/Squidex/app/features/dashboard/pages/dashboard-page.component.scss # src/Squidex/app/features/schemas/pages/schema/field-wizard.component.scss # src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss # src/Squidex/app/framework/angular/forms/code-editor.component.scss # src/Squidex/app/framework/angular/forms/color-picker.component.scss # src/Squidex/app/framework/angular/forms/json-editor.component.scss # src/Squidex/app/shared/components/asset.component.scss # src/Squidex/app/shared/components/assets-selector.component.scss # src/Squidex/app/shared/components/history-list.component.scss # src/Squidex/app/shared/state/assets.state.spec.ts # src/Squidex/package-lock.json # src/Squidex/package.jsonpull/376/head
397 changed files with 9772 additions and 6999 deletions
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<RunSettings> |
|||
<RunConfiguration> |
|||
<MaxCpuCount>4</MaxCpuCount> |
|||
</RunConfiguration> |
|||
</RunSettings> |
|||
@ -0,0 +1,42 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure.Json.Newtonsoft; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents.Json |
|||
{ |
|||
public sealed class StatusConverter : JsonConverter, ISupportedTypes |
|||
{ |
|||
public IEnumerable<Type> SupportedTypes |
|||
{ |
|||
get { yield return typeof(Status); } |
|||
} |
|||
|
|||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) |
|||
{ |
|||
writer.WriteValue(value.ToString()); |
|||
} |
|||
|
|||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) |
|||
{ |
|||
if (reader.TokenType != JsonToken.String) |
|||
{ |
|||
throw new JsonException($"Expected String, but got {reader.TokenType}."); |
|||
} |
|||
|
|||
return new Status(reader.Value.ToString()); |
|||
} |
|||
|
|||
public override bool CanConvert(Type objectType) |
|||
{ |
|||
return objectType == typeof(Status); |
|||
} |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public static class StatusFlow |
|||
{ |
|||
private static readonly Dictionary<Status, Status[]> Flow = new Dictionary<Status, Status[]> |
|||
{ |
|||
[Status.Draft] = new[] { Status.Published, Status.Archived }, |
|||
[Status.Archived] = new[] { Status.Draft }, |
|||
[Status.Published] = new[] { Status.Draft, Status.Archived } |
|||
}; |
|||
|
|||
public static bool Exists(Status status) |
|||
{ |
|||
return Flow.ContainsKey(status); |
|||
} |
|||
|
|||
public static bool CanChange(Status status, Status toStatus) |
|||
{ |
|||
return Flow.TryGetValue(status, out var state) && state.Contains(toStatus); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Bson.Serialization.Serializers; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
|||
{ |
|||
public sealed class StatusSerializer : SerializerBase<Status> |
|||
{ |
|||
private static volatile int isRegistered; |
|||
|
|||
public static void Register() |
|||
{ |
|||
if (Interlocked.Increment(ref isRegistered) == 1) |
|||
{ |
|||
BsonSerializer.RegisterSerializer(new StatusSerializer()); |
|||
} |
|||
} |
|||
|
|||
public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) |
|||
{ |
|||
var value = context.Reader.ReadString(); |
|||
|
|||
return new Status(value); |
|||
} |
|||
|
|||
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Status value) |
|||
{ |
|||
context.Writer.WriteString(value.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps |
|||
{ |
|||
public static class AppExtensions |
|||
{ |
|||
public static NamedId<Guid> NamedId(this IAppEntity app) |
|||
{ |
|||
return new NamedId<Guid>(app.Id, app.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public class AssetResult |
|||
{ |
|||
public IAssetEntity Asset { get; } |
|||
|
|||
public HashSet<string> Tags { get; } |
|||
|
|||
public AssetResult(IAssetEntity asset, HashSet<string> tags) |
|||
{ |
|||
Asset = asset; |
|||
|
|||
Tags = tags; |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public class AssetSavedResult : EntitySavedResult |
|||
{ |
|||
public long FileVersion { get; } |
|||
|
|||
public string FileHash { get; } |
|||
|
|||
public AssetSavedResult(long version, long fileVersion, string fileHash) |
|||
: base(version) |
|||
{ |
|||
FileVersion = fileVersion; |
|||
FileHash = fileHash; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Assets; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
|||
{ |
|||
public abstract class UploadAssetCommand : AssetCommand |
|||
{ |
|||
public AssetFile File { get; set; } |
|||
|
|||
public ImageInfo ImageInfo { get; set; } |
|||
|
|||
public string FileHash { get; set; } |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public sealed class ContentDataChangedResult : EntitySavedResult |
|||
{ |
|||
public NamedContentData Data { get; } |
|||
|
|||
public ContentDataChangedResult(NamedContentData data, long version) |
|||
: base(version) |
|||
{ |
|||
Data = data; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public sealed class DefaultContentWorkflow : IContentWorkflow |
|||
{ |
|||
private static readonly Status[] All = { Status.Archived, Status.Draft, Status.Published }; |
|||
|
|||
private static readonly Dictionary<Status, Status[]> Flow = new Dictionary<Status, Status[]> |
|||
{ |
|||
[Status.Draft] = new[] { Status.Archived, Status.Published }, |
|||
[Status.Archived] = new[] { Status.Draft }, |
|||
[Status.Published] = new[] { Status.Draft, Status.Archived } |
|||
}; |
|||
|
|||
public Task<Status> GetInitialStatusAsync(ISchemaEntity schema) |
|||
{ |
|||
return Task.FromResult(Status.Draft); |
|||
} |
|||
|
|||
public Task<bool> CanMoveToAsync(IContentEntity content, Status next) |
|||
{ |
|||
return Task.FromResult(Flow.TryGetValue(content.Status, out var state) && state.Contains(next)); |
|||
} |
|||
|
|||
public Task<bool> CanUpdateAsync(IContentEntity content) |
|||
{ |
|||
return Task.FromResult(content.Status != Status.Archived); |
|||
} |
|||
|
|||
public Task<Status[]> GetNextsAsync(IContentEntity content) |
|||
{ |
|||
return Task.FromResult(Flow.TryGetValue(content.Status, out var result) ? result : Array.Empty<Status>()); |
|||
} |
|||
|
|||
public Task<Status[]> GetAllAsync(ISchemaEntity schema) |
|||
{ |
|||
return Task.FromResult(All); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using GraphQL.Instrumentation; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL |
|||
{ |
|||
public static class LoggingMiddleware |
|||
{ |
|||
public static Func<FieldMiddlewareDelegate, FieldMiddlewareDelegate> Create(ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
return new Func<FieldMiddlewareDelegate, FieldMiddlewareDelegate>(next => |
|||
{ |
|||
return async context => |
|||
{ |
|||
try |
|||
{ |
|||
return await next(context); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
log.LogWarning(ex, w => w |
|||
.WriteProperty("action", "reolveField") |
|||
.WriteProperty("status", "failed") |
|||
.WriteProperty("field", context.FieldName)); |
|||
|
|||
throw ex; |
|||
} |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using GraphQL.DataLoader; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
|||
{ |
|||
public static class Extensions |
|||
{ |
|||
public static IEnumerable<(T Field, string Name, string Type)> SafeFields<T>(this IEnumerable<T> fields) where T : IField |
|||
{ |
|||
var allFields = |
|||
fields.ForApi() |
|||
.Select(f => (Field: f, Name: f.Name.ToCamelCase(), Type: f.TypeName())).GroupBy(x => x.Name) |
|||
.Select(g => |
|||
{ |
|||
return g.Select((f, i) => (f.Field, f.Name.SafeString(i), f.Type.SafeString(i))); |
|||
}) |
|||
.SelectMany(x => x); |
|||
|
|||
return allFields; |
|||
} |
|||
|
|||
private static string SafeString(this string value, int index) |
|||
{ |
|||
if (index > 0) |
|||
{ |
|||
return value + (index + 1); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
public static async Task<IReadOnlyList<T>> LoadManyAsync<TKey, T>(this IDataLoader<TKey, T> dataLoader, ICollection<TKey> keys) where T : class |
|||
{ |
|||
var contents = await Task.WhenAll(keys.Select(x => dataLoader.LoadAsync(x))); |
|||
|
|||
return contents.Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public interface IContentWorkflow |
|||
{ |
|||
Task<Status> GetInitialStatusAsync(ISchemaEntity schema); |
|||
|
|||
Task<bool> CanMoveToAsync(IContentEntity content, Status next); |
|||
|
|||
Task<bool> CanUpdateAsync(IContentEntity content); |
|||
|
|||
Task<Status[]> GetNextsAsync(IContentEntity content); |
|||
|
|||
Task<Status[]> GetAllAsync(ISchemaEntity schema); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public static class EntityExtensions |
|||
{ |
|||
public static NamedId<Guid> NamedId(this IAppEntity entity) |
|||
{ |
|||
return new NamedId<Guid>(entity.Id, entity.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using FluentFTP; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class FTPAssetStore : IAssetStore, IInitializable |
|||
{ |
|||
private readonly string path; |
|||
private readonly ISemanticLog log; |
|||
private readonly Func<IFtpClient> factory; |
|||
|
|||
public FTPAssetStore(Func<IFtpClient> factory, string path, ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
Guard.NotNullOrEmpty(path, nameof(path)); |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
this.factory = factory; |
|||
this.path = path; |
|||
|
|||
this.log = log; |
|||
} |
|||
|
|||
public string GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public async Task InitializeAsync(CancellationToken ct = default) |
|||
{ |
|||
using (var client = factory()) |
|||
{ |
|||
await client.ConnectAsync(ct); |
|||
|
|||
if (!await client.DirectoryExistsAsync(path, ct)) |
|||
{ |
|||
await client.CreateDirectoryAsync(path, ct); |
|||
} |
|||
} |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "FTPAssetStoreConfigured") |
|||
.WriteProperty("path", path)); |
|||
} |
|||
|
|||
public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); |
|||
Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
var tempPath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); |
|||
|
|||
using (var stream = new FileStream(tempPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose)) |
|||
{ |
|||
await DownloadAsync(client, sourceFileName, stream, ct); |
|||
await UploadAsync(client, targetFileName, stream, false, ct); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task DownloadAsync(string fileName, Stream stream, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
await DownloadAsync(client, fileName, stream, ct); |
|||
} |
|||
} |
|||
|
|||
public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
await UploadAsync(client, fileName, stream, overwrite, ct); |
|||
} |
|||
} |
|||
|
|||
private static async Task DownloadAsync(IFtpClient client, string fileName, Stream stream, CancellationToken ct) |
|||
{ |
|||
try |
|||
{ |
|||
await client.DownloadAsync(stream, fileName, token: ct); |
|||
} |
|||
catch (FtpException ex) when (IsNotFound(ex)) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
private static async Task UploadAsync(IFtpClient client, string fileName, Stream stream, bool overwrite, CancellationToken ct) |
|||
{ |
|||
if (!overwrite && await client.FileExistsAsync(fileName, ct)) |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName); |
|||
} |
|||
|
|||
await client.UploadAsync(stream, fileName, overwrite ? FtpExists.Overwrite : FtpExists.Skip, true, null, ct); |
|||
} |
|||
|
|||
public async Task DeleteAsync(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
try |
|||
{ |
|||
await client.DeleteFileAsync(fileName); |
|||
} |
|||
catch (FtpException ex) |
|||
{ |
|||
if (!IsNotFound(ex)) |
|||
{ |
|||
throw ex; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IFtpClient GetFtpClient() |
|||
{ |
|||
var client = factory(); |
|||
|
|||
client.Connect(); |
|||
client.SetWorkingDirectory(path); |
|||
|
|||
return client; |
|||
} |
|||
|
|||
private static bool IsNotFound(Exception exception) |
|||
{ |
|||
if (exception is FtpCommandException command) |
|||
{ |
|||
return command.CompletionCode == "550"; |
|||
} |
|||
|
|||
return exception.InnerException != null && IsNotFound(exception.InnerException); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
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<PermissionFeature>(); |
|||
|
|||
if (feature == null) |
|||
{ |
|||
feature = new PermissionFeature(httpContext.User.Permissions()); |
|||
|
|||
httpContext.Features.Set(feature); |
|||
} |
|||
|
|||
return feature.Permissions; |
|||
} |
|||
|
|||
public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) |
|||
{ |
|||
return httpContext.Permissions().Includes(permission) || permissions?.Includes(permission) == true; |
|||
} |
|||
|
|||
public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*", PermissionSet permissions = null) |
|||
{ |
|||
return httpContext.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); |
|||
} |
|||
|
|||
public static bool HasPermission(this ApiController controller, Permission permission, PermissionSet permissions = null) |
|||
{ |
|||
return controller.HttpContext.HasPermission(permission, permissions); |
|||
} |
|||
|
|||
public static bool HasPermission(this ApiController controller, string id, string app = "*", string schema = "*", PermissionSet permissions = null) |
|||
{ |
|||
if (app == "*") |
|||
{ |
|||
if (controller.RouteData.Values.TryGetValue("app", out var value) && value is string s) |
|||
{ |
|||
app = s; |
|||
} |
|||
} |
|||
|
|||
if (schema == "*") |
|||
{ |
|||
if (controller.RouteData.Values.TryGetValue("name", out var value) && value is string s) |
|||
{ |
|||
schema = s; |
|||
} |
|||
} |
|||
|
|||
return controller.HasPermission(Shared.Permissions.ForApp(id, app, schema), permissions); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Web |
|||
{ |
|||
public abstract class Resource |
|||
{ |
|||
[JsonProperty("_links")] |
|||
[Required] |
|||
[Display(Description = "The links.")] |
|||
public Dictionary<string, ResourceLink> Links { get; } = new Dictionary<string, ResourceLink>(); |
|||
|
|||
public void AddSelfLink(string href) |
|||
{ |
|||
AddGetLink("self", href); |
|||
} |
|||
|
|||
public void AddGetLink(string rel, string href) |
|||
{ |
|||
AddLink(rel, "GET", href); |
|||
} |
|||
|
|||
public void AddPatchLink(string rel, string href) |
|||
{ |
|||
AddLink(rel, "PATCH", href); |
|||
} |
|||
|
|||
public void AddPostLink(string rel, string href) |
|||
{ |
|||
AddLink(rel, "POST", href); |
|||
} |
|||
|
|||
public void AddPutLink(string rel, string href) |
|||
{ |
|||
AddLink(rel, "PUT", href); |
|||
} |
|||
|
|||
public void AddDeleteLink(string rel, string href) |
|||
{ |
|||
AddLink(rel, "DELETE", href); |
|||
} |
|||
|
|||
public void AddLink(string rel, string method, string href) |
|||
{ |
|||
Guard.NotNullOrEmpty(rel, nameof(rel)); |
|||
Guard.NotNullOrEmpty(href, nameof(href)); |
|||
Guard.NotNullOrEmpty(method, nameof(method)); |
|||
|
|||
Links[rel] = new ResourceLink { Href = href, Method = method }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
|
|||
namespace Squidex.Web |
|||
{ |
|||
public static class UrlHelperExtensions |
|||
{ |
|||
private static class NameOf<T> |
|||
{ |
|||
public static readonly string Controller; |
|||
|
|||
static NameOf() |
|||
{ |
|||
const string suffix = "Controller"; |
|||
|
|||
var name = typeof(T).Name; |
|||
|
|||
if (name.EndsWith(suffix)) |
|||
{ |
|||
name = name.Substring(0, name.Length - suffix.Length); |
|||
} |
|||
|
|||
Controller = name; |
|||
} |
|||
} |
|||
|
|||
public static string Url<T>(this IUrlHelper urlHelper, Func<T, string> action, object values = null) where T : Controller |
|||
{ |
|||
return urlHelper.Action(action(null), NameOf<T>.Controller, values); |
|||
} |
|||
|
|||
public static string Url<T>(this Controller controller, Func<T, string> action, object values = null) where T : Controller |
|||
{ |
|||
return controller.Url.Url<T>(action, values); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using NJsonSchema; |
|||
using NSwag; |
|||
using NSwag.SwaggerGeneration.Processors; |
|||
using NSwag.SwaggerGeneration.Processors.Contexts; |
|||
using Squidex.ClientLibrary.Management; |
|||
using Squidex.Pipeline.Swagger; |
|||
|
|||
namespace Squidex.Areas.Api.Config.Swagger |
|||
{ |
|||
public sealed class ErrorDtoProcessor : IDocumentProcessor |
|||
{ |
|||
public async Task ProcessAsync(DocumentProcessorContext context) |
|||
{ |
|||
var errorSchema = await GetErrorSchemaAsync(context); |
|||
|
|||
foreach (var operation in context.Document.Paths.Values.SelectMany(x => x.Values)) |
|||
{ |
|||
AddErrorResponses(operation, errorSchema); |
|||
|
|||
CleanupResponses(operation); |
|||
} |
|||
} |
|||
|
|||
private static void AddErrorResponses(SwaggerOperation operation, JsonSchema4 errorSchema) |
|||
{ |
|||
if (!operation.Responses.ContainsKey("500")) |
|||
{ |
|||
operation.AddResponse("500", "Operation failed", errorSchema); |
|||
} |
|||
|
|||
foreach (var (code, response) in operation.Responses) |
|||
{ |
|||
if (code != "404" && code.StartsWith("4", StringComparison.OrdinalIgnoreCase) && response.Schema == null) |
|||
{ |
|||
response.Schema = errorSchema; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void CleanupResponses(SwaggerOperation operation) |
|||
{ |
|||
foreach (var (code, response) in operation.Responses.ToList()) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(response.Description) || |
|||
response.Description?.Contains("=>") == true || |
|||
response.Description?.Contains("=>") == true) |
|||
{ |
|||
operation.Responses.Remove(code); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private Task<JsonSchema4> GetErrorSchemaAsync(DocumentProcessorContext context) |
|||
{ |
|||
var errorType = typeof(ErrorDto); |
|||
|
|||
return context.SchemaGenerator.GenerateWithReferenceAsync<JsonSchema4>(errorType, Enumerable.Empty<Attribute>(), context.SchemaResolver); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue