Browse Source

Merge pull request #3 from Squidex/master

Merge from squidex
pull/328/head
Avd6977 7 years ago
committed by GitHub
parent
commit
2a666e61bf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      NuGet.Config
  2. 2
      extensions/Squidex.Extensions/Squidex.Extensions.csproj
  3. 4
      src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  4. 2
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  5. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  6. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs
  7. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  8. 36
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
  9. 21
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs
  10. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  11. 25
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  12. 15
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  13. 8
      src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs
  14. 13
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs
  16. 6
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  17. 14
      src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs
  18. 15
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  19. 2
      src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs
  20. 3
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  21. 6
      src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  22. 14
      src/Squidex.Domain.Users.MongoDb/Infrastructure/MongoPersistedGrantStore.cs
  23. 4
      src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs
  24. 18
      src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs
  25. 2
      src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  26. 2
      src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  27. 6
      src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
  28. 8
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs
  29. 3
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs
  30. 6
      src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
  31. 4
      src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs
  32. 14
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
  33. 2
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
  34. 26
      src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  35. 5
      src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs
  36. 5
      src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs
  37. 7
      src/Squidex.Infrastructure.Redis/RedisPubSub.cs
  38. 14
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  39. 5
      src/Squidex.Infrastructure/IInitializable.cs
  40. 6
      src/Squidex.Infrastructure/Language.cs
  41. 321
      src/Squidex.Infrastructure/Languages.cs
  42. 22
      src/Squidex.Infrastructure/Log/FileChannel.cs
  43. 27
      src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs
  44. 40
      src/Squidex.Infrastructure/Orleans/InitializerStartup.cs
  45. 8
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  46. 7
      src/Squidex/AppServices.cs
  47. 3
      src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs
  48. 1
      src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
  49. 6
      src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs
  50. 15
      src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs
  51. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
  52. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  53. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
  54. 5
      src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs
  55. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  56. 3
      src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  57. 2
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  58. 3
      src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs
  59. 3
      src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs
  60. 3
      src/Squidex/Areas/Api/Controllers/History/HistoryController.cs
  61. 3
      src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs
  62. 27
      src/Squidex/Areas/Api/Controllers/Ping/PingController.cs
  63. 6
      src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs
  64. 3
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  65. 3
      src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs
  66. 3
      src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  67. 3
      src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
  68. 3
      src/Squidex/Areas/Api/Controllers/UI/UIController.cs
  69. 3
      src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
  70. 6
      src/Squidex/Config/Domain/EntitiesServices.cs
  71. 14
      src/Squidex/Config/Domain/LoggingExtensions.cs
  72. 4
      src/Squidex/Config/Domain/LoggingServices.cs
  73. 53
      src/Squidex/Config/Domain/SystemExtensions.cs
  74. 10
      src/Squidex/Config/Logging.cs
  75. 6
      src/Squidex/Config/Orleans/OrleansServices.cs
  76. 49
      src/Squidex/Config/Orleans/SiloWrapper.cs
  77. 20
      src/Squidex/Squidex.csproj
  78. 6
      src/Squidex/WebStartup.cs
  79. 2
      src/Squidex/app/features/administration/guards/user-must-exist.guard.ts
  80. 3
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  81. 8
      src/Squidex/app/features/administration/pages/restore/restore-page.component.ts
  82. 52
      src/Squidex/app/features/assets/pages/assets-page.component.html
  83. 11
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  84. 1
      src/Squidex/app/features/content/declarations.ts
  85. 6
      src/Squidex/app/features/content/module.ts
  86. 2
      src/Squidex/app/features/content/pages/content/content-field.component.html
  87. 13
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  88. 5
      src/Squidex/app/features/content/pages/content/content-history.component.html
  89. 9
      src/Squidex/app/features/content/pages/content/content-history.component.ts
  90. 3
      src/Squidex/app/features/content/pages/content/content-page.component.html
  91. 12
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  92. 8
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  93. 49
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  94. 1
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  95. 25
      src/Squidex/app/features/content/shared/array-editor.component.html
  96. 37
      src/Squidex/app/features/content/shared/array-editor.component.scss
  97. 5
      src/Squidex/app/features/content/shared/array-editor.component.ts
  98. 15
      src/Squidex/app/features/content/shared/array-item.component.html
  99. 20
      src/Squidex/app/features/content/shared/array-item.component.scss
  100. 59
      src/Squidex/app/features/content/shared/array-item.component.ts

1
Nuget.config → NuGet.Config

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="myget.org" value="https://dotnet.myget.org/F/orleans-ci/api/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

2
extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -10,7 +10,7 @@
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="Algolia.Search" Version="5.2.0" />
<PackageReference Include="CoreTweet" Version="0.9.0.415" />
<PackageReference Include="Elasticsearch.Net" Version="6.3.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />

4
src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -8,11 +8,11 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="3.2.4">
<PackageReference Include="Fody" Version="3.2.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Freezable.Fody" Version="1.9.1" />
<PackageReference Include="Freezable.Fody" Version="1.9.3" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

2
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -17,7 +17,7 @@
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NJsonSchema" Version="9.10.75" />
<PackageReference Include="NJsonSchema" Version="9.11.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />

6
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
@ -32,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return "States_Assets";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection, CancellationToken ct = default(CancellationToken))
{
return collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoAssetEntity>(
@ -41,7 +42,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
.Ascending(x => x.IsDeleted)
.Ascending(x => x.FileName)
.Ascending(x => x.Tags)
.Descending(x => x.LastModified)));
.Descending(x => x.LastModified)),
cancellationToken: ct);
}
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, Query query)

13
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
@ -30,12 +31,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return "Projections_AssetStats";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Ascending(x => x.Date)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Descending(x => x.Date)));
return collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Ascending(x => x.Date)),
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Descending(x => x.Date))
}, ct);
}
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate)

5
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
@ -31,10 +32,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
this.collectionName = collectionName;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.ReferencedIds)));
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.ReferencedIds)), cancellationToken: ct);
}
protected override string CollectionName()

36
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using NodaTime;
@ -29,24 +30,25 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.Id)
.Ascending(x => x.IsDeleted)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index
.Text(x => x.DataText)
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)));
await base.SetupCollectionAsync(collection);
await collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoContentEntity>(
Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.Id)
.Ascending(x => x.IsDeleted)),
new CreateIndexModel<MongoContentEntity>(
Index
.Text(x => x.DataText)
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)),
}, ct);
await base.SetupCollectionAsync(collection, ct);
}
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids)

21
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
@ -22,18 +23,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.Id)));
await base.SetupCollectionAsync(collection);
await collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoContentEntity>(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)),
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id))
}, ct);
await base.SetupCollectionAsync(collection, ct);
}
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id)

6
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using NodaTime;
@ -40,10 +41,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
this.database = database;
}
public void Initialize()
public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
contentsDraft.Initialize();
contentsPublished.Initialize();
return Task.WhenAll(contentsDraft.InitializeAsync(ct), contentsPublished.InitializeAsync(ct));
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query)

25
src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.History;
@ -52,18 +53,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
return "Projections_History";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoHistoryEventEntity>(
Index
.Ascending(x => x.AppId)
.Ascending(x => x.Channel)
.Descending(x => x.Created)
.Descending(x => x.Version)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoHistoryEventEntity>(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) }));
return collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoHistoryEventEntity>(
Index
.Ascending(x => x.AppId)
.Ascending(x => x.Channel)
.Descending(x => x.Created)
.Descending(x => x.Version)),
new CreateIndexModel<MongoHistoryEventEntity>(Index.Ascending(x => x.Created),
new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) })
}, ct);
}
public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count)

15
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -32,14 +32,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return "RuleEvents";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection)
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection, CancellationToken ct = default(CancellationToken))
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.NextAttempt)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.AppId).Descending(x => x.Created)));
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }));
await collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.NextAttempt)),
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.AppId).Descending(x => x.Created)),
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero })
}, ct);
}
public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken ct = default(CancellationToken))

8
src/Squidex.Infrastructure/IRunnable.cs → src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs

@ -1,14 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure
namespace Squidex.Domain.Apps.Entities.Assets
{
public interface IRunnable
public sealed class AssetOptions
{
void Run();
public int MaxResults { get; set; } = 200;
}
}

13
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -22,18 +22,19 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetQueryService : IAssetQueryService
{
private const int MaxResults = 200;
private readonly ITagService tagService;
private readonly IAssetRepository assetRepository;
private readonly AssetOptions options;
public AssetQueryService(ITagService tagService, IAssetRepository assetRepository)
public AssetQueryService(ITagService tagService, IAssetRepository assetRepository, AssetOptions options)
{
Guard.NotNull(tagService, nameof(tagService));
Guard.NotNull(options, nameof(options));
Guard.NotNull(assetRepository, nameof(assetRepository));
this.tagService = tagService;
this.assetRepository = assetRepository;
this.options = options;
this.tagService = tagService;
}
public async Task<IAssetEntity> FindAssetAsync(QueryContext context, Guid id)
@ -97,9 +98,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
result.Sort.Add(new SortNode(new List<string> { "lastModified" }, SortOrder.Descending));
}
if (result.Take > MaxResults)
if (result.Take > options.MaxResults)
{
result.Take = MaxResults;
result.Take = options.MaxResults;
}
return result;

2
src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs

@ -18,6 +18,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public bool Publish { get; set; }
public bool DoNotValidate { get; set; }
public CreateContent()
{
ContentId = Guid.NewGuid();

6
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -68,7 +68,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ctx.ExecuteScriptAndTransformAsync(x => x.ScriptCreate, "Create", c, c.Data);
await ctx.EnrichAsync(c.Data);
await ctx.ValidateAsync(c.Data);
if (!c.DoNotValidate)
{
await ctx.ValidateAsync(c.Data);
}
if (c.Publish)
{

14
src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentOptions
{
public int MaxResults { get; set; } = 200;
}
}

15
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -28,7 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
public sealed class ContentQueryService : IContentQueryService
{
private const int MaxResults = 200;
private static readonly Status[] StatusAll = { Status.Archived, Status.Draft, Status.Published };
private static readonly Status[] StatusArchived = { Status.Archived };
private static readonly Status[] StatusPublished = { Status.Published };
@ -37,6 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly IContentVersionLoader contentVersionLoader;
private readonly IAppProvider appProvider;
private readonly IScriptEngine scriptEngine;
private readonly ContentOptions options;
private readonly EdmModelBuilder modelBuilder;
public ContentQueryService(
@ -44,18 +44,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
IContentRepository contentRepository,
IContentVersionLoader contentVersionLoader,
IScriptEngine scriptEngine,
ContentOptions options,
EdmModelBuilder modelBuilder)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(contentVersionLoader, nameof(contentVersionLoader));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(options, nameof(options));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
this.appProvider = appProvider;
this.contentRepository = contentRepository;
this.contentVersionLoader = contentVersionLoader;
this.modelBuilder = modelBuilder;
this.options = options;
this.scriptEngine = scriptEngine;
}
@ -120,12 +123,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
private IContentEntity Transform(QueryContext context, ISchemaEntity schema, bool checkType, IContentEntity content)
{
return TansformCore(context, schema, checkType, Enumerable.Repeat(content, 1)).FirstOrDefault();
return TransformCore(context, schema, checkType, Enumerable.Repeat(content, 1)).FirstOrDefault();
}
private IResultList<IContentEntity> Transform(QueryContext context, ISchemaEntity schema, bool checkType, IResultList<IContentEntity> contents)
{
var transformed = TansformCore(context, schema, checkType, contents);
var transformed = TransformCore(context, schema, checkType, contents);
return ResultList.Create(contents.Total, transformed);
}
@ -137,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return ResultList.Create(contents.Total, sorted);
}
private IEnumerable<IContentEntity> TansformCore(QueryContext context, ISchemaEntity schema, bool checkType, IEnumerable<IContentEntity> contents)
private IEnumerable<IContentEntity> TransformCore(QueryContext context, ISchemaEntity schema, bool checkType, IEnumerable<IContentEntity> contents)
{
using (Profiler.TraceMethod<ContentQueryService>())
{
@ -214,9 +217,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.Sort.Add(new SortNode(new List<string> { "lastModified" }, SortOrder.Descending));
}
if (result.Take > MaxResults)
if (result.Take > options.MaxResults)
{
result.Take = MaxResults;
result.Take = options.MaxResults;
}
return result;

2
src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs

@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var data = new NamedContentData();
var contentId = schemaId.Id;
var content = new CreateContent { Data = data, ContentId = contentId, SchemaId = schemaId };
var content = new CreateContent { Data = data, ContentId = contentId, SchemaId = schemaId, DoNotValidate = true };
SimpleMapper.Map(createSchema, content);

3
src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs

@ -86,13 +86,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
protected void On(ContentStatusChanged @event)
{
ScheduleJob = null;
Status = @event.Status;
if (@event.Status == Status.Published)
{
Data = DataDraft;
}
IsPending = false;
}
protected void On(ContentSchedulingCancelled @event)

6
src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -14,12 +14,12 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="GraphQL" Version="2.0.0" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.0-rc2">
<PackageReference Include="GraphQL" Version="2.1.0" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0" />
<PackageReference Include="NodaTime" Version="2.4.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />

14
src/Squidex.Domain.Users.MongoDb/Infrastructure/MongoPersistedGrantStore.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Stores;
@ -36,13 +37,14 @@ namespace Squidex.Domain.Users.MongoDb.Infrastructure
return "Identity_PersistedGrants";
}
protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection)
protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection, CancellationToken ct = default(CancellationToken))
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.ClientId))),
collection.Indexes.CreateOneAsync(
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.SubjectId))));
return collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.ClientId)),
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.SubjectId))
}, ct);
}
public Task StoreAsync(PersistedGrant grant)

4
src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs

@ -26,10 +26,10 @@ namespace Squidex.Domain.Users.MongoDb
return "Identity_Roles";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoRole> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoRole> collection, CancellationToken ct = default(CancellationToken))
{
return collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoRole>(Index.Ascending(x => x.NormalizedName), new CreateIndexOptions { Unique = true }));
new CreateIndexModel<MongoRole>(Index.Ascending(x => x.NormalizedName), new CreateIndexOptions { Unique = true }), cancellationToken: ct);
}
protected override MongoCollectionSettings CollectionSettings()

18
src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs

@ -46,15 +46,15 @@ namespace Squidex.Domain.Users.MongoDb
return "Identity_Users";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoUser> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoUser>(Index.Ascending("Logins.LoginProvider").Ascending("Logins.ProviderKey"))),
collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoUser>(Index.Ascending(x => x.NormalizedUserName), new CreateIndexOptions { Unique = true })),
collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoUser>(Index.Ascending(x => x.NormalizedEmail), new CreateIndexOptions { Unique = true })));
protected override Task SetupCollectionAsync(IMongoCollection<MongoUser> collection, CancellationToken ct = default(CancellationToken))
{
return collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoUser>(Index.Ascending("Logins.LoginProvider").Ascending("Logins.ProviderKey")),
new CreateIndexModel<MongoUser>(Index.Ascending(x => x.NormalizedUserName), new CreateIndexOptions { Unique = true }),
new CreateIndexModel<MongoUser>(Index.Ascending(x => x.NormalizedEmail), new CreateIndexOptions { Unique = true })
}, ct);
}
protected override MongoCollectionSettings CollectionSettings()

2
src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -19,7 +19,7 @@
<PackageReference Include="MongoDB.Driver" Version="2.7.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.1" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

2
src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -17,7 +17,7 @@
<PackageReference Include="SharpPwned.NET" Version="1.0.8" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />
<PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.5.1" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

6
src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs

@ -29,7 +29,7 @@ namespace Squidex.Infrastructure.Assets
this.containerName = containerName;
}
public void Initialize()
public async Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
@ -38,7 +38,7 @@ namespace Squidex.Infrastructure.Assets
var blobClient = storageAccount.CreateCloudBlobClient();
var blobReference = blobClient.GetContainerReference(containerName);
blobReference.CreateIfNotExistsAsync().Wait();
await blobReference.CreateIfNotExistsAsync();
blobContainer = blobReference;
}
@ -130,7 +130,7 @@ namespace Squidex.Infrastructure.Assets
return blob.DeleteIfExistsAsync();
}
private async Task UploadCoreAsync(string blobName, Stream stream, CancellationToken ct)
private async Task UploadCoreAsync(string blobName, Stream stream, CancellationToken ct = default(CancellationToken))
{
try
{

8
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs

@ -34,18 +34,18 @@ namespace Squidex.Infrastructure.EventSourcing
projectionClient = new ProjectionClient(connection, prefix, projectionHost);
}
public void Initialize()
public async Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
connection.ConnectAsync().Wait();
await connection.ConnectAsync();
}
catch (Exception ex)
{
throw new ConfigurationException("Cannot connect to event store.", ex);
}
projectionClient.ConnectAsync().Wait();
await projectionClient.ConnectAsync();
}
public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null)
@ -82,7 +82,7 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
private async Task QueryAsync(Func<StoredEvent, Task> callback, string streamName, long sliceStart, CancellationToken ct)
private async Task QueryAsync(Func<StoredEvent, Task> callback, string streamName, long sliceStart, CancellationToken ct = default(CancellationToken))
{
StreamEventsSlice currentSlice;
do

3
src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs

@ -10,6 +10,7 @@ using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
using EventStore.ClientAPI.Exceptions;
@ -103,7 +104,7 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
public async Task ConnectAsync()
public async Task ConnectAsync(CancellationToken ct = default(CancellationToken))
{
var addressParts = projectionHost.Split(':');

6
src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs

@ -30,13 +30,13 @@ namespace Squidex.Infrastructure.Assets
this.bucketName = bucketName;
}
public void Initialize()
public async Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
storageClient = StorageClient.Create();
storageClient.GetBucket(bucketName);
await storageClient.GetBucketAsync(bucketName, cancellationToken: ct);
}
catch (Exception ex)
{
@ -103,7 +103,7 @@ namespace Squidex.Infrastructure.Assets
return DeleteCoreAsync(fileName);
}
private async Task UploadCoreAsync(string objectName, Stream stream, CancellationToken ct)
private async Task UploadCoreAsync(string objectName, Stream stream, CancellationToken ct = default(CancellationToken))
{
try
{

4
src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs

@ -27,11 +27,11 @@ namespace Squidex.Infrastructure.Assets
this.bucket = bucket;
}
public void Initialize()
public async Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
bucket.Database.ListCollections();
await bucket.Database.ListCollectionsAsync(cancellationToken: ct);
}
catch (MongoException ex)
{

14
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
@ -43,13 +44,14 @@ namespace Squidex.Infrastructure.EventSourcing
return new MongoCollectionSettings { ReadPreference = ReadPreference.Primary, WriteConcern = WriteConcern.WMajority };
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoEventCommit> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoEventCommit> collection, CancellationToken ct = default(CancellationToken))
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoEventCommit>(Index.Ascending(x => x.Timestamp).Ascending(x => x.EventStream))),
collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoEventCommit>(Index.Ascending(x => x.EventStream).Descending(x => x.EventStreamOffset), new CreateIndexOptions { Unique = true })));
return collection.Indexes.CreateManyAsync(
new[]
{
new CreateIndexModel<MongoEventCommit>(Index.Ascending(x => x.Timestamp).Ascending(x => x.EventStream)),
new CreateIndexModel<MongoEventCommit>(Index.Ascending(x => x.EventStream).Descending(x => x.EventStreamOffset), new CreateIndexOptions { Unique = true })
}, ct);
}
}
}

2
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs

@ -91,7 +91,7 @@ namespace Squidex.Infrastructure.EventSourcing
return QueryAsync(callback, lastPosition, filter, ct);
}
private async Task QueryAsync(Func<StoredEvent, Task> callback, StreamPosition lastPosition, FilterDefinition<MongoEventCommit> filter, CancellationToken ct)
private async Task QueryAsync(Func<StoredEvent, Task> callback, StreamPosition lastPosition, FilterDefinition<MongoEventCommit> filter, CancellationToken ct = default(CancellationToken))
{
using (Profiler.TraceMethod<MongoEventStore>())
{

26
src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs

@ -7,6 +7,7 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure.Tasks;
@ -67,21 +68,12 @@ namespace Squidex.Infrastructure.MongoDb
private Lazy<IMongoCollection<TEntity>> CreateCollection()
{
return new Lazy<IMongoCollection<TEntity>>(() =>
{
return Task.Run(async () =>
{
var databaseCollection = mongoDatabase.GetCollection<TEntity>(
CollectionName(),
CollectionSettings() ?? new MongoCollectionSettings());
await SetupCollectionAsync(databaseCollection).ConfigureAwait(false);
return databaseCollection;
}).Result;
});
mongoDatabase.GetCollection<TEntity>(
CollectionName(),
CollectionSettings() ?? new MongoCollectionSettings()));
}
protected virtual Task SetupCollectionAsync(IMongoCollection<TEntity> collection)
protected virtual Task SetupCollectionAsync(IMongoCollection<TEntity> collection, CancellationToken ct = default(CancellationToken))
{
return TaskHelper.Done;
}
@ -93,7 +85,7 @@ namespace Squidex.Infrastructure.MongoDb
await SetupCollectionAsync(Collection);
}
public async Task<bool> DropCollectionIfExistsAsync()
public async Task<bool> DropCollectionIfExistsAsync(CancellationToken ct = default(CancellationToken))
{
try
{
@ -101,6 +93,8 @@ namespace Squidex.Infrastructure.MongoDb
mongoCollection = CreateCollection();
await SetupCollectionAsync(Collection, ct);
return true;
}
catch
@ -109,11 +103,11 @@ namespace Squidex.Infrastructure.MongoDb
}
}
public void Initialize()
public async Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
Database.ListCollections();
await SetupCollectionAsync(Collection, ct);
}
catch (Exception ex)
{

5
src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb;
@ -26,10 +27,10 @@ namespace Squidex.Infrastructure.UsageTracking
return "Usages";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection)
protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection, CancellationToken ct = default(CancellationToken))
{
return collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)));
new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)), cancellationToken: ct);
}
public Task TrackUsagesAsync(DateTime date, string key, string category, double count, double elapsedMs)

5
src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs

@ -7,6 +7,7 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RabbitMQ.Client;
@ -61,7 +62,7 @@ namespace Squidex.Infrastructure.CQRS.Events
}
}
public void Initialize()
public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
@ -71,6 +72,8 @@ namespace Squidex.Infrastructure.CQRS.Events
{
throw new ConfigurationException($"RabbitMq event bus failed to connect to {connectionFactory.Endpoint}");
}
return TaskHelper.Done;
}
catch (Exception e)
{

7
src/Squidex.Infrastructure.Redis/RedisPubSub.cs

@ -7,7 +7,10 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
using StackExchange.Redis;
namespace Squidex.Infrastructure
@ -30,11 +33,13 @@ namespace Squidex.Infrastructure
redisSubscriber = new Lazy<ISubscriber>(() => redis.Value.GetSubscriber());
}
public void Initialize()
public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
redisClient.Value.GetStatus();
return TaskHelper.Done;
}
catch (Exception ex)
{

14
src/Squidex.Infrastructure/Assets/FolderAssetStore.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Linq;
using System.Threading;
@ -30,7 +31,7 @@ namespace Squidex.Infrastructure.Assets
directory = new DirectoryInfo(path);
}
public void Initialize()
public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
{
try
{
@ -42,13 +43,12 @@ namespace Squidex.Infrastructure.Assets
log.LogInformation(w => w
.WriteProperty("action", "FolderAssetStoreConfigured")
.WriteProperty("path", directory.FullName));
return TaskHelper.Done;
}
catch
catch (Exception ex)
{
if (!directory.Exists)
{
throw new ConfigurationException($"Cannot access directory {directory.FullName}");
}
throw new ConfigurationException($"Cannot access directory {directory.FullName}", ex);
}
}
@ -125,7 +125,7 @@ namespace Squidex.Infrastructure.Assets
return TaskHelper.Done;
}
private static async Task UploadCoreAsync(FileInfo file, Stream stream, CancellationToken ct)
private static async Task UploadCoreAsync(FileInfo file, Stream stream, CancellationToken ct = default(CancellationToken))
{
try
{

5
src/Squidex.Infrastructure/IInitializable.cs

@ -5,10 +5,13 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
namespace Squidex.Infrastructure
{
public interface IInitializable
{
void Initialize();
Task InitializeAsync(CancellationToken ct = default(CancellationToken));
}
}

6
src/Squidex.Infrastructure/Language.cs

@ -20,11 +20,7 @@ namespace Squidex.Infrastructure
private static Language AddLanguage(string iso2Code, string englishName)
{
var language = new Language(iso2Code, englishName);
AllLanguagesField[iso2Code] = language;
return language;
return AllLanguagesField.GetOrAdd(iso2Code, code => new Language(code, englishName));
}
public static Language GetLanguage(string iso2Code)

321
src/Squidex.Infrastructure/Languages.cs

@ -1,4 +1,5 @@
// ==========================================================================
// Languages.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
@ -190,5 +191,325 @@ namespace Squidex.Infrastructure
public static readonly Language ZA = AddLanguage("za", "Zhuang");
public static readonly Language ZH = AddLanguage("zh", "Chinese");
public static readonly Language ZU = AddLanguage("zu", "Zulu");
public static readonly Language AfarDjibouti = AddLanguage("aa-DJ", "Afar (Djibouti)");
public static readonly Language AfarEritrea = AddLanguage("aa-ER", "Afar (Eritrea)");
public static readonly Language AfarEthiopia = AddLanguage("aa-ET", "Afar (Ethiopia)");
public static readonly Language AfrikaansNamibia = AddLanguage("af-NA", "Afrikaans (Namibia)");
public static readonly Language AfrikaansSouthAfrica = AddLanguage("af-ZA", "Afrikaans (South Africa)");
public static readonly Language ArabicUnitedArabEmirates = AddLanguage("ar-AE", "Arabic (United Arab Emirates)");
public static readonly Language ArabicBahrain = AddLanguage("ar-BH", "Arabic (Bahrain)");
public static readonly Language ArabicDjibouti = AddLanguage("ar-DJ", "Arabic (Djibouti)");
public static readonly Language ArabicAlgeria = AddLanguage("ar-DZ", "Arabic (Algeria)");
public static readonly Language ArabicEgypt = AddLanguage("ar-EG", "Arabic (Egypt)");
public static readonly Language ArabicEritrea = AddLanguage("ar-ER", "Arabic (Eritrea)");
public static readonly Language ArabicIsrael = AddLanguage("ar-IL", "Arabic (Israel)");
public static readonly Language ArabicIraq = AddLanguage("ar-IQ", "Arabic (Iraq)");
public static readonly Language ArabicJordan = AddLanguage("ar-JO", "Arabic (Jordan)");
public static readonly Language ArabicComoros = AddLanguage("ar-KM", "Arabic (Comoros)");
public static readonly Language ArabicKuwait = AddLanguage("ar-KW", "Arabic (Kuwait)");
public static readonly Language ArabicLebanon = AddLanguage("ar-LB", "Arabic (Lebanon)");
public static readonly Language ArabicLibya = AddLanguage("ar-LY", "Arabic (Libya)");
public static readonly Language ArabicMorocco = AddLanguage("ar-MA", "Arabic (Morocco)");
public static readonly Language ArabicMauritania = AddLanguage("ar-MR", "Arabic (Mauritania)");
public static readonly Language ArabicOman = AddLanguage("ar-OM", "Arabic (Oman)");
public static readonly Language ArabicPalestinianAuthority = AddLanguage("ar-PS", "Arabic (Palestinian Authority)");
public static readonly Language ArabicQatar = AddLanguage("ar-QA", "Arabic (Qatar)");
public static readonly Language ArabicSaudiArabia = AddLanguage("ar-SA", "Arabic (Saudi Arabia)");
public static readonly Language ArabicSudan = AddLanguage("ar-SD", "Arabic (Sudan)");
public static readonly Language ArabicSomalia = AddLanguage("ar-SO", "Arabic (Somalia)");
public static readonly Language ArabicSouthSudan = AddLanguage("ar-SS", "Arabic (South Sudan)");
public static readonly Language ArabicSyria = AddLanguage("ar-SY", "Arabic (Syria)");
public static readonly Language ArabicChad = AddLanguage("ar-TD", "Arabic (Chad)");
public static readonly Language ArabicTunisia = AddLanguage("ar-TN", "Arabic (Tunisia)");
public static readonly Language ArabicYemen = AddLanguage("ar-YE", "Arabic (Yemen)");
public static readonly Language BanglaBangladesh = AddLanguage("bn-BD", "Bangla (Bangladesh)");
public static readonly Language BanglaIndia = AddLanguage("bn-IN", "Bangla (India)");
public static readonly Language TibetanChina = AddLanguage("bo-CN", "Tibetan (China)");
public static readonly Language TibetanIndia = AddLanguage("bo-IN", "Tibetan (India)");
public static readonly Language CatalanAndorra = AddLanguage("ca-AD", "Catalan (Andorra)");
public static readonly Language CatalanCatalan = AddLanguage("ca-ES", "Catalan (Catalan)");
public static readonly Language CatalanFrance = AddLanguage("ca-FR", "Catalan (France)");
public static readonly Language CatalanItaly = AddLanguage("ca-IT", "Catalan (Italy)");
public static readonly Language DanishDenmark = AddLanguage("da-DK", "Danish (Denmark)");
public static readonly Language DanishGreenland = AddLanguage("da-GL", "Danish (Greenland)");
public static readonly Language GermanAustria = AddLanguage("de-AT", "German (Austria)");
public static readonly Language GermanBelgium = AddLanguage("de-BE", "German (Belgium)");
public static readonly Language GermanSwitzerland = AddLanguage("de-CH", "German (Switzerland)");
public static readonly Language GermanGermany = AddLanguage("de-DE", "German (Germany)");
public static readonly Language GermanItaly = AddLanguage("de-IT", "German (Italy)");
public static readonly Language GermanLiechtenstein = AddLanguage("de-LI", "German (Liechtenstein)");
public static readonly Language GermanLuxembourg = AddLanguage("de-LU", "German (Luxembourg)");
public static readonly Language EweGhana = AddLanguage("ee-GH", "Ewe (Ghana)");
public static readonly Language EweTogo = AddLanguage("ee-TG", "Ewe (Togo)");
public static readonly Language GreekCyprus = AddLanguage("el-CY", "Greek (Cyprus)");
public static readonly Language GreekGreece = AddLanguage("el-GR", "Greek (Greece)");
public static readonly Language EnglishAntiguaandBarbuda = AddLanguage("en-AG", "English (Antigua and Barbuda)");
public static readonly Language EnglishAnguilla = AddLanguage("en-AI", "English (Anguilla)");
public static readonly Language EnglishAmericanSamoa = AddLanguage("en-AS", "English (American Samoa)");
public static readonly Language EnglishAustria = AddLanguage("en-AT", "English (Austria)");
public static readonly Language EnglishAustralia = AddLanguage("en-AU", "English (Australia)");
public static readonly Language EnglishBarbados = AddLanguage("en-BB", "English (Barbados)");
public static readonly Language EnglishBelgium = AddLanguage("en-BE", "English (Belgium)");
public static readonly Language EnglishBurundi = AddLanguage("en-BI", "English (Burundi)");
public static readonly Language EnglishBermuda = AddLanguage("en-BM", "English (Bermuda)");
public static readonly Language EnglishBahamas = AddLanguage("en-BS", "English (Bahamas)");
public static readonly Language EnglishBotswana = AddLanguage("en-BW", "English (Botswana)");
public static readonly Language EnglishBelize = AddLanguage("en-BZ", "English (Belize)");
public static readonly Language EnglishCanada = AddLanguage("en-CA", "English (Canada)");
public static readonly Language EnglishCocosKeelingIslands = AddLanguage("en-CC", "English (Cocos (Keeling) Islands)");
public static readonly Language EnglishSwitzerland = AddLanguage("en-CH", "English (Switzerland)");
public static readonly Language EnglishCookIslands = AddLanguage("en-CK", "English (Cook Islands)");
public static readonly Language EnglishCameroon = AddLanguage("en-CM", "English (Cameroon)");
public static readonly Language EnglishChristmasIsland = AddLanguage("en-CX", "English (Christmas Island)");
public static readonly Language EnglishCyprus = AddLanguage("en-CY", "English (Cyprus)");
public static readonly Language EnglishGermany = AddLanguage("en-DE", "English (Germany)");
public static readonly Language EnglishDenmark = AddLanguage("en-DK", "English (Denmark)");
public static readonly Language EnglishDominica = AddLanguage("en-DM", "English (Dominica)");
public static readonly Language EnglishEritrea = AddLanguage("en-ER", "English (Eritrea)");
public static readonly Language EnglishFinland = AddLanguage("en-FI", "English (Finland)");
public static readonly Language EnglishFiji = AddLanguage("en-FJ", "English (Fiji)");
public static readonly Language EnglishFalklandIslands = AddLanguage("en-FK", "English (Falkland Islands)");
public static readonly Language EnglishMicronesia = AddLanguage("en-FM", "English (Micronesia)");
public static readonly Language EnglishUnitedKingdom = AddLanguage("en-GB", "English (United Kingdom)");
public static readonly Language EnglishGrenada = AddLanguage("en-GD", "English (Grenada)");
public static readonly Language EnglishGuernsey = AddLanguage("en-GG", "English (Guernsey)");
public static readonly Language EnglishGhana = AddLanguage("en-GH", "English (Ghana)");
public static readonly Language EnglishGibraltar = AddLanguage("en-GI", "English (Gibraltar)");
public static readonly Language EnglishGambia = AddLanguage("en-GM", "English (Gambia)");
public static readonly Language EnglishGuam = AddLanguage("en-GU", "English (Guam)");
public static readonly Language EnglishGuyana = AddLanguage("en-GY", "English (Guyana)");
public static readonly Language EnglishHongKongSAR = AddLanguage("en-HK", "English (Hong Kong SAR)");
public static readonly Language EnglishIndonesia = AddLanguage("en-ID", "English (Indonesia)");
public static readonly Language EnglishIreland = AddLanguage("en-IE", "English (Ireland)");
public static readonly Language EnglishIsrael = AddLanguage("en-IL", "English (Israel)");
public static readonly Language EnglishIsleofMan = AddLanguage("en-IM", "English (Isle of Man)");
public static readonly Language EnglishIndia = AddLanguage("en-IN", "English (India)");
public static readonly Language EnglishBritishIndianOceanTerritory = AddLanguage("en-IO", "English (British Indian Ocean Territory)");
public static readonly Language EnglishJersey = AddLanguage("en-JE", "English (Jersey)");
public static readonly Language EnglishJamaica = AddLanguage("en-JM", "English (Jamaica)");
public static readonly Language EnglishKenya = AddLanguage("en-KE", "English (Kenya)");
public static readonly Language EnglishKiribati = AddLanguage("en-KI", "English (Kiribati)");
public static readonly Language EnglishSaintKittsandNevis = AddLanguage("en-KN", "English (Saint Kitts and Nevis)");
public static readonly Language EnglishCaymanIslands = AddLanguage("en-KY", "English (Cayman Islands)");
public static readonly Language EnglishSaintLucia = AddLanguage("en-LC", "English (Saint Lucia)");
public static readonly Language EnglishLiberia = AddLanguage("en-LR", "English (Liberia)");
public static readonly Language EnglishLesotho = AddLanguage("en-LS", "English (Lesotho)");
public static readonly Language EnglishMadagascar = AddLanguage("en-MG", "English (Madagascar)");
public static readonly Language EnglishMarshallIslands = AddLanguage("en-MH", "English (Marshall Islands)");
public static readonly Language EnglishMacaoSAR = AddLanguage("en-MO", "English (Macao SAR)");
public static readonly Language EnglishNorthernMarianaIslands = AddLanguage("en-MP", "English (Northern Mariana Islands)");
public static readonly Language EnglishMontserrat = AddLanguage("en-MS", "English (Montserrat)");
public static readonly Language EnglishMalta = AddLanguage("en-MT", "English (Malta)");
public static readonly Language EnglishMauritius = AddLanguage("en-MU", "English (Mauritius)");
public static readonly Language EnglishMalawi = AddLanguage("en-MW", "English (Malawi)");
public static readonly Language EnglishMalaysia = AddLanguage("en-MY", "English (Malaysia)");
public static readonly Language EnglishNamibia = AddLanguage("en-NA", "English (Namibia)");
public static readonly Language EnglishNorfolkIsland = AddLanguage("en-NF", "English (Norfolk Island)");
public static readonly Language EnglishNigeria = AddLanguage("en-NG", "English (Nigeria)");
public static readonly Language EnglishNetherlands = AddLanguage("en-NL", "English (Netherlands)");
public static readonly Language EnglishNauru = AddLanguage("en-NR", "English (Nauru)");
public static readonly Language EnglishNiue = AddLanguage("en-NU", "English (Niue)");
public static readonly Language EnglishNewZealand = AddLanguage("en-NZ", "English (New Zealand)");
public static readonly Language EnglishPapuaNewGuinea = AddLanguage("en-PG", "English (Papua New Guinea)");
public static readonly Language EnglishPhilippines = AddLanguage("en-PH", "English (Philippines)");
public static readonly Language EnglishPakistan = AddLanguage("en-PK", "English (Pakistan)");
public static readonly Language EnglishPitcairnIslands = AddLanguage("en-PN", "English (Pitcairn Islands)");
public static readonly Language EnglishPuertoRico = AddLanguage("en-PR", "English (Puerto Rico)");
public static readonly Language EnglishPalau = AddLanguage("en-PW", "English (Palau)");
public static readonly Language EnglishRwanda = AddLanguage("en-RW", "English (Rwanda)");
public static readonly Language EnglishSolomonIslands = AddLanguage("en-SB", "English (Solomon Islands)");
public static readonly Language EnglishSeychelles = AddLanguage("en-SC", "English (Seychelles)");
public static readonly Language EnglishSudan = AddLanguage("en-SD", "English (Sudan)");
public static readonly Language EnglishSweden = AddLanguage("en-SE", "English (Sweden)");
public static readonly Language EnglishSingapore = AddLanguage("en-SG", "English (Singapore)");
public static readonly Language EnglishStHelenaAscensionTristandaCunha = AddLanguage("en-SH", "English (St Helena, Ascension, Tristan da Cunha)");
public static readonly Language EnglishSlovenia = AddLanguage("en-SI", "English (Slovenia)");
public static readonly Language EnglishSierraLeone = AddLanguage("en-SL", "English (Sierra Leone)");
public static readonly Language EnglishSouthSudan = AddLanguage("en-SS", "English (South Sudan)");
public static readonly Language EnglishSintMaarten = AddLanguage("en-SX", "English (Sint Maarten)");
public static readonly Language EnglishSwaziland = AddLanguage("en-SZ", "English (Swaziland)");
public static readonly Language EnglishTurksandCaicosIslands = AddLanguage("en-TC", "English (Turks and Caicos Islands)");
public static readonly Language EnglishTokelau = AddLanguage("en-TK", "English (Tokelau)");
public static readonly Language EnglishTonga = AddLanguage("en-TO", "English (Tonga)");
public static readonly Language EnglishTrinidadandTobago = AddLanguage("en-TT", "English (Trinidad and Tobago)");
public static readonly Language EnglishTuvalu = AddLanguage("en-TV", "English (Tuvalu)");
public static readonly Language EnglishTanzania = AddLanguage("en-TZ", "English (Tanzania)");
public static readonly Language EnglishUganda = AddLanguage("en-UG", "English (Uganda)");
public static readonly Language EnglishUSOutlyingIslands = AddLanguage("en-UM", "English (U.S. Outlying Islands)");
public static readonly Language EnglishUnitedStates = AddLanguage("en-US", "English (United States)");
public static readonly Language EnglishSaintVincentandtheGrenadines = AddLanguage("en-VC", "English (Saint Vincent and the Grenadines)");
public static readonly Language EnglishBritishVirginIslands = AddLanguage("en-VG", "English (British Virgin Islands)");
public static readonly Language EnglishUSVirginIslands = AddLanguage("en-VI", "English (U.S. Virgin Islands)");
public static readonly Language EnglishVanuatu = AddLanguage("en-VU", "English (Vanuatu)");
public static readonly Language EnglishSamoa = AddLanguage("en-WS", "English (Samoa)");
public static readonly Language EnglishSouthAfrica = AddLanguage("en-ZA", "English (South Africa)");
public static readonly Language EnglishZambia = AddLanguage("en-ZM", "English (Zambia)");
public static readonly Language EnglishZimbabwe = AddLanguage("en-ZW", "English (Zimbabwe)");
public static readonly Language SpanishArgentina = AddLanguage("es-AR", "Spanish (Argentina)");
public static readonly Language SpanishBolivia = AddLanguage("es-BO", "Spanish (Bolivia)");
public static readonly Language SpanishBrazil = AddLanguage("es-BR", "Spanish (Brazil)");
public static readonly Language SpanishBelize = AddLanguage("es-BZ", "Spanish (Belize)");
public static readonly Language SpanishChile = AddLanguage("es-CL", "Spanish (Chile)");
public static readonly Language SpanishColombia = AddLanguage("es-CO", "Spanish (Colombia)");
public static readonly Language SpanishCostaRica = AddLanguage("es-CR", "Spanish (Costa Rica)");
public static readonly Language SpanishCuba = AddLanguage("es-CU", "Spanish (Cuba)");
public static readonly Language SpanishDominicanRepublic = AddLanguage("es-DO", "Spanish (Dominican Republic)");
public static readonly Language SpanishEcuador = AddLanguage("es-EC", "Spanish (Ecuador)");
public static readonly Language SpanishSpainInternationalSort = AddLanguage("es-ES", "Spanish (Spain, International Sort)");
public static readonly Language SpanishEquatorialGuinea = AddLanguage("es-GQ", "Spanish (Equatorial Guinea)");
public static readonly Language SpanishGuatemala = AddLanguage("es-GT", "Spanish (Guatemala)");
public static readonly Language SpanishHonduras = AddLanguage("es-HN", "Spanish (Honduras)");
public static readonly Language SpanishMexico = AddLanguage("es-MX", "Spanish (Mexico)");
public static readonly Language SpanishNicaragua = AddLanguage("es-NI", "Spanish (Nicaragua)");
public static readonly Language SpanishPanama = AddLanguage("es-PA", "Spanish (Panama)");
public static readonly Language SpanishPeru = AddLanguage("es-PE", "Spanish (Peru)");
public static readonly Language SpanishPhilippines = AddLanguage("es-PH", "Spanish (Philippines)");
public static readonly Language SpanishPuertoRico = AddLanguage("es-PR", "Spanish (Puerto Rico)");
public static readonly Language SpanishParaguay = AddLanguage("es-PY", "Spanish (Paraguay)");
public static readonly Language SpanishElSalvador = AddLanguage("es-SV", "Spanish (El Salvador)");
public static readonly Language SpanishUnitedStates = AddLanguage("es-US", "Spanish (United States)");
public static readonly Language SpanishUruguay = AddLanguage("es-UY", "Spanish (Uruguay)");
public static readonly Language SpanishVenezuela = AddLanguage("es-VE", "Spanish (Venezuela)");
public static readonly Language FulahCameroon = AddLanguage("ff-CM", "Fulah (Cameroon)");
public static readonly Language FulahGuinea = AddLanguage("ff-GN", "Fulah (Guinea)");
public static readonly Language FulahMauritania = AddLanguage("ff-MR", "Fulah (Mauritania)");
public static readonly Language FulahNigeria = AddLanguage("ff-NG", "Fulah (Nigeria)");
public static readonly Language FaroeseDenmark = AddLanguage("fo-DK", "Faroese (Denmark)");
public static readonly Language FaroeseFaroeIslands = AddLanguage("fo-FO", "Faroese (Faroe Islands)");
public static readonly Language FrenchBelgium = AddLanguage("fr-BE", "French (Belgium)");
public static readonly Language FrenchBurkinaFaso = AddLanguage("fr-BF", "French (Burkina Faso)");
public static readonly Language FrenchBurundi = AddLanguage("fr-BI", "French (Burundi)");
public static readonly Language FrenchBenin = AddLanguage("fr-BJ", "French (Benin)");
public static readonly Language FrenchSaintBarthélemy = AddLanguage("fr-BL", "French (Saint Barthélemy)");
public static readonly Language FrenchCanada = AddLanguage("fr-CA", "French (Canada)");
public static readonly Language FrenchCongoDRC = AddLanguage("fr-CD", "French Congo (DRC)");
public static readonly Language FrenchCentralAfricanRepublic = AddLanguage("fr-CF", "French (Central African Republic)");
public static readonly Language FrenchCongo = AddLanguage("fr-CG", "French (Congo)");
public static readonly Language FrenchSwitzerland = AddLanguage("fr-CH", "French (Switzerland)");
public static readonly Language FrenchCôtedIvoire = AddLanguage("fr-CI", "French (Côte d’Ivoire)");
public static readonly Language FrenchCameroon = AddLanguage("fr-CM", "French (Cameroon)");
public static readonly Language FrenchDjibouti = AddLanguage("fr-DJ", "French (Djibouti)");
public static readonly Language FrenchAlgeria = AddLanguage("fr-DZ", "French (Algeria)");
public static readonly Language FrenchFrance = AddLanguage("fr-FR", "French (France)");
public static readonly Language FrenchGabon = AddLanguage("fr-GA", "French (Gabon)");
public static readonly Language FrenchFrenchGuiana = AddLanguage("fr-GF", "French (French Guiana)");
public static readonly Language FrenchGuinea = AddLanguage("fr-GN", "French (Guinea)");
public static readonly Language FrenchGuadeloupe = AddLanguage("fr-GP", "French (Guadeloupe)");
public static readonly Language FrenchEquatorialGuinea = AddLanguage("fr-GQ", "French (Equatorial Guinea)");
public static readonly Language FrenchHaiti = AddLanguage("fr-HT", "French (Haiti)");
public static readonly Language FrenchComoros = AddLanguage("fr-KM", "French (Comoros)");
public static readonly Language FrenchLuxembourg = AddLanguage("fr-LU", "French (Luxembourg)");
public static readonly Language FrenchMorocco = AddLanguage("fr-MA", "French (Morocco)");
public static readonly Language FrenchMonaco = AddLanguage("fr-MC", "French (Monaco)");
public static readonly Language FrenchSaintMartin = AddLanguage("fr-MF", "French (Saint Martin)");
public static readonly Language FrenchMadagascar = AddLanguage("fr-MG", "French (Madagascar)");
public static readonly Language FrenchMali = AddLanguage("fr-ML", "French (Mali)");
public static readonly Language FrenchMartinique = AddLanguage("fr-MQ", "French (Martinique)");
public static readonly Language FrenchMauritania = AddLanguage("fr-MR", "French (Mauritania)");
public static readonly Language FrenchMauritius = AddLanguage("fr-MU", "French (Mauritius)");
public static readonly Language FrenchNewCaledonia = AddLanguage("fr-NC", "French (New Caledonia)");
public static readonly Language FrenchNiger = AddLanguage("fr-NE", "French (Niger)");
public static readonly Language FrenchFrenchPolynesia = AddLanguage("fr-PF", "French (French Polynesia)");
public static readonly Language FrenchSaintPierreandMiquelon = AddLanguage("fr-PM", "French (Saint Pierre and Miquelon)");
public static readonly Language FrenchRéunion = AddLanguage("fr-RE", "French (Réunion)");
public static readonly Language FrenchRwanda = AddLanguage("fr-RW", "French (Rwanda)");
public static readonly Language FrenchSeychelles = AddLanguage("fr-SC", "French (Seychelles)");
public static readonly Language FrenchSenegal = AddLanguage("fr-SN", "French (Senegal)");
public static readonly Language FrenchSyria = AddLanguage("fr-SY", "French (Syria)");
public static readonly Language FrenchChad = AddLanguage("fr-TD", "French (Chad)");
public static readonly Language FrenchTogo = AddLanguage("fr-TG", "French (Togo)");
public static readonly Language FrenchTunisia = AddLanguage("fr-TN", "French (Tunisia)");
public static readonly Language FrenchVanuatu = AddLanguage("fr-VU", "French (Vanuatu)");
public static readonly Language FrenchWallisandFutuna = AddLanguage("fr-WF", "French (Wallis and Futuna)");
public static readonly Language FrenchMayotte = AddLanguage("fr-YT", "French (Mayotte)");
public static readonly Language CroatianBosniaandHerzegovina = AddLanguage("hr-BA", "Croatian (Bosnia and Herzegovina)");
public static readonly Language CroatianCroatia = AddLanguage("hr-HR", "Croatian (Croatia)");
public static readonly Language ItalianSwitzerland = AddLanguage("it-CH", "Italian (Switzerland)");
public static readonly Language ItalianItaly = AddLanguage("it-IT", "Italian (Italy)");
public static readonly Language ItalianSanMarino = AddLanguage("it-SM", "Italian (San Marino)");
public static readonly Language ItalianVaticanCity = AddLanguage("it-VA", "Italian (Vatican City)");
public static readonly Language KoreanNorthKorea = AddLanguage("ko-KP", "Korean (North Korea)");
public static readonly Language KoreanKorea = AddLanguage("ko-KR", "Korean (Korea)");
public static readonly Language LingalaAngola = AddLanguage("ln-AO", "Lingala (Angola)");
public static readonly Language LingalaCongoDRC = AddLanguage("ln-CD", "Lingala (Congo DRC)");
public static readonly Language LingalaCentralAfricanRepublic = AddLanguage("ln-CF", "Lingala (Central African Republic)");
public static readonly Language LingalaCongo = AddLanguage("ln-CG", "Lingala (Congo)");
public static readonly Language MalayBrunei = AddLanguage("ms-BN", "Malay (Brunei)");
public static readonly Language MalayMalaysia = AddLanguage("ms-MY", "Malay (Malaysia)");
public static readonly Language MalaySingapore = AddLanguage("ms-SG", "Malay (Singapore)");
public static readonly Language NorwegianBokmålNorway = AddLanguage("nb-NO", "Norwegian Bokmål (Norway)");
public static readonly Language NorwegianBokmålSvalbardandJanMayen = AddLanguage("nb-SJ", "Norwegian Bokmål (Svalbard and Jan Mayen)");
public static readonly Language NepaliIndia = AddLanguage("ne-IN", "Nepali (India)");
public static readonly Language NepaliNepal = AddLanguage("ne-NP", "Nepali (Nepal)");
public static readonly Language DutchAruba = AddLanguage("nl-AW", "Dutch (Aruba)");
public static readonly Language DutchBelgium = AddLanguage("nl-BE", "Dutch (Belgium)");
public static readonly Language DutchBonaireSintEustatiusandSaba = AddLanguage("nl-BQ", "Dutch (Bonaire, Sint Eustatius and Saba)");
public static readonly Language DutchCuraçao = AddLanguage("nl-CW", "Dutch (Curaçao)");
public static readonly Language DutchNetherlands = AddLanguage("nl-NL", "Dutch (Netherlands)");
public static readonly Language DutchSuriname = AddLanguage("nl-SR", "Dutch (Suriname)");
public static readonly Language DutchSintMaarten = AddLanguage("nl-SX", "Dutch (Sint Maarten)");
public static readonly Language OromoEthiopia = AddLanguage("om-ET", "Oromo (Ethiopia)");
public static readonly Language OromoKenya = AddLanguage("om-KE", "Oromo (Kenya)");
public static readonly Language PortugueseAngola = AddLanguage("pt-AO", "Portuguese (Angola)");
public static readonly Language PortugueseBrazil = AddLanguage("pt-BR", "Portuguese (Brazil)");
public static readonly Language PortugueseSwitzerland = AddLanguage("pt-CH", "Portuguese (Switzerland)");
public static readonly Language PortugueseCaboVerde = AddLanguage("pt-CV", "Portuguese (Cabo Verde)");
public static readonly Language PortugueseEquatorialGuinea = AddLanguage("pt-GQ", "Portuguese (Equatorial Guinea)");
public static readonly Language PortugueseGuineaBissau = AddLanguage("pt-GW", "Portuguese (Guinea-Bissau)");
public static readonly Language PortugueseLuxembourg = AddLanguage("pt-LU", "Portuguese (Luxembourg)");
public static readonly Language PortugueseMacaoSAR = AddLanguage("pt-MO", "Portuguese (Macao SAR)");
public static readonly Language PortugueseMozambique = AddLanguage("pt-MZ", "Portuguese (Mozambique)");
public static readonly Language PortuguesePortugal = AddLanguage("pt-PT", "Portuguese (Portugal)");
public static readonly Language PortugueseSãoToméandPríncipe = AddLanguage("pt-ST", "Portuguese (São Tomé and Príncipe)");
public static readonly Language PortugueseTimorLeste = AddLanguage("pt-TL", "Portuguese (Timor-Leste)");
public static readonly Language RomanianMoldova = AddLanguage("ro-MD", "Romanian (Moldova)");
public static readonly Language RomanianRomania = AddLanguage("ro-RO", "Romanian (Romania)");
public static readonly Language RussianBelarus = AddLanguage("ru-BY", "Russian (Belarus)");
public static readonly Language RussianKyrgyzstan = AddLanguage("ru-KG", "Russian (Kyrgyzstan)");
public static readonly Language RussianKazakhstan = AddLanguage("ru-KZ", "Russian (Kazakhstan)");
public static readonly Language RussianMoldova = AddLanguage("ru-MD", "Russian (Moldova)");
public static readonly Language RussianRussia = AddLanguage("ru-RU", "Russian (Russia)");
public static readonly Language RussianUkraine = AddLanguage("ru-UA", "Russian (Ukraine)");
public static readonly Language SamiNorthernFinland = AddLanguage("se-FI", "Sami, Northern (Finland)");
public static readonly Language SamiNorthernNorway = AddLanguage("se-NO", "Sami, Northern (Norway)");
public static readonly Language SamiNorthernSweden = AddLanguage("se-SE", "Sami, Northern (Sweden)");
public static readonly Language SomaliDjibouti = AddLanguage("so-DJ", "Somali (Djibouti)");
public static readonly Language SomaliEthiopia = AddLanguage("so-ET", "Somali (Ethiopia)");
public static readonly Language SomaliKenya = AddLanguage("so-KE", "Somali (Kenya)");
public static readonly Language SomaliSomalia = AddLanguage("so-SO", "Somali (Somalia)");
public static readonly Language AlbanianAlbania = AddLanguage("sq-AL", "Albanian (Albania)");
public static readonly Language AlbanianMacedoniaFYRO = AddLanguage("sq-MK", "Albanian (Macedonia, FYRO)");
public static readonly Language AlbanianKosovo = AddLanguage("sq-XK", "Albanian (Kosovo)");
public static readonly Language siSwatiSwaziland = AddLanguage("ss-SZ", "siSwati (Swaziland)");
public static readonly Language siSwatiSouthAfrica = AddLanguage("ss-ZA", "siSwati (South Africa)");
public static readonly Language SesothoLesotho = AddLanguage("st-LS", "Sesotho (Lesotho)");
public static readonly Language SesothoSouthAfrica = AddLanguage("st-ZA", "Sesotho (South Africa)");
public static readonly Language SwedishÅlandIslands = AddLanguage("sv-AX", "Swedish (Åland Islands)");
public static readonly Language SwedishFinland = AddLanguage("sv-FI", "Swedish (Finland)");
public static readonly Language SwedishSweden = AddLanguage("sv-SE", "Swedish (Sweden)");
public static readonly Language KiswahiliCongoDRC = AddLanguage("sw-CD", "Kiswahili (Congo DRC)");
public static readonly Language KiswahiliKenya = AddLanguage("sw-KE", "Kiswahili (Kenya)");
public static readonly Language KiswahiliTanzania = AddLanguage("sw-TZ", "Kiswahili (Tanzania)");
public static readonly Language KiswahiliUganda = AddLanguage("sw-UG", "Kiswahili (Uganda)");
public static readonly Language TamilIndia = AddLanguage("ta-IN", "Tamil (India)");
public static readonly Language TamilSriLanka = AddLanguage("ta-LK", "Tamil (Sri Lanka)");
public static readonly Language TamilMalaysia = AddLanguage("ta-MY", "Tamil (Malaysia)");
public static readonly Language TamilSingapore = AddLanguage("ta-SG", "Tamil (Singapore)");
public static readonly Language TigrinyaEritrea = AddLanguage("ti-ER", "Tigrinya (Eritrea)");
public static readonly Language TigrinyaEthiopia = AddLanguage("ti-ET", "Tigrinya (Ethiopia)");
public static readonly Language SetswanaBotswana = AddLanguage("tn-BW", "Setswana (Botswana)");
public static readonly Language SetswanaSouthAfrica = AddLanguage("tn-ZA", "Setswana (South Africa)");
public static readonly Language TurkishCyprus = AddLanguage("tr-CY", "Turkish (Cyprus)");
public static readonly Language TurkishTurkey = AddLanguage("tr-TR", "Turkish (Turkey)");
public static readonly Language UrduIndia = AddLanguage("ur-IN", "Urdu (India)");
public static readonly Language UrduPakistan = AddLanguage("ur-PK", "Urdu (Pakistan)");
public static readonly Language YorubaBenin = AddLanguage("yo-BJ", "Yoruba (Benin)");
public static readonly Language YorubaNigeria = AddLanguage("yo-NG", "Yoruba (Nigeria)");
public static readonly Language ChineseSimplifiedChina = AddLanguage("zh-CN", "Chinese (Simplified, China)");
public static readonly Language ChineseTraditionalHongKongSAR = AddLanguage("zh-HK", "Chinese (Traditional, Hong Kong SAR)");
public static readonly Language ChineseTraditionalMacaoSAR = AddLanguage("zh-MO", "Chinese (Traditional, Macao SAR)");
public static readonly Language ChineseSimplifiedSingapore = AddLanguage("zh-SG", "Chinese (Simplified, Singapore)");
public static readonly Language ChineseTraditionalTaiwan = AddLanguage("zh-TW", "Chinese (Traditional, Taiwan)");
}
}

22
src/Squidex.Infrastructure/Log/FileChannel.cs

@ -9,9 +9,11 @@ using Squidex.Infrastructure.Log.Internal;
namespace Squidex.Infrastructure.Log
{
public sealed class FileChannel : DisposableObjectBase, ILogChannel, IInitializable
public sealed class FileChannel : DisposableObjectBase, ILogChannel
{
private readonly FileLogProcessor processor;
private readonly object lockObject = new object();
private bool isInitialized;
public FileChannel(string path)
{
@ -30,12 +32,20 @@ namespace Squidex.Infrastructure.Log
public void Log(SemanticLogLevel logLevel, string message)
{
processor.EnqueueMessage(new LogMessageEntry { Message = message });
}
if (!isInitialized)
{
lock (lockObject)
{
if (!isInitialized)
{
processor.Initialize();
public void Initialize()
{
processor.Connect();
isInitialized = true;
}
}
}
processor.EnqueueMessage(new LogMessageEntry { Message = message });
}
}
}

27
src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs

@ -20,6 +20,7 @@ namespace Squidex.Infrastructure.Log.Internal
private readonly BlockingCollection<LogMessageEntry> messageQueue = new BlockingCollection<LogMessageEntry>(MaxQueuedMessages);
private readonly Task outputTask;
private readonly string path;
private StreamWriter writer;
public FileLogProcessor(string path)
{
@ -45,16 +46,33 @@ namespace Squidex.Infrastructure.Log.Internal
throw;
}
}
finally
{
writer.Dispose();
}
}
}
public void Connect()
public void Initialize()
{
var fileInfo = new FileInfo(path);
try
{
if (!fileInfo.Directory.Exists)
{
fileInfo.Directory.Create();
}
if (!fileInfo.Directory.Exists)
var fs = new FileStream(fileInfo.FullName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
writer = new StreamWriter(fs, Encoding.UTF8);
writer.AutoFlush = true;
writer.WriteLine($"--- Started Logging {DateTime.UtcNow} ---", 1);
}
catch (Exception ex)
{
throw new ConfigurationException($"Log directory '{fileInfo.Directory.FullName}' does not exist.");
throw new ConfigurationException($"Log directory '{fileInfo.Directory.FullName}' does not exist or cannot be created.", ex);
}
}
@ -71,8 +89,7 @@ namespace Squidex.Infrastructure.Log.Internal
{
try
{
File.AppendAllText(path, entry.Message + Environment.NewLine, Encoding.UTF8);
writer.WriteLine(entry.Message);
break;
}
catch (Exception ex)

40
src/Squidex.Infrastructure/Orleans/InitializerStartup.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Orleans.Runtime;
using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.Orleans
{
public sealed class InitializerStartup : IStartupTask
{
private readonly IEnumerable<IInitializable> targets;
private readonly ISemanticLog log;
public InitializerStartup(IEnumerable<IInitializable> targets, ISemanticLog log)
{
Guard.NotNull(targets, nameof(targets));
this.targets = targets;
this.log = log;
}
public async Task Execute(CancellationToken cancellationToken)
{
foreach (var target in targets)
{
await target.InitializeAsync(cancellationToken);
log?.LogInformation(w => w.WriteProperty("siloInitializedSystem", target.GetType().Name));
}
}
}
}

8
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,12 +11,12 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.0-rc2">
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="2.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NodaTime" Version="2.4.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
@ -25,7 +25,7 @@
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Reactive" Version="4.1.0" />
<PackageReference Include="System.Reactive" Version="4.1.1" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.5.1" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.9.0" />

7
src/Squidex/AppServices.cs

@ -14,6 +14,8 @@ using Squidex.Config;
using Squidex.Config.Authentication;
using Squidex.Config.Domain;
using Squidex.Config.Web;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Extensions.Actions.Twitter;
using Squidex.Infrastructure.Commands;
@ -44,9 +46,12 @@ namespace Squidex
services.AddMySwaggerSettings();
services.AddMySubscriptionServices(config);
services.Configure<ContentOptions>(
config.GetSection("contents"));
services.Configure<AssetOptions>(
config.GetSection("assets"));
services.Configure<ReadonlyOptions>(
config.GetSection("mode"));
services.Configure<TwitterOptions>(
config.GetSection("twitter"));

3
src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -20,7 +19,7 @@ namespace Squidex.Areas.Api.Config.Swagger
{
var urlOptions = app.ApplicationServices.GetService<IOptions<MyUrlsOptions>>().Value;
app.UseSwagger(typeof(SwaggerExtensions).GetTypeInfo().Assembly, settings =>
app.UseSwaggerWithApiExplorer(settings =>
{
settings.AddAssetODataParams();
settings.ConfigureNames();

1
src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs

@ -106,7 +106,6 @@ namespace Squidex.Areas.Api.Config.Swagger
settings.GeneratorSettings.DocumentProcessors.Add(new RuleActionProcessor());
settings.GeneratorSettings.DocumentProcessors.Add(new XmlTagProcessor());
settings.GeneratorSettings.OperationProcessors.Add(new XmlTagProcessor());
settings.GeneratorSettings.OperationProcessors.Add(new XmlResponseTypesProcessor());
return settings;

6
src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NJsonSchema.Infrastructure;
@ -43,7 +42,7 @@ namespace Squidex.Areas.Api.Config.Swagger
response.Description = match.Groups["Description"].Value;
if (string.Equals(statusCode, "200", StringComparison.OrdinalIgnoreCase))
if (statusCode == "200" || statusCode == "204")
{
hasOkResponse = true;
}
@ -72,8 +71,7 @@ namespace Squidex.Areas.Api.Config.Swagger
private static void RemoveOkResponse(SwaggerOperation operation)
{
if (operation.Responses.TryGetValue("200", out var response) &&
response.Description != null &&
response.Description.Contains("=>"))
response.Description?.Contains("=>") == true)
{
operation.Responses.Remove("200");
}

15
src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs

@ -16,7 +16,7 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Areas.Api.Config.Swagger
{
public sealed class XmlTagProcessor : IOperationProcessor, IDocumentProcessor
public sealed class XmlTagProcessor : IDocumentProcessor
{
public Task ProcessAsync(DocumentProcessorContext context)
{
@ -47,18 +47,5 @@ namespace Squidex.Areas.Api.Config.Swagger
return TaskHelper.Done;
}
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
var tagAttribute = context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttribute<SwaggerTagAttribute>();
if (tagAttribute != null)
{
context.OperationDescription.Operation.Tags.Clear();
context.OperationDescription.Operation.Tags.Add(tagAttribute.Name);
}
return TaskHelper.True;
}
}
}

3
src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs

@ -8,7 +8,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands;
@ -23,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[SwaggerTag(nameof(Apps))]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppClientsController : ApiController
{
public AppClientsController(ICommandBus commandBus)

3
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
@ -23,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiExceptionFilter]
[AppApi]
[MustBeAppOwner]
[SwaggerTag(nameof(Apps))]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppContributorsController : ApiController
{
private readonly IAppPlansProvider appPlansProvider;

3
src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
@ -23,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Apps))]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppLanguagesController : ApiController
{
public AppLanguagesController(ICommandBus commandBus)

5
src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs

@ -9,7 +9,6 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands;
@ -24,7 +23,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[MustBeAppDeveloper]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Apps))]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppPatternsController : ApiController
{
public AppPatternsController(ICommandBus commandBus)
@ -104,7 +103,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
}
/// <summary>
/// Revoke an app client.
/// Delete an existing app pattern.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The id of the pattern to be deleted.</param>

3
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -9,7 +9,6 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -25,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[SwaggerTag(nameof(Apps))]
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppsController : ApiController
{
private readonly IAppProvider appProvider;

3
src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -9,7 +9,6 @@ using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
@ -24,7 +23,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// </summary>
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Assets))]
[ApiExplorerSettings(GroupName = nameof(Assets))]
public sealed class AssetContentController : ApiController
{
private readonly IAssetStore assetStore;

2
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -33,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Assets))]
[ApiExplorerSettings(GroupName = nameof(Assets))]
public sealed class AssetsController : ApiController
{
private readonly IAssetQueryService assetQuery;

3
src/Squidex/Areas/Api/Controllers/Backups/BackupContentController.cs

@ -7,7 +7,6 @@
using System;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -19,7 +18,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// </summary>
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Backups))]
[ApiExplorerSettings(GroupName = nameof(Backups))]
public class BackupContentController : ApiController
{
private readonly IAssetStore assetStore;

3
src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.Backups.Models;
using Squidex.Domain.Apps.Entities.Backup;
@ -27,7 +26,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
[ApiExceptionFilter]
[AppApi]
[MustBeAppOwner]
[SwaggerTag(nameof(Backups))]
[ApiExplorerSettings(GroupName = nameof(Backups))]
public class BackupsController : ApiController
{
private readonly IGrainFactory grainFactory;

3
src/Squidex/Areas/Api/Controllers/History/HistoryController.cs

@ -8,7 +8,6 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.History.Models;
using Squidex.Domain.Apps.Entities.History.Repositories;
using Squidex.Infrastructure.Commands;
@ -23,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.History
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[SwaggerTag(nameof(History))]
[ApiExplorerSettings(GroupName = nameof(History))]
public sealed class HistoryController : ApiController
{
private readonly IHistoryEventRepository historyEventRepository;

3
src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs

@ -7,7 +7,6 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -19,7 +18,7 @@ namespace Squidex.Areas.Api.Controllers.Languages
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[SwaggerTag(nameof(Languages))]
[ApiExplorerSettings(GroupName = nameof(Languages))]
public sealed class LanguagesController : ApiController
{
public LanguagesController(ICommandBus commandBus)

27
src/Squidex/Areas/Api/Controllers/Ping/PingController.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -15,11 +14,8 @@ namespace Squidex.Areas.Api.Controllers.Ping
/// <summary>
/// Makes a ping request.
/// </summary>
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[MustBeAppReader]
[SwaggerTag(nameof(Ping))]
[ApiExplorerSettings(GroupName = nameof(Ping))]
public sealed class PingController : ApiController
{
public PingController(ICommandBus commandBus)
@ -27,6 +23,22 @@ namespace Squidex.Areas.Api.Controllers.Ping
{
}
/// <summary>
/// Get ping status of the API.
/// </summary>
/// <returns>
/// 204 => Service ping successful.
/// </returns>
/// <remarks>
/// Can be used to test, if the Squidex API is alive and responding.
/// </remarks>
[HttpGet]
[Route("ping/")]
public IActionResult GetPing()
{
return NoContent();
}
/// <summary>
/// Get ping status.
/// </summary>
@ -37,9 +49,12 @@ namespace Squidex.Areas.Api.Controllers.Ping
/// <remarks>
/// Can be used to test, if the Squidex API is alive and responding.
/// </remarks>
[ApiAuthorize]
[ApiCosts(0)]
[AppApi]
[MustBeAppReader]
[HttpGet]
[Route("ping/{app}/")]
[ApiCosts(0)]
public IActionResult GetPing(string app)
{
return NoContent();

6
src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs

@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Plans.Models;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Commands;
@ -21,7 +20,7 @@ namespace Squidex.Areas.Api.Controllers.Plans
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Plans))]
[ApiExplorerSettings(GroupName = nameof(Plans))]
public sealed class AppPlansController : ApiController
{
private readonly IAppPlansProvider appPlansProvider;
@ -66,8 +65,7 @@ namespace Squidex.Areas.Api.Controllers.Plans
/// <param name="app">The name of the app.</param>
/// <param name="request">Plan object that needs to be changed.</param>
/// <returns>
/// 201 => Redirected to checkout page.
/// 204 => Plan changed.
/// 200 => Plan changed or redirect url returned.
/// 400 => Plan not owned by user.
/// 404 => App not found.
/// </returns>

3
src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -11,7 +11,6 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Rules.Commands;
@ -29,7 +28,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Rules))]
[ApiExplorerSettings(GroupName = nameof(Rules))]
[MustBeAppDeveloper]
public sealed class RulesController : ApiController
{

3
src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs

@ -7,7 +7,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Schemas.Models;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Commands;
@ -22,7 +21,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[ApiExceptionFilter]
[AppApi]
[MustBeAppDeveloper]
[SwaggerTag(nameof(Schemas))]
[ApiExplorerSettings(GroupName = nameof(Schemas))]
public sealed class SchemaFieldsController : ApiController
{
public SchemaFieldsController(ICommandBus commandBus)

3
src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -9,7 +9,6 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Schemas.Models;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Schemas;
@ -25,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(Schemas))]
[ApiExplorerSettings(GroupName = nameof(Schemas))]
public sealed class SchemasController : ApiController
{
private readonly IAppProvider appProvider;

3
src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Statistics.Models;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
@ -27,7 +26,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[ApiExceptionFilter]
[AppApi]
[MustBeAppEditor]
[SwaggerTag(nameof(Statistics))]
[ApiExplorerSettings(GroupName = nameof(Statistics))]
public sealed class UsagesController : ApiController
{
private readonly IUsageTracker usageTracker;

3
src/Squidex/Areas/Api/Controllers/UI/UIController.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.UI.Models;
using Squidex.Config;
@ -25,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.UI
[ApiAuthorize]
[ApiExceptionFilter]
[AppApi]
[SwaggerTag(nameof(UI))]
[ApiExplorerSettings(GroupName = nameof(UI))]
public sealed class UIController : ApiController
{
private readonly MyUIOptions uiOptions;

3
src/Squidex/Areas/Api/Controllers/Users/UsersController.cs

@ -12,7 +12,6 @@ using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Users.Models;
using Squidex.Domain.Users;
using Squidex.Infrastructure.Commands;
@ -26,7 +25,7 @@ namespace Squidex.Areas.Api.Controllers.Users
/// Readonly API to retrieve information about squidex users.
/// </summary>
[ApiExceptionFilter]
[SwaggerTag(nameof(Users))]
[ApiExplorerSettings(GroupName = nameof(Users))]
public sealed class UsersController : ApiController
{
private static readonly byte[] AvatarBytes;

6
src/Squidex/Config/Domain/EntitiesServices.cs

@ -65,9 +65,15 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppProvider>()
.As<IAppProvider>();
services.AddSingletonAs(c => c.GetRequiredService<IOptions<AssetOptions>>().Value)
.AsSelf();
services.AddSingletonAs<AssetQueryService>()
.As<IAssetQueryService>();
services.AddSingletonAs(c => c.GetRequiredService<IOptions<ContentOptions>>().Value)
.AsSelf();
services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>();

14
src/Squidex/Config/Domain/LoggingExtensions.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -19,15 +20,20 @@ namespace Squidex.Config.Domain
{
var log = services.GetRequiredService<ISemanticLog>();
var config = services.GetRequiredService<IConfiguration>();
log.LogInformation(w => w
.WriteProperty("message", "Application started")
.WriteObject("environment", c =>
{
foreach (var kvp in config.AsEnumerable().Where(kvp => kvp.Value != null))
var config = services.GetRequiredService<IConfiguration>();
var logged = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in config.AsEnumerable().Where(kvp => kvp.Value != null).Select(x => new { Key = x.Key.ToLowerInvariant(), x.Value }).OrderBy(x => x.Key))
{
c.WriteProperty(kvp.Key, kvp.Value);
if (logged.Add(kvp.Key))
{
c.WriteProperty(kvp.Key, kvp.Value);
}
}
}));
}

4
src/Squidex/Config/Domain/LoggingServices.cs

@ -9,7 +9,6 @@ using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Pipeline;
@ -38,8 +37,7 @@ namespace Squidex.Config.Domain
if (!string.IsNullOrWhiteSpace(loggingFile))
{
services.AddSingletonAs(file ?? (file = new FileChannel(loggingFile)))
.As<ILogChannel>()
.As<IInitializable>();
.As<ILogChannel>();
}
var useColors = config.GetValue<bool>("logging:colors");

53
src/Squidex/Config/Domain/SystemExtensions.cs

@ -5,41 +5,64 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Migrations;
namespace Squidex.Config.Domain
{
public static class SystemExtensions
{
public static void RunInitialization(this IServiceProvider services)
public sealed class InitializeHostedService : IHostedService
{
var systems = services.GetRequiredService<IEnumerable<IInitializable>>();
private readonly IEnumerable<IInitializable> targets;
private readonly ISemanticLog log;
foreach (var system in systems)
public InitializeHostedService(IEnumerable<IInitializable> targets, ISemanticLog log)
{
system.Initialize();
this.targets = targets;
this.log = log;
}
}
public static void RunRunnables(this IServiceProvider services)
{
var systems = services.GetRequiredService<IEnumerable<IRunnable>>();
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var target in targets)
{
await target.InitializeAsync(cancellationToken);
foreach (var system in systems)
log.LogInformation(w => w.WriteProperty("initializedSystem", target.GetType().Name));
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
system.Run();
return Task.CompletedTask;
}
}
public static void RunMigrate(this IServiceProvider services)
public sealed class MigratorHostedService : IHostedService
{
var migrator = services.GetRequiredService<Migrator>();
private readonly Migrator migrator;
public MigratorHostedService(Migrator migrator)
{
this.migrator = migrator;
}
migrator.MigrateAsync().Wait();
public Task StartAsync(CancellationToken cancellationToken)
{
return migrator.MigrateAsync();
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
}

10
src/Squidex/Config/Logging.cs

@ -16,6 +16,16 @@ namespace Squidex.Config
{
builder.AddFilter((category, level) =>
{
if (category.StartsWith("Orleans.Runtime.NoOpHostEnvironmentStatistics", StringComparison.OrdinalIgnoreCase))
{
return level >= LogLevel.Error;
}
if (category.StartsWith("Orleans.Runtime.Scheduler", StringComparison.OrdinalIgnoreCase))
{
return level >= LogLevel.Error;
}
if (category.StartsWith("Orleans.", StringComparison.OrdinalIgnoreCase))
{
return level >= LogLevel.Warning;

6
src/Squidex/Config/Orleans/OrleansServices.cs

@ -6,8 +6,8 @@
// ==========================================================================
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Orleans;
using Squidex.Infrastructure;
namespace Squidex.Config.Orleans
{
@ -16,7 +16,7 @@ namespace Squidex.Config.Orleans
public static void AddOrleansSilo(this IServiceCollection services)
{
services.AddSingletonAs<SiloWrapper>()
.As<IInitializable>()
.As<IHostedService>()
.AsSelf();
services.AddServicesForSelfHostedDashboard(null, options =>
@ -24,7 +24,7 @@ namespace Squidex.Config.Orleans
options.HideTrace = true;
});
services.AddSingletonAs(c => c.GetRequiredService<SiloWrapper>().Client)
services.AddSingletonAs(c => c.GetRequiredService<IClusterClient>())
.As<IClusterClient>();
services.AddSingletonAs(c => c.GetRequiredService<SiloWrapper>().Client)

49
src/Squidex/Config/Orleans/SiloWrapper.cs

@ -7,14 +7,15 @@
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using Squidex.Config.Domain;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure;
@ -22,14 +23,14 @@ using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Log.Adapter;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Config.Orleans
{
public sealed class SiloWrapper : DisposableObjectBase, IInitializable, IDisposable
public sealed class SiloWrapper : IHostedService
{
private readonly Lazy<ISiloHost> silo;
private readonly Lazy<ISiloHost> lazySilo;
private readonly ISemanticLog log;
private bool isStopping;
internal sealed class Source : IConfigurationSource
{
@ -48,28 +49,23 @@ namespace Squidex.Config.Orleans
public IClusterClient Client
{
get { return silo.Value.Services.GetRequiredService<IClusterClient>(); }
get { return lazySilo.Value.Services.GetRequiredService<IClusterClient>(); }
}
public SiloWrapper(IConfiguration config, ISemanticLog log)
public SiloWrapper(IConfiguration config, ISemanticLog log, IApplicationLifetime lifetime)
{
this.log = log;
silo = new Lazy<ISiloHost>(() =>
lazySilo = new Lazy<ISiloHost>(() =>
{
var hostBuilder = new SiloHostBuilder()
.UseDashboard(options => options.HostSelf = false)
.EnableDirectClient()
.AddIncomingGrainCallFilter<LocalCacheFilter>()
.AddStartupTask<InitializerStartup>()
.AddStartupTask<Bootstrap<IContentSchedulerGrain>>()
.AddStartupTask<Bootstrap<IEventConsumerManagerGrain>>()
.AddStartupTask<Bootstrap<IRuleDequeuerGrain>>()
.AddStartupTask((services, ct) =>
{
services.RunInitialization();
return TaskHelper.Done;
})
.Configure<ClusterOptions>(options =>
{
options.Configure();
@ -141,16 +137,26 @@ namespace Squidex.Config.Orleans
}
});
return hostBuilder.Build();
var silo = hostBuilder.Build();
silo.Stopped.ContinueWith(x =>
{
if (!isStopping)
{
lifetime.StopApplication();
}
});
return silo;
});
}
public void Initialize()
public async Task StartAsync(CancellationToken cancellationToken)
{
var watch = ValueStopwatch.StartNew();
try
{
silo.Value.StartAsync().Wait();
await lazySilo.Value.StartAsync(cancellationToken);
}
finally
{
@ -162,14 +168,13 @@ namespace Squidex.Config.Orleans
}
}
protected override void DisposeObject(bool disposing)
public async Task StopAsync(CancellationToken cancellationToken)
{
if (disposing)
if (lazySilo.IsValueCreated)
{
if (silo.IsValueCreated)
{
Task.Run(() => silo.Value.StopAsync()).Wait();
}
isStopping = true;
await lazySilo.Value.StopAsync(cancellationToken);
}
}
}

20
src/Squidex/Squidex.csproj

@ -54,36 +54,36 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="Algolia.Search" Version="5.2.0" />
<PackageReference Include="Ben.BlockingDetector" Version="0.0.3" />
<PackageReference Include="EventStore.ClientAPI.NetCore" Version="4.1.0.23" />
<PackageReference Include="IdentityServer4" Version="2.2.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.4" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.0-rc2" />
<PackageReference Include="Microsoft.Orleans.Client" Version="2.1.0" />
<PackageReference Include="Microsoft.Orleans.Core" Version="2.1.0" />
<PackageReference Include="Microsoft.Orleans.Core.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="2.1.0" />
<PackageReference Include="MongoDB.Driver" Version="2.7.0" />
<PackageReference Include="NJsonSchema" Version="9.10.75" />
<PackageReference Include="NJsonSchema" Version="9.11.0" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" />
<PackageReference Include="NSwag.AspNetCore" Version="11.19.2" />
<PackageReference Include="NSwag.AspNetCore" Version="11.20.1" />
<PackageReference Include="OpenCover" Version="4.6.519" />
<PackageReference Include="Orleans.Providers.MongoDB" Version="2.0.1" />
<PackageReference Include="OrleansDashboard" Version="2.0.9" />
<PackageReference Include="OrleansDashboard" Version="2.1.2" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="3.1.2" />
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" />

6
src/Squidex/WebStartup.cs

@ -32,14 +32,14 @@ namespace Squidex
{
services.AddOrleansSilo();
services.AddAppServices(configuration);
services.AddHostedService<SystemExtensions.InitializeHostedService>();
services.AddHostedService<SystemExtensions.MigratorHostedService>();
}
public void Configure(IApplicationBuilder app)
{
app.ApplicationServices.LogConfiguration();
app.ApplicationServices.RunInitialization();
app.ApplicationServices.RunMigrate();
app.ApplicationServices.RunRunnables();
app.UseMyLocalCache();
app.UseMyCors();

2
src/Squidex/app/features/administration/guards/user-must-exist.guard.ts

@ -32,7 +32,7 @@ export class UserMustExistGuard implements CanActivate {
this.router.navigate(['/404']);
}
}),
map(u => u !== null));
map(u => !!u));
return result;
}

3
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -38,8 +38,7 @@ export class EventConsumersPageComponent implements OnDestroy, OnInit {
this.eventConsumersState.load(false, true).pipe(onErrorResumeNext()).subscribe();
this.timerSubscription =
timer(2000, 2000).pipe(
switchMap(x => this.eventConsumersState.load(true, true)), onErrorResumeNext())
timer(2000, 2000).pipe(switchMap(x => this.eventConsumersState.load(true, true)), onErrorResumeNext())
.subscribe();
}

8
src/Squidex/app/features/administration/pages/restore/restore-page.component.ts

@ -8,7 +8,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Subscription, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { filter, switchMap } from 'rxjs/operators';
import {
AuthService,
@ -43,11 +43,9 @@ export class RestorePageComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.timerSubscription =
timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore()))
timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore()), filter(x => !!x))
.subscribe(dto => {
if (dto !== null) {
this.restoreJob = dto;
}
this.restoreJob = dto!;
});
}

52
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -34,18 +34,30 @@
</div>
</div>
</div>
<div class="col col-auto pl-1">
<div class="btn-group" data-toggle="buttons">
<button type="button" class="btn btn-secondary btn-toggle" [class.btn-primary]="isListView" [disabled]="isListView" (click)="changeView(true)">
<i class="icon-list"></i>
</button>
<button type="button" class="btn btn-secondary btn-toggle" [class.btn-primary]="!isListView" [disabled]="!isListView" (click)="changeView(false)">
<i class="icon-grid"></i>
</button>
</div>
</div>
</div>
</ng-container>
<ng-container content>
<sqx-assets-list [state]="assetsState"></sqx-assets-list>
<sqx-assets-list [state]="assetsState" [isListView]="isListView"></sqx-assets-list>
</ng-container>
<ng-container sidebar>
<div class="sidebar-section">
<a class="row sidebar-item" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()">
<div class="col">
All tags
<a class="sidebar-item" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()">
<div class="row">
<div class="col">
All tags
</div>
</div>
</a>
</div>
@ -53,12 +65,14 @@
<div class="sidebar-section">
<h3>Tags</h3>
<a class="row sidebar-item" *ngFor="let tag of assetsState.tags | async" (click)="toggleTag(tag.name)" [class.active]="assetsState.isTagSelected(tag.name)">
<div class="col">
{{tag.name}}
</div>
<div class="col col-auto">
{{tag.count}}
<a class="sidebar-item" *ngFor="let tag of assetsState.tags | async" (click)="toggleTag(tag.name)" [class.active]="assetsState.isTagSelected(tag.name)">
<div class="row">
<div class="col">
{{tag.name}}
</div>
<div class="col col-auto">
{{tag.count}}
</div>
</div>
</a>
</div>
@ -66,14 +80,16 @@
<div class="sidebar-section">
<h3>Saved queries</h3>
<a class="row sidebar-item" *ngFor="let query of queries.queries | async" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="queries.remove(query.name)">
<i class="icon-close"></i>
</a>
<a class="sidebar-item" *ngFor="let query of queries.queries | async" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="row">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="queries.remove(query.name)">
<i class="icon-close"></i>
</a>
</div>
</div>
</a>
</div>

11
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -14,6 +14,7 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
AssetsState,
LocalStoreService,
Queries,
UIState
} from '@app/shared';
@ -28,11 +29,15 @@ export class AssetsPageComponent implements OnInit {
public queries = new Queries(this.uiState, 'assets');
public isListView: boolean;
constructor(
public readonly appsState: AppsState,
public readonly assetsState: AssetsState,
private readonly localStore: LocalStoreService,
private readonly uiState: UIState
) {
this.isListView = this.localStore.get('assetView') === 'List';
}
public ngOnInit() {
@ -70,5 +75,11 @@ export class AssetsPageComponent implements OnInit {
public isSelectedQuery(query: string) {
return query === this.assetsState.snapshot.assetsQuery || (!query && !this.assetsState.assetsQuery);
}
public changeView(isListView: boolean) {
this.localStore.set('assetView', isListView ? 'List' : 'Grid');
this.isListView = isListView;
}
}

1
src/Squidex/app/features/content/declarations.ts

@ -13,6 +13,7 @@ export * from './pages/schemas/schemas-page.component';
export * from './shared/array-editor.component';
export * from './shared/assets-editor.component';
export * from './shared/array-item.component';
export * from './shared/content-item.component';
export * from './shared/content-status.component';
export * from './shared/contents-selector.component';

6
src/Squidex/app/features/content/module.ts

@ -15,6 +15,7 @@ import {
ContentMustExistGuard,
LoadLanguagesGuard,
SchemaMustExistPublishedGuard,
SchemaMustNotBeSingletonGuard,
SqxFrameworkModule,
SqxSharedModule,
UnsetContentGuard
@ -22,6 +23,7 @@ import {
import {
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
ContentFieldComponent,
ContentHistoryComponent,
@ -52,12 +54,13 @@ const routes: Routes = [
{
path: '',
component: ContentsPageComponent,
canActivate: [SchemaMustNotBeSingletonGuard],
canDeactivate: [CanDeactivateGuard]
},
{
path: 'new',
component: ContentPageComponent,
canActivate: [UnsetContentGuard],
canActivate: [SchemaMustNotBeSingletonGuard, UnsetContentGuard],
canDeactivate: [CanDeactivateGuard]
},
{
@ -90,6 +93,7 @@ const routes: Routes = [
],
declarations: [
ArrayEditorComponent,
ArrayItemComponent,
AssetsEditorComponent,
ContentFieldComponent,
ContentHistoryComponent,

2
src/Squidex/app/features/content/pages/content/content-field.component.html

@ -1,4 +1,4 @@
<div class="table-items-row" [class.invalid]="fieldForm.invalid">
<div class="table-items-row" [class.invalid]="isInvalid | async">
<ng-container *ngIf="field.isLocalizable && languages.length > 1">
<div class="languages-buttons" #buttonLanguages>
<sqx-language-selector size="sm"

13
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -5,8 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import {
AppLanguageDto,
@ -20,7 +22,8 @@ import {
@Component({
selector: 'sqx-content-field',
styleUrls: ['./content-field.component.scss'],
templateUrl: './content-field.component.html'
templateUrl: './content-field.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContentFieldComponent implements OnChanges {
@Input()
@ -43,6 +46,8 @@ export class ContentFieldComponent implements OnChanges {
public selectedFormControl: AbstractControl;
public isInvalid: Observable<boolean>;
public ngOnChanges(changes: SimpleChanges) {
if (this.field.isLocalizable) {
this.selectedFormControl = this.fieldForm.controls[this.language.iso2Code];
@ -55,6 +60,10 @@ export class ContentFieldComponent implements OnChanges {
this.selectedFormControl['_clearChangeFns']();
}
}
if (changes['fieldForm']) {
this.isInvalid = this.fieldForm.statusChanges.pipe(startWith(this.fieldForm.invalid), map(x => this.fieldForm.invalid));
}
}
}

5
src/Squidex/app/features/content/pages/content/content-history.component.html

@ -4,13 +4,14 @@
</ng-container>
<ng-container content>
<div *ngFor="let event of events | async" class="event row no-gutters">
<div *ngFor="let event of events | async; trackBy: trackByEvent" class="event row no-gutters">
<div class="col col-auto">
<img class="user-picture" [attr.title]="event.actor | sqxUserNameRef:null" [attr.src]="event.actor | sqxUserPictureRef" />
</div>
<div class="col pl-2">
<div class="event-message">
<span class="event-actor user-ref">{{event.actor | sqxUserNameRef:null}}</span> <span [innerHTML]="format(event.message) | async"></span>
<span class="event-actor user-ref mr-1">{{event.actor | sqxUserNameRef:null}}</span>
<span [innerHTML]="event | sqxHistoryMessage"></span>
</div>
<div class="event-created">{{event.created | sqxFromNow}}</div>

9
src/Squidex/app/features/content/pages/content/content-history.component.ts

@ -13,12 +13,10 @@ import { delay, switchMap } from 'rxjs/operators';
import {
allParams,
AppsState,
formatHistoryMessage,
HistoryChannelUpdated,
HistoryEventDto,
HistoryService,
MessageBus,
UsersProviderService,
Version
} from '@app/shared';
@ -59,8 +57,7 @@ export class ContentHistoryComponent {
private readonly appsState: AppsState,
private readonly historyService: HistoryService,
private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute,
private readonly users: UsersProviderService
private readonly route: ActivatedRoute
) {
}
@ -68,7 +65,7 @@ export class ContentHistoryComponent {
this.messageBus.emit(new ContentVersionSelected(new Version(version.toString())));
}
public format(message: string): Observable<string> {
return formatHistoryMessage(message, this.users);
public trackByEvent(index: number, event: HistoryEventDto) {
return event.eventId;
}
}

3
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -34,7 +34,8 @@
<div class="dropdown dropdown-options ml-1" *ngIf="content">
<button type="button" class="btn btn-outline-secondary" (click)="dropdown.toggle()" [disabled]="schema.isSingleton && !content.isPending"
[class.active]="dropdown.isOpen | async" #optionsButton>
<sqx-content-status
<sqx-content-status
[alignMiddle]="false"
[status]="content.status"
[scheduledTo]="content.scheduleJob?.status"
[scheduledAt]="content.scheduleJob?.dueTime"

12
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -8,7 +8,7 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, of, Subscription } from 'rxjs';
import { filter, map, onErrorResumeNext, switchMap } from 'rxjs/operators';
import { filter, onErrorResumeNext, switchMap } from 'rxjs/operators';
import { ContentVersionSelected } from './../messages';
@ -88,19 +88,19 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
});
this.selectedSchemaSubscription =
this.schemasState.selectedSchema.pipe(filter(s => !!s), map(s => s!))
this.schemasState.selectedSchema.pipe(filter(s => !!s))
.subscribe(schema => {
this.schema = schema;
this.schema = schema!;
this.contentForm = new EditContentForm(this.schema, this.languages);
});
this.contentSubscription =
this.contentsState.selectedContent.pipe(filter(c => !!c), map(c => c!))
this.contentsState.selectedContent.pipe(filter(c => !!c))
.subscribe(content => {
this.content = content;
this.content = content!;
this.loadContent(content.dataDraft);
this.loadContent(this.content.dataDraft);
});
this.contentVersionSelectedSubscription =

8
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -73,19 +73,19 @@
<div class="selection" *ngIf="selectionCount > 0">
{{selectionCount}} items selected:&nbsp;&nbsp;
<button class="btn btn-secondary" (click)="publishSelected()" *ngIf="canPublish">
<button class="btn btn-secondary mr-1" (click)="publishSelected()" *ngIf="canPublish">
Publish
</button>
<button class="btn btn-secondary" (click)="unpublishSelected()" *ngIf="canUnpublish">
<button class="btn btn-secondary mr-1" (click)="unpublishSelected()" *ngIf="canUnpublish">
Unpublish
</button>
<button class="btn btn-secondary" (click)="archiveSelected()" *ngIf="(contentsState.isArchive | async) === false">
<button class="btn btn-secondary mr-1" (click)="archiveSelected()" *ngIf="(contentsState.isArchive | async) === false">
Archive
</button>
<button class="btn btn-secondary" (click)="restoreSelected()" *ngIf="contentsState.isArchive | async">
<button class="btn btn-secondary mr-1" (click)="restoreSelected()" *ngIf="contentsState.isArchive | async">
Restore
</button>

49
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -6,8 +6,9 @@
*/
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
import { filter, onErrorResumeNext, switchMap, takeUntil, tap } from 'rxjs/operators';
import {
AppLanguageDto,
@ -17,6 +18,7 @@ import {
ImmutableArray,
LanguagesState,
ModalModel,
navigatedToOtherComponent,
Queries,
SchemaDetailsDto,
SchemasState,
@ -59,6 +61,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public readonly contentsState: ContentsState,
private readonly languagesState: LanguagesState,
private readonly schemasState: SchemasState,
private readonly router: Router,
private readonly uiState: UIState
) {
}
@ -70,8 +73,10 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
const routeChanged = this.router.events.pipe(filter(navigatedToOtherComponent(this.router)));
this.selectedSchemaSubscription =
this.schemasState.selectedSchema
this.schemasState.selectedSchema.pipe(takeUntil(routeChanged))
.subscribe(schema => {
this.resetSelection();
@ -82,7 +87,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
});
this.contentsSubscription =
this.contentsState.contents
this.contentsState.contents.pipe(takeUntil(routeChanged))
.subscribe(() => {
this.updateSelectionSummary();
});
@ -100,7 +105,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public deleteSelected() {
this.contentsState.deleteMany(this.select()).pipe(onErrorResumeNext()).subscribe();
this.contentsState.deleteMany(this.selectItems()).pipe(onErrorResumeNext()).subscribe();
}
public delete(content: ContentDto) {
@ -112,7 +117,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public publishSelected() {
this.changeContentItems(this.select(c => c.status !== 'Published'), 'Publish');
this.changeContentItems(this.selectItems(c => c.status !== 'Published'), 'Publish');
}
public unpublish(content: ContentDto) {
@ -120,7 +125,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public unpublishSelected() {
this.changeContentItems(this.select(c => c.status === 'Published'), 'Unpublish');
this.changeContentItems(this.selectItems(c => c.status === 'Published'), 'Unpublish');
}
public archive(content: ContentDto) {
@ -128,15 +133,15 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public archiveSelected() {
this.changeContentItems(this.select(), 'Archive');
this.changeContentItems(this.selectItems(), 'Archive');
}
public restore(content: ContentDto) {
this.changeContentItems([content], 'Restore');
}
public restoreSelected(scheduled: boolean) {
this.changeContentItems(this.select(), 'Restore');
public restoreSelected() {
this.changeContentItems(this.selectItems(), 'Restore');
}
public clone(content: ContentDto) {
@ -144,7 +149,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public isSelectedQuery(query: string) {
return query === this.contentsState.snapshot.contentsQuery || (!query && !this.contentsState.contentsQuery);
return query === this.contentsState.snapshot.contentsQuery || (!query && !this.contentsState.snapshot.contentsQuery);
}
private changeContentItems(contents: ContentDto[], action: string) {
@ -185,12 +190,16 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentsState.search(query).pipe(onErrorResumeNext()).subscribe();
}
public selectLanguage(language: AppLanguageDto) {
this.language = language;
}
public isItemSelected(content: ContentDto): boolean {
return !!this.selectedItems[content.id];
}
public selectLanguage(language: AppLanguageDto) {
this.language = language;
private selectItems(predicate?: (content: ContentDto) => boolean) {
return this.contentsState.snapshot.contents.values.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
}
public selectItem(content: ContentDto, isSelected: boolean) {
@ -199,6 +208,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.updateSelectionSummary();
}
private resetSelection() {
this.selectedItems = {};
this.updateSelectionSummary();
}
public selectAll(isSelected: boolean) {
this.selectedItems = {};
@ -215,16 +230,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
return content.id;
}
private select(predicate?: (content: ContentDto) => boolean) {
return this.contentsState.snapshot.contents.values.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
}
private resetSelection() {
this.selectedItems = {};
this.updateSelectionSummary();
}
private updateSelectionSummary() {
this.isAllSelected = this.contentsState.snapshot.contents.length > 0;

1
src/Squidex/app/features/content/pages/schemas/schemas-page.component.html

@ -21,6 +21,7 @@
[name]="category"
[schemas]="schemas"
[schemasFilter]="schemasFilter.valueChanges | async"
[routeSingletonToContent]="true"
[isReadonly]="true">
</sqx-schema-category>
</ng-container>

25
src/Squidex/app/features/content/shared/array-editor.component.html

@ -1,20 +1,13 @@
<div class="array-container" *ngIf="arrayControl.controls.length > 0">
<div class="array-item" *ngFor="let nestedForm of arrayControl.controls; let i = index" [class.invalid]="nestedForm.invalid">
<button type="button" class="btn btn-link btn-danger array-item-remove" (click)="removeItem(i); $event.preventDefault()">
<i class="icon-bin2"></i>
</button>
<div class="form-group" *ngFor="let nestedField of field.nested">
<ng-container *ngIf="nestedForm.get(nestedField.name); let nestedFieldForm">
<sqx-field-editor
[form]="form"
[field]="nestedField"
[language]="language"
[languages]="languages"
[control]="nestedFieldForm">
</sqx-field-editor>
</ng-container>
</div>
<div class="item" *ngFor="let itemForm of arrayControl.controls; let i = index">
<sqx-array-item
[form]="form"
[field]="field"
[itemForm]="itemForm"
[language]="language"
[languages]="languages"
(removing)="removeItem(i)">
</sqx-array-item>
</div>
</div>

37
src/Squidex/app/features/content/shared/array-editor.component.scss

@ -1,34 +1,19 @@
@import '_vars';
@import '_mixins';
.array {
&-container {
background: $color-border;
padding: 1rem;
position: relative;
margin-bottom: 1rem;
}
&-item {
& {
background: $panel-light-background;
border: 1px solid darken($color-border, 5%);
border-left-width: 4px;
padding: 1rem;
position: relative;
margin-bottom: 1rem;
}
.array-container {
background: $color-border;
padding: 1rem;
position: relative;
margin-bottom: 1rem;
}
&:last-child {
margin-bottom: 0;
}
.item {
& {
margin-bottom: 1rem;
}
&-item-remove {
@include absolute(.5rem, .5rem, auto, auto);
&:last-child {
margin-bottom: 0;
}
}
.invalid {
border-left-color: $color-theme-error;
}

5
src/Squidex/app/features/content/shared/array-editor.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { FormArray } from '@angular/forms';
import {
@ -18,7 +18,8 @@ import {
@Component({
selector: 'sqx-array-editor',
styleUrls: ['./array-editor.component.scss'],
templateUrl: './array-editor.component.html'
templateUrl: './array-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ArrayEditorComponent {
@Input()

15
src/Squidex/app/features/content/shared/array-item.component.html

@ -0,0 +1,15 @@
<div class="item" [class.invalid]="isInvalid | async">
<button type="button" class="btn btn-link btn-danger item-remove" (click)="removing.emit(); $event.preventDefault()">
<i class="icon-bin2"></i>
</button>
<div class="form-group" *ngFor="let fieldControl of fieldControls">
<sqx-field-editor
[form]="form"
[field]="fieldControl.field"
[language]="language"
[languages]="languages"
[control]="fieldControl.control">
</sqx-field-editor>
</div>
</div>

20
src/Squidex/app/features/content/shared/array-item.component.scss

@ -0,0 +1,20 @@
@import '_vars';
@import '_mixins';
.item {
& {
background: $panel-light-background;
border: 1px solid darken($color-border, 5%);
border-left-width: 4px;
padding: 1rem;
position: relative;
}
&-remove {
@include absolute(.5rem, .5rem, auto, auto);
}
}
.invalid {
border-left-color: $color-theme-error;
}

59
src/Squidex/app/features/content/shared/array-item.component.ts

@ -0,0 +1,59 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import {
AppLanguageDto,
EditContentForm,
FieldDto,
ImmutableArray,
RootFieldDto
} from '@app/shared';
@Component({
selector: 'sqx-array-item',
styleUrls: ['./array-item.component.scss'],
templateUrl: './array-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ArrayItemComponent implements OnChanges {
@Output()
public removing = new EventEmitter();
@Input()
public form: EditContentForm;
@Input()
public field: RootFieldDto;
@Input()
public itemForm: FormGroup;
@Input()
public language: AppLanguageDto;
@Input()
public languages: ImmutableArray<AppLanguageDto>;
public isInvalid: Observable<boolean>;
public fieldControls: { field: FieldDto, control: AbstractControl }[];
public ngOnChanges(changes: SimpleChanges) {
if (changes['itemForm']) {
this.isInvalid = this.itemForm.statusChanges.pipe(startWith(this.itemForm.invalid), map(x => this.itemForm.invalid));
}
if (changes['itemForm'] || changes['field']) {
this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !!x.control);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save