mirror of https://github.com/Squidex/squidex.git
Browse Source
* Migration to .NET Core 3.0 * Migration to Orleans 3.0 * Nullable support * Better separation of frontend and backend.pull/443/head
committed by
GitHub
3442 changed files with 94228 additions and 93724 deletions
@ -1,37 +0,0 @@ |
|||||
FROM squidex/dotnet:2.2-sdk-chromium-phantomjs-node as builder |
|
||||
|
|
||||
ARG SQUIDEX__VERSION=1.0.0 |
|
||||
|
|
||||
WORKDIR /src |
|
||||
|
|
||||
# Copy Node project files. |
|
||||
COPY src/Squidex/package*.json /tmp/ |
|
||||
|
|
||||
# Install Node packages |
|
||||
RUN cd /tmp && npm install --loglevel=error |
|
||||
|
|
||||
# Copy Dotnet project files. |
|
||||
COPY /**/**/*.csproj /tmp/ |
|
||||
# Copy nuget.config for package sources. |
|
||||
COPY NuGet.Config /tmp/ |
|
||||
|
|
||||
# Install Dotnet packages |
|
||||
RUN bash -c 'pushd /tmp; for p in *.csproj; do dotnet restore $p --verbosity quiet; true; done; popd' |
|
||||
|
|
||||
COPY . . |
|
||||
|
|
||||
# Build Frontend |
|
||||
RUN cp -a /tmp/node_modules src/Squidex/ \ |
|
||||
&& cd src/Squidex \ |
|
||||
&& npm run test:coverage \ |
|
||||
&& npm run build |
|
||||
|
|
||||
# Test Backend |
|
||||
RUN dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj --filter Category!=Dependencies \ |
|
||||
&& dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \ |
|
||||
&& dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \ |
|
||||
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj \ |
|
||||
&& dotnet test tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj |
|
||||
|
|
||||
# Publish |
|
||||
RUN dotnet publish src/Squidex/Squidex.csproj --output /out/ --configuration Release -p:version=$SQUIDEX__VERSION |
|
||||
@ -0,0 +1,16 @@ |
|||||
|
[*.cs] |
||||
|
|
||||
|
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. |
||||
|
dotnet_diagnostic.CS8618.severity = none |
||||
|
|
||||
|
# SA1011: Closing square brackets should be spaced correctly |
||||
|
dotnet_diagnostic.SA1011.severity = none |
||||
|
|
||||
|
# IDE0066: Convert switch statement to expression |
||||
|
csharp_style_prefer_switch_expression = false:suggestion |
||||
|
|
||||
|
# IDE0010: Add missing cases |
||||
|
dotnet_diagnostic.IDE0010.severity = none |
||||
|
|
||||
|
# IDE0063: Use simple 'using' statement |
||||
|
csharp_prefer_simple_using_statement = false:suggestion |
||||
@ -0,0 +1,135 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Algolia.Search; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
||||
|
using AlgoliaIndex = Algolia.Search.Index; |
||||
|
|
||||
|
#pragma warning disable IDE0059 // Value assigned to symbol is never used
|
||||
|
|
||||
|
namespace Squidex.Extensions.Actions.Algolia |
||||
|
{ |
||||
|
public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, AlgoliaJob> |
||||
|
{ |
||||
|
private readonly ClientPool<(string AppId, string ApiKey, string IndexName), AlgoliaIndex> clients; |
||||
|
|
||||
|
public AlgoliaActionHandler(RuleEventFormatter formatter) |
||||
|
: base(formatter) |
||||
|
{ |
||||
|
clients = new ClientPool<(string AppId, string ApiKey, string IndexName), AlgoliaIndex>(key => |
||||
|
{ |
||||
|
var client = new AlgoliaClient(key.AppId, key.ApiKey); |
||||
|
|
||||
|
return client.InitIndex(key.IndexName); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, AlgoliaJob Data) CreateJob(EnrichedEvent @event, AlgoliaAction action) |
||||
|
{ |
||||
|
if (@event is EnrichedContentEvent contentEvent) |
||||
|
{ |
||||
|
var contentId = contentEvent.Id.ToString(); |
||||
|
|
||||
|
var ruleDescription = string.Empty; |
||||
|
var ruleJob = new AlgoliaJob |
||||
|
{ |
||||
|
AppId = action.AppId, |
||||
|
ApiKey = action.ApiKey, |
||||
|
ContentId = contentId, |
||||
|
IndexName = Format(action.IndexName, @event) |
||||
|
}; |
||||
|
|
||||
|
if (contentEvent.Type == EnrichedContentEventType.Deleted || |
||||
|
contentEvent.Type == EnrichedContentEventType.Unpublished) |
||||
|
{ |
||||
|
ruleDescription = $"Delete entry from Algolia index: {action.IndexName}"; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; |
||||
|
|
||||
|
JObject json; |
||||
|
try |
||||
|
{ |
||||
|
string jsonString; |
||||
|
|
||||
|
if (!string.IsNullOrEmpty(action.Document)) |
||||
|
{ |
||||
|
jsonString = Format(action.Document, @event)?.Trim(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
jsonString = ToJson(contentEvent); |
||||
|
} |
||||
|
|
||||
|
json = JObject.Parse(jsonString); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
json = new JObject(new JProperty("error", $"Invalid JSON: {ex.Message}")); |
||||
|
} |
||||
|
|
||||
|
ruleJob.Content = json; |
||||
|
ruleJob.Content["objectID"] = contentId; |
||||
|
} |
||||
|
|
||||
|
return (ruleDescription, ruleJob); |
||||
|
} |
||||
|
|
||||
|
return ("Ignore", new AlgoliaJob()); |
||||
|
} |
||||
|
|
||||
|
protected override async Task<Result> ExecuteJobAsync(AlgoliaJob job, CancellationToken ct = default) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(job.AppId)) |
||||
|
{ |
||||
|
return Result.Ignored(); |
||||
|
} |
||||
|
|
||||
|
var index = clients.GetClient((job.AppId, job.ApiKey, job.IndexName)); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (job.Content != null) |
||||
|
{ |
||||
|
var response = await index.PartialUpdateObjectAsync(job.Content, true, ct); |
||||
|
|
||||
|
return Result.Success(response.ToString(Formatting.Indented)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var response = await index.DeleteObjectAsync(job.ContentId, ct); |
||||
|
|
||||
|
return Result.Success(response.ToString(Formatting.Indented)); |
||||
|
} |
||||
|
} |
||||
|
catch (AlgoliaException ex) |
||||
|
{ |
||||
|
return Result.Failed(ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public sealed class AlgoliaJob |
||||
|
{ |
||||
|
public string AppId { get; set; } |
||||
|
|
||||
|
public string ApiKey { get; set; } |
||||
|
|
||||
|
public string ContentId { get; set; } |
||||
|
|
||||
|
public string IndexName { get; set; } |
||||
|
|
||||
|
public JObject Content { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Extensions.Actions.Fastly |
||||
|
{ |
||||
|
public sealed class FastlyActionHandler : RuleActionHandler<FastlyAction, FastlyJob> |
||||
|
{ |
||||
|
private const string Description = "Purge key in fastly"; |
||||
|
|
||||
|
private readonly IHttpClientFactory httpClientFactory; |
||||
|
|
||||
|
public FastlyActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) |
||||
|
: base(formatter) |
||||
|
{ |
||||
|
Guard.NotNull(httpClientFactory); |
||||
|
|
||||
|
this.httpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action) |
||||
|
{ |
||||
|
var id = @event is IEnrichedEntityEvent entityEvent ? entityEvent.Id.ToString() : string.Empty; |
||||
|
|
||||
|
var ruleJob = new FastlyJob |
||||
|
{ |
||||
|
Key = id, |
||||
|
FastlyApiKey = action.ApiKey, |
||||
|
FastlyServiceID = action.ServiceId |
||||
|
}; |
||||
|
|
||||
|
return (Description, ruleJob); |
||||
|
} |
||||
|
|
||||
|
protected override async Task<Result> ExecuteJobAsync(FastlyJob job, CancellationToken ct = default) |
||||
|
{ |
||||
|
using (var httpClient = httpClientFactory.CreateClient()) |
||||
|
{ |
||||
|
httpClient.Timeout = TimeSpan.FromSeconds(2); |
||||
|
|
||||
|
var requestUrl = $"https://api.fastly.com/service/{job.FastlyServiceID}/purge/{job.Key}"; |
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); |
||||
|
|
||||
|
request.Headers.Add("Fastly-Key", job.FastlyApiKey); |
||||
|
|
||||
|
return await httpClient.OneWayRequestAsync(request, ct: ct); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public sealed class FastlyJob |
||||
|
{ |
||||
|
public string FastlyApiKey { get; set; } |
||||
|
|
||||
|
public string FastlyServiceID { get; set; } |
||||
|
|
||||
|
public string Key { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Extensions.Actions.Slack |
||||
|
{ |
||||
|
public sealed class SlackActionHandler : RuleActionHandler<SlackAction, SlackJob> |
||||
|
{ |
||||
|
private const string Description = "Send message to slack"; |
||||
|
|
||||
|
private readonly IHttpClientFactory httpClientFactory; |
||||
|
|
||||
|
public SlackActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) |
||||
|
: base(formatter) |
||||
|
{ |
||||
|
Guard.NotNull(httpClientFactory); |
||||
|
|
||||
|
this.httpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) |
||||
|
{ |
||||
|
var body = new { text = Format(action.Text, @event) }; |
||||
|
|
||||
|
var ruleJob = new SlackJob |
||||
|
{ |
||||
|
RequestUrl = action.WebhookUrl.ToString(), |
||||
|
RequestBody = ToJson(body) |
||||
|
}; |
||||
|
|
||||
|
return (Description, ruleJob); |
||||
|
} |
||||
|
|
||||
|
protected override async Task<Result> ExecuteJobAsync(SlackJob job, CancellationToken ct = default) |
||||
|
{ |
||||
|
using (var httpClient = httpClientFactory.CreateClient()) |
||||
|
{ |
||||
|
httpClient.Timeout = TimeSpan.FromSeconds(2); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) |
||||
|
{ |
||||
|
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") |
||||
|
}; |
||||
|
|
||||
|
return await httpClient.OneWayRequestAsync(request, job.RequestBody, ct); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public sealed class SlackJob |
||||
|
{ |
||||
|
public string RequestUrl { get; set; } |
||||
|
|
||||
|
public string RequestBody { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using CoreTweet; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Extensions.Actions.Twitter |
||||
|
{ |
||||
|
public sealed class TweetActionHandler : RuleActionHandler<TweetAction, TweetJob> |
||||
|
{ |
||||
|
private const string Description = "Send a tweet"; |
||||
|
|
||||
|
private readonly TwitterOptions twitterOptions; |
||||
|
|
||||
|
public TweetActionHandler(RuleEventFormatter formatter, IOptions<TwitterOptions> twitterOptions) |
||||
|
: base(formatter) |
||||
|
{ |
||||
|
Guard.NotNull(twitterOptions); |
||||
|
|
||||
|
this.twitterOptions = twitterOptions.Value; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, TweetJob Data) CreateJob(EnrichedEvent @event, TweetAction action) |
||||
|
{ |
||||
|
var ruleJob = new TweetJob |
||||
|
{ |
||||
|
Text = Format(action.Text, @event), |
||||
|
AccessToken = action.AccessToken, |
||||
|
AccessSecret = action.AccessSecret |
||||
|
}; |
||||
|
|
||||
|
return (Description, ruleJob); |
||||
|
} |
||||
|
|
||||
|
protected override async Task<Result> ExecuteJobAsync(TweetJob job, CancellationToken ct = default) |
||||
|
{ |
||||
|
var tokens = Tokens.Create( |
||||
|
twitterOptions.ClientId, |
||||
|
twitterOptions.ClientSecret, |
||||
|
job.AccessToken, |
||||
|
job.AccessSecret); |
||||
|
|
||||
|
var request = new Dictionary<string, object> |
||||
|
{ |
||||
|
["status"] = job.Text |
||||
|
}; |
||||
|
|
||||
|
await tokens.Statuses.UpdateAsync(request, ct); |
||||
|
|
||||
|
return Result.Success($"Tweeted: {job.Text}"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public sealed class TweetJob |
||||
|
{ |
||||
|
public string AccessToken { get; set; } |
||||
|
|
||||
|
public string AccessSecret { get; set; } |
||||
|
|
||||
|
public string Text { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Extensions.Actions.Webhook |
||||
|
{ |
||||
|
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, WebhookJob> |
||||
|
{ |
||||
|
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2); |
||||
|
private readonly IHttpClientFactory httpClientFactory; |
||||
|
|
||||
|
public WebhookActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) |
||||
|
: base(formatter) |
||||
|
{ |
||||
|
Guard.NotNull(httpClientFactory); |
||||
|
|
||||
|
this.httpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, WebhookJob Data) CreateJob(EnrichedEvent @event, WebhookAction action) |
||||
|
{ |
||||
|
string requestBody; |
||||
|
|
||||
|
if (!string.IsNullOrEmpty(action.Payload)) |
||||
|
{ |
||||
|
requestBody = Format(action.Payload, @event); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
requestBody = ToEnvelopeJson(@event); |
||||
|
} |
||||
|
|
||||
|
var requestUrl = Format(action.Url, @event); |
||||
|
|
||||
|
var ruleDescription = $"Send event to webhook '{requestUrl}'"; |
||||
|
var ruleJob = new WebhookJob |
||||
|
{ |
||||
|
RequestUrl = Format(action.Url.ToString(), @event), |
||||
|
RequestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(), |
||||
|
RequestBody = requestBody |
||||
|
}; |
||||
|
|
||||
|
return (ruleDescription, ruleJob); |
||||
|
} |
||||
|
|
||||
|
protected override async Task<Result> ExecuteJobAsync(WebhookJob job, CancellationToken ct = default) |
||||
|
{ |
||||
|
using (var httpClient = httpClientFactory.CreateClient()) |
||||
|
{ |
||||
|
httpClient.Timeout = DefaultTimeout; |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) |
||||
|
{ |
||||
|
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") |
||||
|
}; |
||||
|
|
||||
|
request.Headers.Add("X-Signature", job.RequestSignature); |
||||
|
request.Headers.Add("X-Application", "Squidex Webhook"); |
||||
|
request.Headers.Add("User-Agent", "Squidex Webhook"); |
||||
|
|
||||
|
return await httpClient.OneWayRequestAsync(request, job.RequestBody, ct); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public sealed class WebhookJob |
||||
|
{ |
||||
|
public string RequestUrl { get; set; } |
||||
|
|
||||
|
public string RequestSignature { get; set; } |
||||
|
|
||||
|
public string RequestBody { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.0</TargetFramework> |
||||
|
<LangVersion>8.0</LangVersion> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Squidex.Web\Squidex.Web.csproj" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Algolia.Search" Version="5.3.1" /> |
||||
|
<PackageReference Include="Confluent.Kafka" Version="1.2.1" /> |
||||
|
<PackageReference Include="CoreTweet" Version="1.0.0.483" /> |
||||
|
<PackageReference Include="Elasticsearch.Net" Version="6.8.1" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.0.0" /> |
||||
|
<PackageReference Include="Microsoft.OData.Core" Version="7.6.1" /> |
||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> |
||||
|
<PackageReference Include="NodaTime" Version="2.4.7" /> |
||||
|
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> |
||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> |
||||
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.6.0" /> |
||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
||||
|
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" /> |
||||
|
</ItemGroup> |
||||
|
<PropertyGroup> |
||||
|
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,46 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppClient : Named |
||||
|
{ |
||||
|
public string Role { get; } |
||||
|
|
||||
|
public string Secret { get; } |
||||
|
|
||||
|
public AppClient(string name, string secret, string role) |
||||
|
: base(name) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(secret); |
||||
|
Guard.NotNullOrEmpty(role); |
||||
|
|
||||
|
Role = role; |
||||
|
|
||||
|
Secret = secret; |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClient Update(string newRole) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(newRole); |
||||
|
|
||||
|
return new AppClient(Name, Secret, newRole); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClient Rename(string newName) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(newName); |
||||
|
|
||||
|
return new AppClient(newName, Secret, Role); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppClients : ArrayDictionary<string, AppClient> |
||||
|
{ |
||||
|
public static readonly AppClients Empty = new AppClients(); |
||||
|
|
||||
|
private AppClients() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AppClients(KeyValuePair<string, AppClient>[] items) |
||||
|
: base(items) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClients Revoke(string id) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id); |
||||
|
|
||||
|
return new AppClients(Without(id)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClients Add(string id, AppClient client) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id); |
||||
|
Guard.NotNull(client); |
||||
|
|
||||
|
if (ContainsKey(id)) |
||||
|
{ |
||||
|
throw new ArgumentException("Id already exists.", nameof(id)); |
||||
|
} |
||||
|
|
||||
|
return new AppClients(With(id, client)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClients Add(string id, string secret) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id); |
||||
|
|
||||
|
if (ContainsKey(id)) |
||||
|
{ |
||||
|
throw new ArgumentException("Id already exists.", nameof(id)); |
||||
|
} |
||||
|
|
||||
|
return new AppClients(With(id, new AppClient(id, secret, Role.Editor))); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClients Rename(string id, string newName) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id); |
||||
|
|
||||
|
if (!TryGetValue(id, out var client)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new AppClients(With(id, client.Rename(newName))); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppClients Update(string id, string role) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(id); |
||||
|
|
||||
|
if (!TryGetValue(id, out var client)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new AppClients(With(id, client.Update(role))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppContributors : ArrayDictionary<string, string> |
||||
|
{ |
||||
|
public static readonly AppContributors Empty = new AppContributors(); |
||||
|
|
||||
|
private AppContributors() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AppContributors(KeyValuePair<string, string>[] items) |
||||
|
: base(items) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppContributors Assign(string contributorId, string role) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(contributorId); |
||||
|
Guard.NotNullOrEmpty(role); |
||||
|
|
||||
|
return new AppContributors(With(contributorId, role)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppContributors Remove(string contributorId) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(contributorId); |
||||
|
|
||||
|
return new AppContributors(Without(contributorId)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppImage |
||||
|
{ |
||||
|
public string MimeType { get; } |
||||
|
|
||||
|
public string Etag { get; } |
||||
|
|
||||
|
public AppImage(string mimeType, string? etag = null) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(mimeType); |
||||
|
|
||||
|
MimeType = mimeType; |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(etag)) |
||||
|
{ |
||||
|
Etag = RandomHash.Simple(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Etag = etag; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppPattern : Named |
||||
|
{ |
||||
|
public string Pattern { get; } |
||||
|
|
||||
|
public string? Message { get; } |
||||
|
|
||||
|
public AppPattern(string name, string pattern, string? message = null) |
||||
|
: base(name) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(pattern); |
||||
|
|
||||
|
Pattern = pattern; |
||||
|
|
||||
|
Message = message; |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPattern Update(string newName, string newPattern, string? newMessage) |
||||
|
{ |
||||
|
return new AppPattern(newName, newPattern, newMessage); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppPatterns : ArrayDictionary<Guid, AppPattern> |
||||
|
{ |
||||
|
public static readonly AppPatterns Empty = new AppPatterns(); |
||||
|
|
||||
|
private AppPatterns() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AppPatterns(KeyValuePair<Guid, AppPattern>[] items) |
||||
|
: base(items) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Remove(Guid id) |
||||
|
{ |
||||
|
return new AppPatterns(Without(id)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Add(Guid id, string name, string pattern, string? message) |
||||
|
{ |
||||
|
var newPattern = new AppPattern(name, pattern, message); |
||||
|
|
||||
|
if (ContainsKey(id)) |
||||
|
{ |
||||
|
throw new ArgumentException("Id already exists.", nameof(id)); |
||||
|
} |
||||
|
|
||||
|
return new AppPatterns(With(id, newPattern)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Update(Guid id, string name, string pattern, string? message) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name); |
||||
|
Guard.NotNullOrEmpty(pattern); |
||||
|
|
||||
|
if (!TryGetValue(id, out var appPattern)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new AppPatterns(With(id, appPattern.Update(name, pattern, message))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppPlan |
||||
|
{ |
||||
|
public RefToken Owner { get; } |
||||
|
|
||||
|
public string PlanId { get; } |
||||
|
|
||||
|
public AppPlan(RefToken owner, string planId) |
||||
|
{ |
||||
|
Guard.NotNull(owner); |
||||
|
Guard.NotNullOrEmpty(planId); |
||||
|
|
||||
|
Owner = owner; |
||||
|
|
||||
|
PlanId = planId; |
||||
|
} |
||||
|
|
||||
|
public static AppPlan? Build(RefToken owner, string planId) |
||||
|
{ |
||||
|
if (planId == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return new AppPlan(owner, planId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps.Json |
||||
|
{ |
||||
|
public class JsonAppPattern |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string? Message { get; set; } |
||||
|
|
||||
|
public JsonAppPattern() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public JsonAppPattern(AppPattern pattern) |
||||
|
{ |
||||
|
SimpleMapper.Map(pattern, this); |
||||
|
} |
||||
|
|
||||
|
public AppPattern ToPattern() |
||||
|
{ |
||||
|
return new AppPattern(Name, Pattern, Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps.Json |
||||
|
{ |
||||
|
public sealed class JsonLanguagesConfig |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public Dictionary<string, JsonLanguageConfig> Languages { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public Language? Master { get; set; } |
||||
|
|
||||
|
public JsonLanguagesConfig() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public JsonLanguagesConfig(LanguagesConfig value) |
||||
|
{ |
||||
|
Languages = new Dictionary<string, JsonLanguageConfig>(value.Count); |
||||
|
|
||||
|
foreach (LanguageConfig config in value) |
||||
|
{ |
||||
|
Languages.Add(config.Language, new JsonLanguageConfig(config)); |
||||
|
} |
||||
|
|
||||
|
Master = value.Master?.Language; |
||||
|
} |
||||
|
|
||||
|
public LanguagesConfig ToConfig() |
||||
|
{ |
||||
|
var languagesConfig = new LanguageConfig[Languages?.Count ?? 0]; |
||||
|
|
||||
|
if (Languages != null) |
||||
|
{ |
||||
|
var i = 0; |
||||
|
|
||||
|
foreach (var config in Languages) |
||||
|
{ |
||||
|
languagesConfig[i++] = config.Value.ToConfig(config.Key); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var result = LanguagesConfig.Build(languagesConfig); |
||||
|
|
||||
|
if (Master != null) |
||||
|
{ |
||||
|
result = result.MakeMaster(Master); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class LanguageConfig : IFieldPartitionItem |
||||
|
{ |
||||
|
private readonly Language language; |
||||
|
private readonly Language[] languageFallbacks; |
||||
|
|
||||
|
public bool IsOptional { get; } |
||||
|
|
||||
|
public Language Language |
||||
|
{ |
||||
|
get { return language; } |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<Language> LanguageFallbacks |
||||
|
{ |
||||
|
get { return languageFallbacks; } |
||||
|
} |
||||
|
|
||||
|
string IFieldPartitionItem.Key |
||||
|
{ |
||||
|
get { return language.Iso2Code; } |
||||
|
} |
||||
|
|
||||
|
string IFieldPartitionItem.Name |
||||
|
{ |
||||
|
get { return language.EnglishName; } |
||||
|
} |
||||
|
|
||||
|
IEnumerable<string> IFieldPartitionItem.Fallback |
||||
|
{ |
||||
|
get { return LanguageFallbacks.Select(x => x.Iso2Code); } |
||||
|
} |
||||
|
|
||||
|
public LanguageConfig(Language language, bool isOptional = false, IEnumerable<Language>? fallback = null) |
||||
|
: this(language, isOptional, fallback?.ToArray()) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public LanguageConfig(Language language, bool isOptional = false, params Language[]? fallback) |
||||
|
{ |
||||
|
Guard.NotNull(language); |
||||
|
|
||||
|
IsOptional = isOptional; |
||||
|
|
||||
|
this.language = language; |
||||
|
this.languageFallbacks = fallback ?? Array.Empty<Language>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,179 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class LanguagesConfig : IFieldPartitioning |
||||
|
{ |
||||
|
public static readonly LanguagesConfig English = Build(Language.EN); |
||||
|
|
||||
|
private readonly ArrayDictionary<Language, LanguageConfig> languages; |
||||
|
private readonly LanguageConfig master; |
||||
|
|
||||
|
public LanguageConfig Master |
||||
|
{ |
||||
|
get { return master; } |
||||
|
} |
||||
|
|
||||
|
IFieldPartitionItem IFieldPartitioning.Master |
||||
|
{ |
||||
|
get { return master; } |
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
return languages.Values.GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator() |
||||
|
{ |
||||
|
return languages.Values.GetEnumerator(); |
||||
|
} |
||||
|
|
||||
|
public int Count |
||||
|
{ |
||||
|
get { return languages.Count; } |
||||
|
} |
||||
|
|
||||
|
private LanguagesConfig(ArrayDictionary<Language, LanguageConfig> languages, LanguageConfig master, bool checkMaster = true) |
||||
|
{ |
||||
|
if (checkMaster) |
||||
|
{ |
||||
|
this.master = master ?? throw new InvalidOperationException("Config has no master language."); |
||||
|
} |
||||
|
|
||||
|
foreach (var languageConfig in languages.Values) |
||||
|
{ |
||||
|
foreach (var fallback in languageConfig.LanguageFallbacks) |
||||
|
{ |
||||
|
if (!languages.ContainsKey(fallback)) |
||||
|
{ |
||||
|
var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'"; |
||||
|
|
||||
|
throw new InvalidOperationException(message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.languages = languages; |
||||
|
} |
||||
|
|
||||
|
public static LanguagesConfig Build(ICollection<LanguageConfig> configs) |
||||
|
{ |
||||
|
Guard.NotNull(configs); |
||||
|
|
||||
|
return new LanguagesConfig(configs.ToArrayDictionary(x => x.Language), configs.FirstOrDefault()); |
||||
|
} |
||||
|
|
||||
|
public static LanguagesConfig Build(params LanguageConfig[] configs) |
||||
|
{ |
||||
|
return Build(configs?.ToList()!); |
||||
|
} |
||||
|
|
||||
|
public static LanguagesConfig Build(params Language[] languages) |
||||
|
{ |
||||
|
return Build(languages?.Select(x => new LanguageConfig(x)).ToList()!); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public LanguagesConfig MakeMaster(Language language) |
||||
|
{ |
||||
|
Guard.NotNull(language); |
||||
|
|
||||
|
return new LanguagesConfig(languages, languages[language]); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public LanguagesConfig Set(Language language, bool isOptional = false, IEnumerable<Language>? fallback = null) |
||||
|
{ |
||||
|
Guard.NotNull(language); |
||||
|
|
||||
|
return Set(new LanguageConfig(language, isOptional, fallback)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public LanguagesConfig Set(LanguageConfig config) |
||||
|
{ |
||||
|
Guard.NotNull(config); |
||||
|
|
||||
|
var newLanguages = |
||||
|
new ArrayDictionary<Language, LanguageConfig>(languages.With(config.Language, config)); |
||||
|
|
||||
|
var newMaster = Master?.Language == config.Language ? config : Master; |
||||
|
|
||||
|
return new LanguagesConfig(newLanguages, newMaster!); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public LanguagesConfig Remove(Language language) |
||||
|
{ |
||||
|
Guard.NotNull(language); |
||||
|
|
||||
|
var newLanguages = |
||||
|
languages.Values.Where(x => x.Language != language) |
||||
|
.Select(config => new LanguageConfig( |
||||
|
config.Language, |
||||
|
config.IsOptional, |
||||
|
config.LanguageFallbacks.Except(new[] { language }))) |
||||
|
.ToArrayDictionary(x => x.Language); |
||||
|
|
||||
|
var newMaster = |
||||
|
newLanguages.Values.FirstOrDefault(x => x.Language == Master.Language) ?? |
||||
|
newLanguages.Values.FirstOrDefault(); |
||||
|
|
||||
|
return new LanguagesConfig(newLanguages, newMaster); |
||||
|
} |
||||
|
|
||||
|
public bool Contains(Language language) |
||||
|
{ |
||||
|
return language != null && languages.ContainsKey(language); |
||||
|
} |
||||
|
|
||||
|
public bool TryGetConfig(Language language, [MaybeNullWhen(false)] out LanguageConfig config) |
||||
|
{ |
||||
|
return languages.TryGetValue(language, out config!); |
||||
|
} |
||||
|
|
||||
|
public bool TryGetItem(string key, [MaybeNullWhen(false)] out IFieldPartitionItem item) |
||||
|
{ |
||||
|
if (Language.IsValidLanguage(key) && languages.TryGetValue(key, out var value)) |
||||
|
{ |
||||
|
item = value; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
item = null!; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public PartitionResolver ToResolver() |
||||
|
{ |
||||
|
return partitioning => |
||||
|
{ |
||||
|
if (partitioning.Equals(Partitioning.Invariant)) |
||||
|
{ |
||||
|
return InvariantPartitioning.Instance; |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Diagnostics.Contracts; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
using AllPermissions = Squidex.Shared.Permissions; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class Role : Named |
||||
|
{ |
||||
|
public const string Editor = "Editor"; |
||||
|
public const string Developer = "Developer"; |
||||
|
public const string Owner = "Owner"; |
||||
|
public const string Reader = "Reader"; |
||||
|
|
||||
|
public PermissionSet Permissions { get; } |
||||
|
|
||||
|
public bool IsDefault |
||||
|
{ |
||||
|
get { return Roles.IsDefault(this); } |
||||
|
} |
||||
|
|
||||
|
public Role(string name, PermissionSet permissions) |
||||
|
: base(name) |
||||
|
{ |
||||
|
Guard.NotNull(permissions); |
||||
|
|
||||
|
Permissions = permissions; |
||||
|
} |
||||
|
|
||||
|
public Role(string name, params string[] permissions) |
||||
|
: this(name, new PermissionSet(permissions)) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Role Update(string[] permissions) |
||||
|
{ |
||||
|
return new Role(Name, new PermissionSet(permissions)); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(string name) |
||||
|
{ |
||||
|
return name != null && name.Equals(Name, StringComparison.Ordinal); |
||||
|
} |
||||
|
|
||||
|
public Role ForApp(string app) |
||||
|
{ |
||||
|
var result = new HashSet<Permission> |
||||
|
{ |
||||
|
AllPermissions.ForApp(AllPermissions.AppCommon, app) |
||||
|
}; |
||||
|
|
||||
|
if (Permissions.Any()) |
||||
|
{ |
||||
|
var prefix = AllPermissions.ForApp(AllPermissions.App, app).Id; |
||||
|
|
||||
|
foreach (var permission in Permissions) |
||||
|
{ |
||||
|
result.Add(new Permission(string.Concat(prefix, ".", permission.Id))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new Role(Name, new PermissionSet(result)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Diagnostics.CodeAnalysis; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class Roles |
||||
|
{ |
||||
|
private readonly ArrayDictionary<string, Role> inner; |
||||
|
|
||||
|
public static readonly IReadOnlyDictionary<string, Role> Defaults = new Dictionary<string, Role> |
||||
|
{ |
||||
|
[Role.Owner] = |
||||
|
new Role(Role.Owner, new PermissionSet( |
||||
|
Clean(Permissions.App))), |
||||
|
[Role.Reader] = |
||||
|
new Role(Role.Reader, new PermissionSet( |
||||
|
Clean(Permissions.AppAssetsRead), |
||||
|
Clean(Permissions.AppContentsRead))), |
||||
|
[Role.Editor] = |
||||
|
new Role(Role.Editor, new PermissionSet( |
||||
|
Clean(Permissions.AppAssets), |
||||
|
Clean(Permissions.AppContents), |
||||
|
Clean(Permissions.AppRolesRead), |
||||
|
Clean(Permissions.AppWorkflowsRead))), |
||||
|
[Role.Developer] = |
||||
|
new Role(Role.Developer, new PermissionSet( |
||||
|
Clean(Permissions.AppApi), |
||||
|
Clean(Permissions.AppAssets), |
||||
|
Clean(Permissions.AppContents), |
||||
|
Clean(Permissions.AppPatterns), |
||||
|
Clean(Permissions.AppRolesRead), |
||||
|
Clean(Permissions.AppRules), |
||||
|
Clean(Permissions.AppSchemas), |
||||
|
Clean(Permissions.AppWorkflows))) |
||||
|
}; |
||||
|
|
||||
|
public static readonly Roles Empty = new Roles(new ArrayDictionary<string, Role>()); |
||||
|
|
||||
|
public int CustomCount |
||||
|
{ |
||||
|
get { return inner.Count; } |
||||
|
} |
||||
|
|
||||
|
public Role this[string name] |
||||
|
{ |
||||
|
get { return inner[name]; } |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<Role> Custom |
||||
|
{ |
||||
|
get { return inner.Values; } |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<Role> All |
||||
|
{ |
||||
|
get { return inner.Values.Union(Defaults.Values); } |
||||
|
} |
||||
|
|
||||
|
private Roles(ArrayDictionary<string, Role> roles) |
||||
|
{ |
||||
|
inner = roles; |
||||
|
} |
||||
|
|
||||
|
public Roles(IEnumerable<KeyValuePair<string, Role>> items) |
||||
|
{ |
||||
|
inner = new ArrayDictionary<string, Role>(Cleaned(items)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Roles Remove(string name) |
||||
|
{ |
||||
|
return new Roles(inner.Without(name)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Roles Add(string name) |
||||
|
{ |
||||
|
var newRole = new Role(name); |
||||
|
|
||||
|
if (inner.ContainsKey(name)) |
||||
|
{ |
||||
|
throw new ArgumentException("Name already exists.", nameof(name)); |
||||
|
} |
||||
|
|
||||
|
if (IsDefault(name)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new Roles(inner.With(name, newRole)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Roles Update(string name, params string[] permissions) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name); |
||||
|
Guard.NotNull(permissions); |
||||
|
|
||||
|
if (!inner.TryGetValue(name, out var role)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new Roles(inner.With(name, role.Update(permissions))); |
||||
|
} |
||||
|
|
||||
|
public static bool IsDefault(string role) |
||||
|
{ |
||||
|
return role != null && Defaults.ContainsKey(role); |
||||
|
} |
||||
|
|
||||
|
public static bool IsDefault(Role role) |
||||
|
{ |
||||
|
return role != null && Defaults.ContainsKey(role.Name); |
||||
|
} |
||||
|
|
||||
|
public bool ContainsCustom(string name) |
||||
|
{ |
||||
|
return inner.ContainsKey(name); |
||||
|
} |
||||
|
|
||||
|
public bool Contains(string name) |
||||
|
{ |
||||
|
return inner.ContainsKey(name) || Defaults.ContainsKey(name); |
||||
|
} |
||||
|
|
||||
|
public bool TryGet(string app, string name, [MaybeNullWhen(false)] out Role value) |
||||
|
{ |
||||
|
Guard.NotNull(app, nameof(app)); |
||||
|
|
||||
|
if (Defaults.TryGetValue(name, out var role) || inner.TryGetValue(name, out role)) |
||||
|
{ |
||||
|
value = role.ForApp(app); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
value = null!; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static string Clean(string permission) |
||||
|
{ |
||||
|
permission = Permissions.ForApp(permission).Id; |
||||
|
|
||||
|
var prefix = Permissions.ForApp(Permissions.App); |
||||
|
|
||||
|
if (permission.StartsWith(prefix.Id, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
permission = permission.Substring(prefix.Id.Length); |
||||
|
} |
||||
|
|
||||
|
if (permission.Length == 0) |
||||
|
{ |
||||
|
return Permission.Any; |
||||
|
} |
||||
|
|
||||
|
return permission.Substring(1); |
||||
|
} |
||||
|
|
||||
|
private static KeyValuePair<string, Role>[] Cleaned(IEnumerable<KeyValuePair<string, Role>> items) |
||||
|
{ |
||||
|
return items.Where(x => !Defaults.ContainsKey(x.Key)).ToArray(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using NodaTime; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Comments |
||||
|
{ |
||||
|
public sealed class Comment |
||||
|
{ |
||||
|
public Guid Id { get; } |
||||
|
|
||||
|
public Instant Time { get; } |
||||
|
|
||||
|
public RefToken User { get; } |
||||
|
|
||||
|
public string Text { get; } |
||||
|
|
||||
|
public Comment(Guid id, Instant time, RefToken user, string text) |
||||
|
{ |
||||
|
Guard.NotEmpty(id); |
||||
|
Guard.NotNull(user); |
||||
|
Guard.NotNull(text); |
||||
|
|
||||
|
Id = id; |
||||
|
|
||||
|
Time = time; |
||||
|
Text = text; |
||||
|
|
||||
|
User = user; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public abstract class ContentData<T> : Dictionary<T, ContentFieldData?>, IEquatable<ContentData<T>> where T : notnull |
||||
|
{ |
||||
|
public IEnumerable<KeyValuePair<T, ContentFieldData?>> ValidValues |
||||
|
{ |
||||
|
get { return this.Where(x => x.Value != null); } |
||||
|
} |
||||
|
|
||||
|
protected ContentData(IEqualityComparer<T> comparer) |
||||
|
: base(comparer) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected ContentData(int capacity, IEqualityComparer<T> comparer) |
||||
|
: base(capacity, comparer) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected static TResult MergeTo<TResult>(TResult target, params TResult[] sources) where TResult : ContentData<T> |
||||
|
{ |
||||
|
Guard.NotEmpty(sources); |
||||
|
|
||||
|
if (sources.Length == 1 || sources.Skip(1).All(x => ReferenceEquals(x, sources[0]))) |
||||
|
{ |
||||
|
return sources[0]; |
||||
|
} |
||||
|
|
||||
|
foreach (var source in sources) |
||||
|
{ |
||||
|
foreach (var otherValue in source) |
||||
|
{ |
||||
|
if (otherValue.Value != null) |
||||
|
{ |
||||
|
var fieldValue = target.GetOrAdd(otherValue.Key, x => new ContentFieldData()); |
||||
|
|
||||
|
if (fieldValue != null) |
||||
|
{ |
||||
|
foreach (var value in otherValue.Value) |
||||
|
{ |
||||
|
fieldValue[value.Key] = value.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
protected static TResult Clean<TResult>(TResult source, TResult target) where TResult : ContentData<T> |
||||
|
{ |
||||
|
foreach (var fieldValue in source.ValidValues) |
||||
|
{ |
||||
|
var resultValue = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var partitionValue in fieldValue.Value.Where(x => x.Value.Type != JsonValueType.Null)) |
||||
|
{ |
||||
|
resultValue[partitionValue.Key] = partitionValue.Value; |
||||
|
} |
||||
|
|
||||
|
if (resultValue.Count > 0) |
||||
|
{ |
||||
|
target[fieldValue.Key] = resultValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object? obj) |
||||
|
{ |
||||
|
return Equals(obj as ContentData<T>); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(ContentData<T>? other) |
||||
|
{ |
||||
|
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other)); |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return this.DictionaryHashCode(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class ContentFieldData : Dictionary<string, IJsonValue>, IEquatable<ContentFieldData> |
||||
|
{ |
||||
|
public ContentFieldData() |
||||
|
: base(StringComparer.OrdinalIgnoreCase) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData AddValue(object? value) |
||||
|
{ |
||||
|
return AddJsonValue(JsonValue.Create(value)); |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData AddValue(string key, object? value) |
||||
|
{ |
||||
|
return AddJsonValue(key, JsonValue.Create(value)); |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData AddJsonValue(IJsonValue value) |
||||
|
{ |
||||
|
this[InvariantPartitioning.Key] = value; |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData AddJsonValue(string key, IJsonValue value) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(key); |
||||
|
|
||||
|
if (Language.IsValidLanguage(key)) |
||||
|
{ |
||||
|
this[key] = value; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this[key] = value; |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object? obj) |
||||
|
{ |
||||
|
return Equals(obj as ContentFieldData); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(ContentFieldData? other) |
||||
|
{ |
||||
|
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other)); |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return this.DictionaryHashCode(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class IdContentData : ContentData<long>, IEquatable<IdContentData> |
||||
|
{ |
||||
|
public IdContentData() |
||||
|
: base(EqualityComparer<long>.Default) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IdContentData(int capacity) |
||||
|
: base(capacity, EqualityComparer<long>.Default) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static IdContentData Merge(params IdContentData[] contents) |
||||
|
{ |
||||
|
return MergeTo(new IdContentData(), contents); |
||||
|
} |
||||
|
|
||||
|
public IdContentData MergeInto(IdContentData target) |
||||
|
{ |
||||
|
return Merge(target, this); |
||||
|
} |
||||
|
|
||||
|
public IdContentData ToCleaned() |
||||
|
{ |
||||
|
return Clean(this, new IdContentData()); |
||||
|
} |
||||
|
|
||||
|
public IdContentData AddField(long id, ContentFieldData? data) |
||||
|
{ |
||||
|
Guard.GreaterThan(id, 0); |
||||
|
|
||||
|
this[id] = data; |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public bool Equals(IdContentData other) |
||||
|
{ |
||||
|
return base.Equals(other); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Newtonsoft; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents.Json |
||||
|
{ |
||||
|
public sealed class ContentFieldDataConverter : JsonClassConverter<ContentFieldData> |
||||
|
{ |
||||
|
protected override void WriteValue(JsonWriter writer, ContentFieldData value, JsonSerializer serializer) |
||||
|
{ |
||||
|
writer.WriteStartObject(); |
||||
|
|
||||
|
foreach (var kvp in value) |
||||
|
{ |
||||
|
writer.WritePropertyName(kvp.Key); |
||||
|
|
||||
|
serializer.Serialize(writer, kvp.Value); |
||||
|
} |
||||
|
|
||||
|
writer.WriteEndObject(); |
||||
|
} |
||||
|
|
||||
|
protected override ContentFieldData ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
while (reader.Read()) |
||||
|
{ |
||||
|
switch (reader.TokenType) |
||||
|
{ |
||||
|
case JsonToken.PropertyName: |
||||
|
var propertyName = reader.Value.ToString()!; |
||||
|
|
||||
|
if (!reader.Read()) |
||||
|
{ |
||||
|
throw new JsonSerializationException("Unexpected end when reading Object."); |
||||
|
} |
||||
|
|
||||
|
var value = serializer.Deserialize<IJsonValue>(reader); |
||||
|
|
||||
|
if (Language.IsValidLanguage(propertyName) || propertyName == InvariantPartitioning.Key) |
||||
|
{ |
||||
|
propertyName = string.Intern(propertyName); |
||||
|
} |
||||
|
|
||||
|
result[propertyName] = value; |
||||
|
break; |
||||
|
case JsonToken.EndObject: |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new JsonSerializationException("Unexpected end when reading Object."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents.Json |
||||
|
{ |
||||
|
public class JsonWorkflowTransition |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public string Expression { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string Role { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public List<string> Roles { get; } |
||||
|
|
||||
|
public JsonWorkflowTransition() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public JsonWorkflowTransition(WorkflowTransition client) |
||||
|
{ |
||||
|
SimpleMapper.Map(client, this); |
||||
|
} |
||||
|
|
||||
|
public WorkflowTransition ToTransition() |
||||
|
{ |
||||
|
var rolesList = Roles; |
||||
|
|
||||
|
if (!string.IsNullOrEmpty(Role)) |
||||
|
{ |
||||
|
rolesList = new List<string> { Role }; |
||||
|
} |
||||
|
|
||||
|
ReadOnlyCollection<string>? roles = null; |
||||
|
|
||||
|
if (rolesList != null && rolesList.Count > 0) |
||||
|
{ |
||||
|
roles = new ReadOnlyCollection<string>(rolesList); |
||||
|
} |
||||
|
|
||||
|
return new WorkflowTransition(Expression, roles); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class NamedContentData : ContentData<string>, IEquatable<NamedContentData> |
||||
|
{ |
||||
|
public NamedContentData() |
||||
|
: base(StringComparer.Ordinal) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public NamedContentData(int capacity) |
||||
|
: base(capacity, StringComparer.Ordinal) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static NamedContentData Merge(params NamedContentData[] contents) |
||||
|
{ |
||||
|
return MergeTo(new NamedContentData(), contents); |
||||
|
} |
||||
|
|
||||
|
public NamedContentData MergeInto(NamedContentData target) |
||||
|
{ |
||||
|
return Merge(target, this); |
||||
|
} |
||||
|
|
||||
|
public NamedContentData ToCleaned() |
||||
|
{ |
||||
|
return Clean(this, new NamedContentData()); |
||||
|
} |
||||
|
|
||||
|
public NamedContentData AddField(string name, ContentFieldData? data) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name); |
||||
|
|
||||
|
this[name] = data; |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public bool Equals(NamedContentData other) |
||||
|
{ |
||||
|
return base.Equals(other); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
[TypeConverter(typeof(StatusConverter))] |
||||
|
public struct Status : IEquatable<Status> |
||||
|
{ |
||||
|
public static readonly Status Archived = new Status("Archived"); |
||||
|
public static readonly Status Draft = new Status("Draft"); |
||||
|
public static readonly Status Published = new Status("Published"); |
||||
|
|
||||
|
private readonly string? name; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return name ?? "Unknown"; } |
||||
|
} |
||||
|
|
||||
|
public Status(string? name) |
||||
|
{ |
||||
|
this.name = name; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object? obj) |
||||
|
{ |
||||
|
return obj is Status status && Equals(status); |
||||
|
} |
||||
|
|
||||
|
public bool Equals(Status other) |
||||
|
{ |
||||
|
return string.Equals(name, other.name); |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
return name?.GetHashCode() ?? 0; |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return Name; |
||||
|
} |
||||
|
|
||||
|
public static bool operator ==(Status lhs, Status rhs) |
||||
|
{ |
||||
|
return lhs.Equals(rhs); |
||||
|
} |
||||
|
|
||||
|
public static bool operator !=(Status lhs, Status rhs) |
||||
|
{ |
||||
|
return !lhs.Equals(rhs); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class StatusConverter : TypeConverter |
||||
|
{ |
||||
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
||||
|
{ |
||||
|
return sourceType == typeof(string); |
||||
|
} |
||||
|
|
||||
|
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) |
||||
|
{ |
||||
|
return destinationType == typeof(string); |
||||
|
} |
||||
|
|
||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
||||
|
{ |
||||
|
return new Status(value?.ToString()); |
||||
|
} |
||||
|
|
||||
|
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) |
||||
|
{ |
||||
|
return value.ToString()!; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class Workflow : Named |
||||
|
{ |
||||
|
private const string DefaultName = "Unnamed"; |
||||
|
|
||||
|
public static readonly IReadOnlyDictionary<Status, WorkflowStep> EmptySteps = new Dictionary<Status, WorkflowStep>(); |
||||
|
public static readonly IReadOnlyList<Guid> EmptySchemaIds = new List<Guid>(); |
||||
|
public static readonly Workflow Default = CreateDefault(); |
||||
|
public static readonly Workflow Empty = new Workflow(default, EmptySteps); |
||||
|
|
||||
|
public IReadOnlyDictionary<Status, WorkflowStep> Steps { get; } = EmptySteps; |
||||
|
|
||||
|
public IReadOnlyList<Guid> SchemaIds { get; } = EmptySchemaIds; |
||||
|
|
||||
|
public Status Initial { get; } |
||||
|
|
||||
|
public Workflow( |
||||
|
Status initial, |
||||
|
IReadOnlyDictionary<Status, WorkflowStep>? steps, |
||||
|
IReadOnlyList<Guid>? schemaIds = null, |
||||
|
string? name = null) |
||||
|
: base(name ?? DefaultName) |
||||
|
{ |
||||
|
Initial = initial; |
||||
|
|
||||
|
if (steps != null) |
||||
|
{ |
||||
|
Steps = steps; |
||||
|
} |
||||
|
|
||||
|
if (schemaIds != null) |
||||
|
{ |
||||
|
SchemaIds = schemaIds; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static Workflow CreateDefault(string? name = null) |
||||
|
{ |
||||
|
return new Workflow( |
||||
|
Status.Draft, new Dictionary<Status, WorkflowStep> |
||||
|
{ |
||||
|
[Status.Archived] = |
||||
|
new WorkflowStep( |
||||
|
new Dictionary<Status, WorkflowTransition> |
||||
|
{ |
||||
|
[Status.Draft] = new WorkflowTransition() |
||||
|
}, |
||||
|
StatusColors.Archived, true), |
||||
|
[Status.Draft] = |
||||
|
new WorkflowStep( |
||||
|
new Dictionary<Status, WorkflowTransition> |
||||
|
{ |
||||
|
[Status.Archived] = new WorkflowTransition(), |
||||
|
[Status.Published] = new WorkflowTransition() |
||||
|
}, |
||||
|
StatusColors.Draft), |
||||
|
[Status.Published] = |
||||
|
new WorkflowStep( |
||||
|
new Dictionary<Status, WorkflowTransition> |
||||
|
{ |
||||
|
[Status.Archived] = new WorkflowTransition(), |
||||
|
[Status.Draft] = new WorkflowTransition() |
||||
|
}, |
||||
|
StatusColors.Published) |
||||
|
}, null, name); |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<(Status Status, WorkflowStep Step, WorkflowTransition Transition)> GetTransitions(Status status) |
||||
|
{ |
||||
|
if (TryGetStep(status, out var step)) |
||||
|
{ |
||||
|
foreach (var transition in step.Transitions) |
||||
|
{ |
||||
|
yield return (transition.Key, Steps[transition.Key], transition.Value); |
||||
|
} |
||||
|
} |
||||
|
else if (TryGetStep(Initial, out var initial)) |
||||
|
{ |
||||
|
yield return (Initial, initial, WorkflowTransition.Default); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool TryGetTransition(Status from, Status to, [MaybeNullWhen(false)] out WorkflowTransition transition) |
||||
|
{ |
||||
|
transition = null!; |
||||
|
|
||||
|
if (TryGetStep(from, out var step)) |
||||
|
{ |
||||
|
if (step.Transitions.TryGetValue(to, out transition!)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
else if (to == Initial) |
||||
|
{ |
||||
|
transition = WorkflowTransition.Default; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public bool TryGetStep(Status status, [MaybeNullWhen(false)] out WorkflowStep step) |
||||
|
{ |
||||
|
return Steps.TryGetValue(status, out step!); |
||||
|
} |
||||
|
|
||||
|
public (Status Key, WorkflowStep) GetInitialStep() |
||||
|
{ |
||||
|
return (Initial, Steps[Initial]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class WorkflowStep |
||||
|
{ |
||||
|
private static readonly IReadOnlyDictionary<Status, WorkflowTransition> EmptyTransitions = new Dictionary<Status, WorkflowTransition>(); |
||||
|
|
||||
|
public IReadOnlyDictionary<Status, WorkflowTransition> Transitions { get; } |
||||
|
|
||||
|
public string? Color { get; } |
||||
|
|
||||
|
public bool NoUpdate { get; } |
||||
|
|
||||
|
public WorkflowStep(IReadOnlyDictionary<Status, WorkflowTransition>? transitions = null, string? color = null, bool noUpdate = false) |
||||
|
{ |
||||
|
Transitions = transitions ?? EmptyTransitions; |
||||
|
|
||||
|
Color = color; |
||||
|
|
||||
|
NoUpdate = noUpdate; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.ObjectModel; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class WorkflowTransition |
||||
|
{ |
||||
|
public static readonly WorkflowTransition Default = new WorkflowTransition(); |
||||
|
|
||||
|
public string? Expression { get; } |
||||
|
|
||||
|
public ReadOnlyCollection<string>? Roles { get; } |
||||
|
|
||||
|
public WorkflowTransition(string? expression = null, ReadOnlyCollection<string>? roles = null) |
||||
|
{ |
||||
|
Expression = expression; |
||||
|
|
||||
|
Roles = roles; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Collections; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public sealed class Workflows : ArrayDictionary<Guid, Workflow> |
||||
|
{ |
||||
|
public static readonly Workflows Empty = new Workflows(); |
||||
|
|
||||
|
private Workflows() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Workflows(KeyValuePair<Guid, Workflow>[] items) |
||||
|
: base(items) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Workflows Remove(Guid id) |
||||
|
{ |
||||
|
return new Workflows(Without(id)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Workflows Add(Guid workflowId, string name) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name); |
||||
|
|
||||
|
return new Workflows(With(workflowId, Workflow.CreateDefault(name))); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Workflows Set(Workflow workflow) |
||||
|
{ |
||||
|
Guard.NotNull(workflow); |
||||
|
|
||||
|
return new Workflows(With(Guid.Empty, workflow)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Workflows Set(Guid id, Workflow workflow) |
||||
|
{ |
||||
|
Guard.NotNull(workflow); |
||||
|
|
||||
|
return new Workflows(With(id, workflow)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public Workflows Update(Guid id, Workflow workflow) |
||||
|
{ |
||||
|
Guard.NotNull(workflow); |
||||
|
|
||||
|
if (id == Guid.Empty) |
||||
|
{ |
||||
|
return Set(workflow); |
||||
|
} |
||||
|
|
||||
|
if (!ContainsKey(id)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new Workflows(With(id, workflow)); |
||||
|
} |
||||
|
|
||||
|
public Workflow GetFirst() |
||||
|
{ |
||||
|
return Values.FirstOrDefault() ?? Workflow.Default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core |
||||
|
{ |
||||
|
public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem |
||||
|
{ |
||||
|
public static readonly InvariantPartitioning Instance = new InvariantPartitioning(); |
||||
|
public static readonly string Key = "iv"; |
||||
|
|
||||
|
public int Count |
||||
|
{ |
||||
|
get { return 1; } |
||||
|
} |
||||
|
|
||||
|
public IFieldPartitionItem Master |
||||
|
{ |
||||
|
get { return this; } |
||||
|
} |
||||
|
|
||||
|
string IFieldPartitionItem.Key |
||||
|
{ |
||||
|
get { return Key; } |
||||
|
} |
||||
|
|
||||
|
string IFieldPartitionItem.Name |
||||
|
{ |
||||
|
get { return "Invariant"; } |
||||
|
} |
||||
|
|
||||
|
bool IFieldPartitionItem.IsOptional |
||||
|
{ |
||||
|
get { return false; } |
||||
|
} |
||||
|
|
||||
|
IEnumerable<string> IFieldPartitionItem.Fallback |
||||
|
{ |
||||
|
get { return Enumerable.Empty<string>(); } |
||||
|
} |
||||
|
|
||||
|
private InvariantPartitioning() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool TryGetItem(string key, [MaybeNullWhen(false)] out IFieldPartitionItem item) |
||||
|
{ |
||||
|
var isFound = string.Equals(key, Key, StringComparison.OrdinalIgnoreCase); |
||||
|
|
||||
|
item = isFound ? this : null!; |
||||
|
|
||||
|
return isFound; |
||||
|
} |
||||
|
|
||||
|
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator() |
||||
|
{ |
||||
|
yield return this; |
||||
|
} |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||
|
{ |
||||
|
yield return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core |
||||
|
{ |
||||
|
public abstract class Named |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
|
||||
|
protected Named(string name) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name); |
||||
|
|
||||
|
Name = name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue