Browse Source

Merge branch 'master' into feature/plugins

# Conflicts:
#	src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
#	src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
#	src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs
#	src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
#	src/Squidex/WebStartup.cs
#	tools/Migrate_01/MigrationPath.cs
#	tools/Migrate_01/Rebuilder.cs
pull/349/head
Sebastian Stehle 7 years ago
parent
commit
3f014a6398
  1. 2
      README.md
  2. 2
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs
  3. 60
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  4. 32
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  5. 37
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
  6. 92
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  7. 3
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  9. 2
      src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
  10. 65
      src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
  11. 83
      src/Squidex.Infrastructure/Languages.cs
  12. 2
      src/Squidex.Web/Constants.cs
  13. 13
      src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  14. 41
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  15. 11
      src/Squidex/Areas/IdentityServer/Controllers/Extensions.cs
  16. 2
      src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml
  17. 1
      src/Squidex/Config/Authentication/OidcServices.cs
  18. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  19. 31
      src/Squidex/Config/Startup/RebuilderHost.cs
  20. 7
      src/Squidex/Pipeline/Squid/SquidMiddleware.cs
  21. 4
      src/Squidex/WebStartup.cs
  22. 23
      src/Squidex/appsettings.json
  23. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs
  24. 88
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  25. 48
      tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs
  26. BIN
      tests/Squidex.Infrastructure.Tests/Assets/Images/logo.jpg
  27. BIN
      tests/Squidex.Infrastructure.Tests/Assets/Images/logo.png
  28. 8
      tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  29. 3
      tools/GenerateLanguages/Program.cs
  30. 10
      tools/Migrate_01/Migrations/BuildFullTextIndices.cs
  31. 24
      tools/Migrate_01/RebuildOptions.cs
  32. 66
      tools/Migrate_01/RebuildRunner.cs
  33. 25
      tools/Migrate_01/Rebuilder.cs

2
README.md

@ -45,7 +45,7 @@ Please create issues to report bugs, suggest new functionalities, ask questions
## Cloud Version ## Cloud Version
Although Squidex is free we are also working on a Saas version on [https://cloud.squidex.io](https://cloud.squidex.io) (More information coming soon). We have also have plans to sell a premium version with first class support and some exlusive features. But don't be afraid, our first priority is to deliver a state of the art, stable, fast and free content management hub to make the life for developers a little bit easier. Although Squidex is free it is also available as a Saas version on [https://cloud.squidex.io](https://cloud.squidex.io).
## License ## License

2
src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
public IJsonValue Visit(IField<JsonFieldProperties> field) public IJsonValue Visit(IField<JsonFieldProperties> field)
{ {
return JsonValue.Object(); return JsonValue.Null;
} }
public IJsonValue Visit(IField<NumberFieldProperties> field) public IJsonValue Visit(IField<NumberFieldProperties> field)

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

@ -30,12 +30,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity> internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity>
{ {
protected IJsonSerializer Serializer { get; } private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer) public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, IAppProvider appProvider)
: base(database) : base(database)
{ {
Serializer = serializer; this.appProvider = appProvider;
this.serializer = serializer;
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default) protected override Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
@ -43,9 +46,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return collection.Indexes.CreateManyAsync(new[] return collection.Indexes.CreateManyAsync(new[]
{ {
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id).Ascending(x => x.Status)), .Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Ascending(x => x.Id)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Ascending(x => x.Id)),
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt).Ascending(x => x.IsDeleted)), .Ascending(x => x.ScheduledAt)
.Ascending(x => x.IsDeleted)),
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ReferencedIds)) .Ascending(x => x.ReferencedIds))
}, ct); }, ct);
@ -77,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef, Serializer); entity.ParseData(schema.SchemaDef, serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
@ -95,9 +107,35 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<Guid> ids, Status[] status, bool useDraft)
{
var find = Collection.Find(FilterFactory.IdsByApp(app.Id, ids, status));
var contentItems = await find.WithoutDraft(useDraft).ToListAsync();
var schemaIds = contentItems.Select(x => x.IndexedSchemaId).ToList();
var schemas = await Task.WhenAll(schemaIds.Select(x => appProvider.GetSchemaAsync(app.Id, x)));
var result = new List<(IContentEntity Content, ISchemaEntity Schema)>();
foreach (var entity in contentItems)
{
var schema = schemas.FirstOrDefault(x => x.Id == entity.IndexedSchemaId);
if (schema != null)
{
entity.ParseData(schema.SchemaDef, serializer);
result.Add((entity, schema));
}
}
return result;
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids, Status[] status, bool useDraft) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids, Status[] status, bool useDraft)
{ {
var find = Collection.Find(FilterFactory.Build(schema.Id, ids, status)); var find = Collection.Find(FilterFactory.IdsBySchema(schema.Id, ids, status));
var contentItems = find.WithoutDraft(useDraft).ToListAsync(); var contentItems = find.WithoutDraft(useDraft).ToListAsync();
var contentCount = find.CountDocumentsAsync(); var contentCount = find.CountDocumentsAsync();
@ -106,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef, Serializer); entity.ParseData(schema.SchemaDef, serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
@ -118,7 +156,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
var contentEntity = await find.WithoutDraft(useDraft).FirstOrDefaultAsync(); var contentEntity = await find.WithoutDraft(useDraft).FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef, Serializer); contentEntity?.ParseData(schema.SchemaDef, serializer);
return contentEntity; return contentEntity;
} }
@ -164,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, Serializer); contentEntity.ParseData(schema.SchemaDef, serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
} }
@ -178,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, Serializer); contentEntity.ParseData(schema.SchemaDef, serializer);
await callback(SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); await callback(SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}, ct); }, ct);

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

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
this.indexer = indexer; this.indexer = indexer;
this.serializer = serializer; this.serializer = serializer;
contents = new MongoContentCollection(database, serializer); contents = new MongoContentCollection(database, serializer, appProvider);
} }
public Task InitializeAsync(CancellationToken ct = default) public Task InitializeAsync(CancellationToken ct = default)
@ -53,6 +53,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query)
{ {
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(status, nameof(status));
Guard.NotNull(query, nameof(query));
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByQuery")) using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByQuery"))
{ {
var useDraft = UseDraft(status); var useDraft = UseDraft(status);
@ -70,6 +75,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids)
{ {
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIds")) using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIds"))
{ {
var useDraft = UseDraft(status); var useDraft = UseDraft(status);
@ -78,8 +88,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, Status[] status, HashSet<Guid> ids)
{
Guard.NotNull(app, nameof(app));
Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIdsWithoutSchema"))
{
var useDraft = UseDraft(status);
return await contents.QueryAsync(app, ids, status, useDraft);
}
}
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id) public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id)
{ {
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(status, nameof(status));
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
var useDraft = UseDraft(status); var useDraft = UseDraft(status);
@ -124,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private static bool UseDraft(Status[] status) private static bool UseDraft(Status[] status)
{ {
return !(status?.Length == 1 && status[0] == Status.Published); return status.Length != 1 || status[0] != Status.Published;
} }
} }
} }

37
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs

@ -117,26 +117,40 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
public static FilterDefinition<MongoContentEntity> Build(Guid schemaId, Guid id, Status[] status) public static FilterDefinition<MongoContentEntity> Build(Guid schemaId, Guid id, Status[] status)
{ {
return CreateFilter(schemaId, new List<Guid> { id }, status, null); return CreateFilter(null, schemaId, new List<Guid> { id }, status, null);
} }
public static FilterDefinition<MongoContentEntity> Build(Guid schemaId, ICollection<Guid> ids, Status[] status) public static FilterDefinition<MongoContentEntity> IdsByApp(Guid appId, ICollection<Guid> ids, Status[] status)
{ {
return CreateFilter(schemaId, ids, status, null); return CreateFilter(appId, null, ids, status, null);
}
public static FilterDefinition<MongoContentEntity> IdsBySchema(Guid schemaId, ICollection<Guid> ids, Status[] status)
{
return CreateFilter(null, schemaId, ids, status, null);
} }
public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, ICollection<Guid> ids, Status[] status) public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, ICollection<Guid> ids, Status[] status)
{ {
return CreateFilter(schemaId, ids, status, query); return CreateFilter(null, schemaId, ids, status, query);
} }
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid schemaId, ICollection<Guid> ids, Status[] status, Query query) private static FilterDefinition<MongoContentEntity> CreateFilter(Guid? appId, Guid? schemaId, ICollection<Guid> ids, Status[] status, Query query)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>();
if (appId.HasValue)
{ {
Filter.Eq(x => x.IndexedSchemaId, schemaId), filters.Add(Filter.Eq(x => x.IndexedAppId, appId.Value));
Filter.Ne(x => x.IsDeleted, true) }
};
if (schemaId.HasValue)
{
filters.Add(Filter.Eq(x => x.IndexedSchemaId, schemaId.Value));
}
filters.Add(Filter.Ne(x => x.IsDeleted, true));
filters.Add(Filter.In(x => x.Status, status));
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
{ {
@ -150,11 +164,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
} }
} }
if (status != null)
{
filters.Add(Filter.In(x => x.Status, status));
}
if (query.Filter != null) if (query.Filter != null)
{ {
filters.Add(query.Filter.BuildFilter<MongoContentEntity>()); filters.Add(query.Filter.BuildFilter<MongoContentEntity>());

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

@ -23,6 +23,7 @@ using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Queries.OData; using Squidex.Infrastructure.Queries.OData;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
@ -81,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schema = await GetSchemaAsync(context, schemaIdOrName); var schema = await GetSchemaAsync(context, schemaIdOrName);
CheckPermission(schema, context.User); CheckPermission(context.User, schema);
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
@ -99,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity));
} }
return Transform(context, schema, true, content); return Transform(context, schema, content);
} }
} }
@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schema = await GetSchemaAsync(context, schemaIdOrName); var schema = await GetSchemaAsync(context, schemaIdOrName);
CheckPermission(schema, context.User); CheckPermission(context.User, schema);
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
@ -120,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (query.Ids?.Count > 0) if (query.Ids?.Count > 0)
{ {
contents = await contentRepository.QueryAsync(context.App, schema, status, new HashSet<Guid>(query.Ids)); contents = await contentRepository.QueryAsync(context.App, schema, status, new HashSet<Guid>(query.Ids));
contents = Sort(contents, query.Ids); contents = SortSet(contents, query.Ids);
} }
else else
{ {
@ -129,34 +130,67 @@ namespace Squidex.Domain.Apps.Entities.Contents
contents = await contentRepository.QueryAsync(context.App, schema, status, parsedQuery); contents = await contentRepository.QueryAsync(context.App, schema, status, parsedQuery);
} }
return Transform(context, schema, true, contents); return Transform(context, schema, contents);
} }
} }
private IContentEntity Transform(QueryContext context, ISchemaEntity schema, bool checkType, IContentEntity content) public async Task<IList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids)
{ {
return TransformCore(context, schema, checkType, Enumerable.Repeat(content, 1)).FirstOrDefault(); Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<ContentQueryService>())
{
var status = GetQueryStatus(context);
List<IContentEntity> result;
if (ids?.Count > 0)
{
var contents = await contentRepository.QueryAsync(context.App, status, new HashSet<Guid>(ids));
var permissions = context.User.Permissions();
contents = contents.Where(x => HasPermission(permissions, x.Schema)).ToList();
result = contents.Select(x => Transform(context, x.Schema, x.Content)).ToList();
result = SortList(result, ids).ToList();
}
else
{
result = new List<IContentEntity>();
}
return result;
}
} }
private IResultList<IContentEntity> Transform(QueryContext context, ISchemaEntity schema, bool checkType, IResultList<IContentEntity> contents) private IResultList<IContentEntity> Transform(QueryContext context, ISchemaEntity schema, IResultList<IContentEntity> contents)
{ {
var transformed = TransformCore(context, schema, checkType, contents); var transformed = TransformCore(context, schema, contents);
return ResultList.Create(contents.Total, transformed); return ResultList.Create(contents.Total, transformed);
} }
private static IResultList<IContentEntity> Sort(IResultList<IContentEntity> contents, IReadOnlyList<Guid> ids) private IContentEntity Transform(QueryContext context, ISchemaEntity schema, IContentEntity content)
{ {
var sorted = ids.Select(id => contents.FirstOrDefault(x => x.Id == id)).Where(x => x != null); return TransformCore(context, schema, Enumerable.Repeat(content, 1)).FirstOrDefault();
}
private static IResultList<IContentEntity> SortSet(IResultList<IContentEntity> contents, IReadOnlyList<Guid> ids)
{
return ResultList.Create(contents.Total, SortList(contents, ids));
}
return ResultList.Create(contents.Total, sorted); private static IEnumerable<IContentEntity> SortList(IEnumerable<IContentEntity> contents, IReadOnlyList<Guid> ids)
{
return ids.Select(id => contents.FirstOrDefault(x => x.Id == id)).Where(x => x != null);
} }
private IEnumerable<IContentEntity> TransformCore(QueryContext context, ISchemaEntity schema, bool checkType, IEnumerable<IContentEntity> contents) private IEnumerable<IContentEntity> TransformCore(QueryContext context, ISchemaEntity schema, IEnumerable<IContentEntity> contents)
{ {
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
var converters = GenerateConverters(context, checkType).ToArray(); var converters = GenerateConverters(context).ToArray();
var scriptText = schema.SchemaDef.Scripts.Query; var scriptText = schema.SchemaDef.Scripts.Query;
@ -170,7 +204,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (!context.IsFrontendClient && isScripting) if (!context.IsFrontendClient && isScripting)
{ {
result.Data = scriptEngine.Transform(new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id }, scriptText); var ctx = new ScriptContext { User = context.User, Data = content.Data, ContentId = content.Id };
result.Data = scriptEngine.Transform(ctx, scriptText);
} }
result.Data = result.Data.ConvertName2Name(schema.SchemaDef, converters); result.Data = result.Data.ConvertName2Name(schema.SchemaDef, converters);
@ -186,7 +222,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
} }
private IEnumerable<FieldConverter> GenerateConverters(QueryContext context, bool checkType) private IEnumerable<FieldConverter> GenerateConverters(QueryContext context)
{ {
if (!context.IsFrontendClient) if (!context.IsFrontendClient)
{ {
@ -194,11 +230,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden()); yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden());
} }
if (checkType) yield return FieldConverters.ExcludeChangedTypes();
{ yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
yield return FieldConverters.ExcludeChangedTypes();
yield return FieldConverters.ForNestedName2Name(ValueConverters.ExcludeChangedTypes());
}
yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig); yield return FieldConverters.ResolveInvariant(context.App.LanguagesConfig);
yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig); yield return FieldConverters.ResolveLanguages(context.App.LanguagesConfig);
@ -274,17 +307,26 @@ namespace Squidex.Domain.Apps.Entities.Contents
return schema; return schema;
} }
private static void CheckPermission(ISchemaEntity schema, ClaimsPrincipal user) private static void CheckPermission(ClaimsPrincipal user, params ISchemaEntity[] schemas)
{ {
var permissions = user.Permissions(); var permissions = user.Permissions();
var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.SchemaDef.Name);
if (!permissions.Allows(permission)) foreach (var schema in schemas)
{ {
throw new DomainForbiddenException("You do not have permission for this schema."); if (!HasPermission(permissions, schema))
{
throw new DomainForbiddenException("You do not have permission for this schema.");
}
} }
} }
private static bool HasPermission(PermissionSet permissions, ISchemaEntity schema)
{
var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.SchemaDef.Name);
return permissions.Allows(permission);
}
private static Status[] GetFindStatus(QueryContext context) private static Status[] GetFindStatus(QueryContext context)
{ {
if (context.IsFrontendClient) if (context.IsFrontendClient)

3
src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -13,6 +14,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentQueryService public interface IContentQueryService
{ {
Task<IList<IContentEntity>> QueryAsync(QueryContext context, IReadOnlyList<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query); Task<IResultList<IContentEntity>> QueryAsync(QueryContext context, string schemaIdOrName, Q query);
Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any); Task<IContentEntity> FindContentAsync(QueryContext context, string schemaIdOrName, Guid id, long version = EtagVersion.Any);

2
src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
{ {
public interface IContentRepository public interface IContentRepository
{ {
Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, Status[] status, HashSet<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids);
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query);

2
src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs

@ -14,6 +14,6 @@ namespace Squidex.Infrastructure.Assets
{ {
Task<ImageInfo> GetImageInfoAsync(Stream source); Task<ImageInfo> GetImageInfoAsync(Stream source);
Task CreateThumbnailAsync(Stream source, Stream destination, int? width, int? height, string mode); Task CreateThumbnailAsync(Stream source, Stream destination, int? width = null, int? height = null, string mode = null, int? quality = null);
} }
} }

65
src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs

@ -9,6 +9,7 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Transforms; using SixLabors.ImageSharp.Processing.Transforms;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -17,49 +18,59 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
{ {
public sealed class ImageSharpAssetThumbnailGenerator : IAssetThumbnailGenerator public sealed class ImageSharpAssetThumbnailGenerator : IAssetThumbnailGenerator
{ {
public ImageSharpAssetThumbnailGenerator() public Task CreateThumbnailAsync(Stream source, Stream destination, int? width = null, int? height = null, string mode = null, int? quality = null)
{
Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Jpeg);
Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Png);
}
public Task CreateThumbnailAsync(Stream source, Stream destination, int? width, int? height, string mode)
{ {
return Task.Run(() => return Task.Run(() =>
{ {
if (width == null && height == null) if (!width.HasValue && !height.HasValue && !quality.HasValue)
{ {
source.CopyTo(destination); source.CopyTo(destination);
return; return;
} }
var isCropUpsize = string.Equals("CropUpsize", mode, StringComparison.OrdinalIgnoreCase); using (var sourceImage = Image.Load(source, out var format))
if (!Enum.TryParse<ResizeMode>(mode, true, out var resizeMode))
{
resizeMode = ResizeMode.Max;
}
if (isCropUpsize)
{ {
resizeMode = ResizeMode.Crop; var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format);
}
var w = width ?? 0; if (quality.HasValue)
var h = height ?? 0; {
encoder = new JpegEncoder { Quality = quality.Value };
}
using (var sourceImage = Image.Load(source, out var format)) if (encoder == null)
{
if (w >= sourceImage.Width && h >= sourceImage.Height && resizeMode == ResizeMode.Crop && !isCropUpsize)
{ {
resizeMode = ResizeMode.BoxPad; throw new NotSupportedException();
} }
var options = new ResizeOptions { Size = new Size(w, h), Mode = resizeMode }; if (width.HasValue || height.HasValue)
{
var isCropUpsize = string.Equals("CropUpsize", mode, StringComparison.OrdinalIgnoreCase);
if (!Enum.TryParse<ResizeMode>(mode, true, out var resizeMode))
{
resizeMode = ResizeMode.Max;
}
if (isCropUpsize)
{
resizeMode = ResizeMode.Crop;
}
var resizeWidth = width ?? 0;
var resizeHeight = height ?? 0;
if (resizeWidth >= sourceImage.Width && resizeHeight >= sourceImage.Height && resizeMode == ResizeMode.Crop && !isCropUpsize)
{
resizeMode = ResizeMode.BoxPad;
}
var options = new ResizeOptions { Size = new Size(resizeWidth, resizeHeight), Mode = resizeMode };
sourceImage.Mutate(x => x.Resize(options));
}
sourceImage.Mutate(x => x.Resize(options)); sourceImage.Save(destination, encoder);
sourceImage.Save(destination, format);
} }
}); });
} }

83
src/Squidex.Infrastructure/Languages.cs

@ -197,6 +197,8 @@ namespace Squidex.Infrastructure
public static readonly Language AfarEthiopia = AddLanguage("aa-ET", "Afar (Ethiopia)"); public static readonly Language AfarEthiopia = AddLanguage("aa-ET", "Afar (Ethiopia)");
public static readonly Language AfrikaansNamibia = AddLanguage("af-NA", "Afrikaans (Namibia)"); 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 AfrikaansSouthAfrica = AddLanguage("af-ZA", "Afrikaans (South Africa)");
public static readonly Language AkanGhana = AddLanguage("ak-GH", "Akan (Ghana)");
public static readonly Language AmharicEthiopia = AddLanguage("am-ET", "Amharic (Ethiopia)");
public static readonly Language ArabicUnitedArabEmirates = AddLanguage("ar-AE", "Arabic (United Arab Emirates)"); 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 ArabicBahrain = AddLanguage("ar-BH", "Arabic (Bahrain)");
public static readonly Language ArabicDjibouti = AddLanguage("ar-DJ", "Arabic (Djibouti)"); public static readonly Language ArabicDjibouti = AddLanguage("ar-DJ", "Arabic (Djibouti)");
@ -223,14 +225,23 @@ namespace Squidex.Infrastructure
public static readonly Language ArabicChad = AddLanguage("ar-TD", "Arabic (Chad)"); public static readonly Language ArabicChad = AddLanguage("ar-TD", "Arabic (Chad)");
public static readonly Language ArabicTunisia = AddLanguage("ar-TN", "Arabic (Tunisia)"); public static readonly Language ArabicTunisia = AddLanguage("ar-TN", "Arabic (Tunisia)");
public static readonly Language ArabicYemen = AddLanguage("ar-YE", "Arabic (Yemen)"); public static readonly Language ArabicYemen = AddLanguage("ar-YE", "Arabic (Yemen)");
public static readonly Language AssameseIndia = AddLanguage("as-IN", "Assamese (India)");
public static readonly Language BashkirRussia = AddLanguage("ba-RU", "Bashkir (Russia)");
public static readonly Language BelarusianBelarus = AddLanguage("be-BY", "Belarusian (Belarus)");
public static readonly Language BulgarianBulgaria = AddLanguage("bg-BG", "Bulgarian (Bulgaria)");
public static readonly Language BanglaBangladesh = AddLanguage("bn-BD", "Bangla (Bangladesh)"); public static readonly Language BanglaBangladesh = AddLanguage("bn-BD", "Bangla (Bangladesh)");
public static readonly Language BanglaIndia = AddLanguage("bn-IN", "Bangla (India)"); public static readonly Language BanglaIndia = AddLanguage("bn-IN", "Bangla (India)");
public static readonly Language TibetanChina = AddLanguage("bo-CN", "Tibetan (China)"); public static readonly Language TibetanChina = AddLanguage("bo-CN", "Tibetan (China)");
public static readonly Language TibetanIndia = AddLanguage("bo-IN", "Tibetan (India)"); public static readonly Language TibetanIndia = AddLanguage("bo-IN", "Tibetan (India)");
public static readonly Language BretonFrance = AddLanguage("br-FR", "Breton (France)");
public static readonly Language CatalanAndorra = AddLanguage("ca-AD", "Catalan (Andorra)"); public static readonly Language CatalanAndorra = AddLanguage("ca-AD", "Catalan (Andorra)");
public static readonly Language CatalanCatalan = AddLanguage("ca-ES", "Catalan (Catalan)"); public static readonly Language CatalanCatalan = AddLanguage("ca-ES", "Catalan (Catalan)");
public static readonly Language CatalanFrance = AddLanguage("ca-FR", "Catalan (France)"); public static readonly Language CatalanFrance = AddLanguage("ca-FR", "Catalan (France)");
public static readonly Language CatalanItaly = AddLanguage("ca-IT", "Catalan (Italy)"); public static readonly Language CatalanItaly = AddLanguage("ca-IT", "Catalan (Italy)");
public static readonly Language ChechenRussia = AddLanguage("ce-RU", "Chechen (Russia)");
public static readonly Language CorsicanFrance = AddLanguage("co-FR", "Corsican (France)");
public static readonly Language CzechCzechia = AddLanguage("cs-CZ", "Czech (Czechia)");
public static readonly Language WelshUnitedKingdom = AddLanguage("cy-GB", "Welsh (United Kingdom)");
public static readonly Language DanishDenmark = AddLanguage("da-DK", "Danish (Denmark)"); public static readonly Language DanishDenmark = AddLanguage("da-DK", "Danish (Denmark)");
public static readonly Language DanishGreenland = AddLanguage("da-GL", "Danish (Greenland)"); public static readonly Language DanishGreenland = AddLanguage("da-GL", "Danish (Greenland)");
public static readonly Language GermanAustria = AddLanguage("de-AT", "German (Austria)"); public static readonly Language GermanAustria = AddLanguage("de-AT", "German (Austria)");
@ -240,6 +251,8 @@ namespace Squidex.Infrastructure
public static readonly Language GermanItaly = AddLanguage("de-IT", "German (Italy)"); public static readonly Language GermanItaly = AddLanguage("de-IT", "German (Italy)");
public static readonly Language GermanLiechtenstein = AddLanguage("de-LI", "German (Liechtenstein)"); public static readonly Language GermanLiechtenstein = AddLanguage("de-LI", "German (Liechtenstein)");
public static readonly Language GermanLuxembourg = AddLanguage("de-LU", "German (Luxembourg)"); public static readonly Language GermanLuxembourg = AddLanguage("de-LU", "German (Luxembourg)");
public static readonly Language DivehiMaldives = AddLanguage("dv-MV", "Divehi (Maldives)");
public static readonly Language DzongkhaBhutan = AddLanguage("dz-BT", "Dzongkha (Bhutan)");
public static readonly Language EweGhana = AddLanguage("ee-GH", "Ewe (Ghana)"); public static readonly Language EweGhana = AddLanguage("ee-GH", "Ewe (Ghana)");
public static readonly Language EweTogo = AddLanguage("ee-TG", "Ewe (Togo)"); public static readonly Language EweTogo = AddLanguage("ee-TG", "Ewe (Togo)");
public static readonly Language GreekCyprus = AddLanguage("el-CY", "Greek (Cyprus)"); public static readonly Language GreekCyprus = AddLanguage("el-CY", "Greek (Cyprus)");
@ -371,10 +384,14 @@ namespace Squidex.Infrastructure
public static readonly Language SpanishUnitedStates = AddLanguage("es-US", "Spanish (United States)"); 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 SpanishUruguay = AddLanguage("es-UY", "Spanish (Uruguay)");
public static readonly Language SpanishVenezuela = AddLanguage("es-VE", "Spanish (Venezuela)"); public static readonly Language SpanishVenezuela = AddLanguage("es-VE", "Spanish (Venezuela)");
public static readonly Language EstonianEstonia = AddLanguage("et-EE", "Estonian (Estonia)");
public static readonly Language BasqueBasque = AddLanguage("eu-ES", "Basque (Basque)");
public static readonly Language PersianIran = AddLanguage("fa-IR", "Persian (Iran)");
public static readonly Language FulahCameroon = AddLanguage("ff-CM", "Fulah (Cameroon)"); public static readonly Language FulahCameroon = AddLanguage("ff-CM", "Fulah (Cameroon)");
public static readonly Language FulahGuinea = AddLanguage("ff-GN", "Fulah (Guinea)"); public static readonly Language FulahGuinea = AddLanguage("ff-GN", "Fulah (Guinea)");
public static readonly Language FulahMauritania = AddLanguage("ff-MR", "Fulah (Mauritania)"); public static readonly Language FulahMauritania = AddLanguage("ff-MR", "Fulah (Mauritania)");
public static readonly Language FulahNigeria = AddLanguage("ff-NG", "Fulah (Nigeria)"); public static readonly Language FulahNigeria = AddLanguage("ff-NG", "Fulah (Nigeria)");
public static readonly Language FinnishFinland = AddLanguage("fi-FI", "Finnish (Finland)");
public static readonly Language FaroeseDenmark = AddLanguage("fo-DK", "Faroese (Denmark)"); 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 FaroeseFaroeIslands = AddLanguage("fo-FO", "Faroese (Faroe Islands)");
public static readonly Language FrenchBelgium = AddLanguage("fr-BE", "French (Belgium)"); public static readonly Language FrenchBelgium = AddLanguage("fr-BE", "French (Belgium)");
@ -423,23 +440,63 @@ namespace Squidex.Infrastructure
public static readonly Language FrenchVanuatu = AddLanguage("fr-VU", "French (Vanuatu)"); 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 FrenchWallisandFutuna = AddLanguage("fr-WF", "French (Wallis and Futuna)");
public static readonly Language FrenchMayotte = AddLanguage("fr-YT", "French (Mayotte)"); public static readonly Language FrenchMayotte = AddLanguage("fr-YT", "French (Mayotte)");
public static readonly Language WesternFrisianNetherlands = AddLanguage("fy-NL", "Western Frisian (Netherlands)");
public static readonly Language IrishIreland = AddLanguage("ga-IE", "Irish (Ireland)");
public static readonly Language ScottishGaelicUnitedKingdom = AddLanguage("gd-GB", "Scottish Gaelic (United Kingdom)");
public static readonly Language GalicianGalician = AddLanguage("gl-ES", "Galician (Galician)");
public static readonly Language GuaraniParaguay = AddLanguage("gn-PY", "Guarani (Paraguay)");
public static readonly Language GujaratiIndia = AddLanguage("gu-IN", "Gujarati (India)");
public static readonly Language ManxIsleofMan = AddLanguage("gv-IM", "Manx (Isle of Man)");
public static readonly Language HebrewIsrael = AddLanguage("he-IL", "Hebrew (Israel)");
public static readonly Language HindiIndia = AddLanguage("hi-IN", "Hindi (India)");
public static readonly Language CroatianBosniaandHerzegovina = AddLanguage("hr-BA", "Croatian (Bosnia and Herzegovina)"); 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 CroatianCroatia = AddLanguage("hr-HR", "Croatian (Croatia)");
public static readonly Language HungarianHungary = AddLanguage("hu-HU", "Hungarian (Hungary)");
public static readonly Language ArmenianArmenia = AddLanguage("hy-AM", "Armenian (Armenia)");
public static readonly Language IndonesianIndonesia = AddLanguage("id-ID", "Indonesian (Indonesia)");
public static readonly Language IgboNigeria = AddLanguage("ig-NG", "Igbo (Nigeria)");
public static readonly Language YiChina = AddLanguage("ii-CN", "Yi (China)");
public static readonly Language IcelandicIceland = AddLanguage("is-IS", "Icelandic (Iceland)");
public static readonly Language ItalianSwitzerland = AddLanguage("it-CH", "Italian (Switzerland)"); public static readonly Language ItalianSwitzerland = AddLanguage("it-CH", "Italian (Switzerland)");
public static readonly Language ItalianItaly = AddLanguage("it-IT", "Italian (Italy)"); 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 ItalianSanMarino = AddLanguage("it-SM", "Italian (San Marino)");
public static readonly Language ItalianVaticanCity = AddLanguage("it-VA", "Italian (Vatican City)"); public static readonly Language ItalianVaticanCity = AddLanguage("it-VA", "Italian (Vatican City)");
public static readonly Language JapaneseJapan = AddLanguage("ja-JP", "Japanese (Japan)");
public static readonly Language GeorgianGeorgia = AddLanguage("ka-GE", "Georgian (Georgia)");
public static readonly Language KikuyuKenya = AddLanguage("ki-KE", "Kikuyu (Kenya)");
public static readonly Language KazakhKazakhstan = AddLanguage("kk-KZ", "Kazakh (Kazakhstan)");
public static readonly Language GreenlandicGreenland = AddLanguage("kl-GL", "Greenlandic (Greenland)");
public static readonly Language KhmerCambodia = AddLanguage("km-KH", "Khmer (Cambodia)");
public static readonly Language KannadaIndia = AddLanguage("kn-IN", "Kannada (India)");
public static readonly Language KoreanNorthKorea = AddLanguage("ko-KP", "Korean (North Korea)"); 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 KoreanKorea = AddLanguage("ko-KR", "Korean (Korea)");
public static readonly Language KanuriNigeria = AddLanguage("kr-NG", "Kanuri (Nigeria)");
public static readonly Language CornishUnitedKingdom = AddLanguage("kw-GB", "Cornish (United Kingdom)");
public static readonly Language KyrgyzKyrgyzstan = AddLanguage("ky-KG", "Kyrgyz (Kyrgyzstan)");
public static readonly Language LuxembourgishLuxembourg = AddLanguage("lb-LU", "Luxembourgish (Luxembourg)");
public static readonly Language GandaUganda = AddLanguage("lg-UG", "Ganda (Uganda)");
public static readonly Language LingalaAngola = AddLanguage("ln-AO", "Lingala (Angola)"); 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 LingalaCongoDRC = AddLanguage("ln-CD", "Lingala (Congo DRC)");
public static readonly Language LingalaCentralAfricanRepublic = AddLanguage("ln-CF", "Lingala (Central African Republic)"); 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 LingalaCongo = AddLanguage("ln-CG", "Lingala (Congo)");
public static readonly Language LaoLaos = AddLanguage("lo-LA", "Lao (Laos)");
public static readonly Language LithuanianLithuania = AddLanguage("lt-LT", "Lithuanian (Lithuania)");
public static readonly Language LubaKatangaCongoDRC = AddLanguage("lu-CD", "Luba-Katanga (Congo DRC)");
public static readonly Language LatvianLatvia = AddLanguage("lv-LV", "Latvian (Latvia)");
public static readonly Language MalagasyMadagascar = AddLanguage("mg-MG", "Malagasy (Madagascar)");
public static readonly Language MaoriNewZealand = AddLanguage("mi-NZ", "Maori (New Zealand)");
public static readonly Language MacedonianMacedoniaFYRO = AddLanguage("mk-MK", "Macedonian (Macedonia, FYRO)");
public static readonly Language MalayalamIndia = AddLanguage("ml-IN", "Malayalam (India)");
public static readonly Language MongolianMongolia = AddLanguage("mn-MN", "Mongolian (Mongolia)");
public static readonly Language MarathiIndia = AddLanguage("mr-IN", "Marathi (India)");
public static readonly Language MalayBrunei = AddLanguage("ms-BN", "Malay (Brunei)"); public static readonly Language MalayBrunei = AddLanguage("ms-BN", "Malay (Brunei)");
public static readonly Language MalayMalaysia = AddLanguage("ms-MY", "Malay (Malaysia)"); public static readonly Language MalayMalaysia = AddLanguage("ms-MY", "Malay (Malaysia)");
public static readonly Language MalaySingapore = AddLanguage("ms-SG", "Malay (Singapore)"); public static readonly Language MalaySingapore = AddLanguage("ms-SG", "Malay (Singapore)");
public static readonly Language MalteseMalta = AddLanguage("mt-MT", "Maltese (Malta)");
public static readonly Language BurmeseMyanmar = AddLanguage("my-MM", "Burmese (Myanmar)");
public static readonly Language NorwegianBokmålNorway = AddLanguage("nb-NO", "Norwegian Bokmål (Norway)"); 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 NorwegianBokmålSvalbardandJanMayen = AddLanguage("nb-SJ", "Norwegian Bokmål (Svalbard and Jan Mayen)");
public static readonly Language NorthNdebeleZimbabwe = AddLanguage("nd-ZW", "North Ndebele (Zimbabwe)");
public static readonly Language NepaliIndia = AddLanguage("ne-IN", "Nepali (India)"); public static readonly Language NepaliIndia = AddLanguage("ne-IN", "Nepali (India)");
public static readonly Language NepaliNepal = AddLanguage("ne-NP", "Nepali (Nepal)"); public static readonly Language NepaliNepal = AddLanguage("ne-NP", "Nepali (Nepal)");
public static readonly Language DutchAruba = AddLanguage("nl-AW", "Dutch (Aruba)"); public static readonly Language DutchAruba = AddLanguage("nl-AW", "Dutch (Aruba)");
@ -449,8 +506,13 @@ namespace Squidex.Infrastructure
public static readonly Language DutchNetherlands = AddLanguage("nl-NL", "Dutch (Netherlands)"); public static readonly Language DutchNetherlands = AddLanguage("nl-NL", "Dutch (Netherlands)");
public static readonly Language DutchSuriname = AddLanguage("nl-SR", "Dutch (Suriname)"); 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 DutchSintMaarten = AddLanguage("nl-SX", "Dutch (Sint Maarten)");
public static readonly Language NorwegianNynorskNorway = AddLanguage("nn-NO", "Norwegian Nynorsk (Norway)");
public static readonly Language SouthNdebeleSouthAfrica = AddLanguage("nr-ZA", "South Ndebele (South Africa)");
public static readonly Language OromoEthiopia = AddLanguage("om-ET", "Oromo (Ethiopia)"); public static readonly Language OromoEthiopia = AddLanguage("om-ET", "Oromo (Ethiopia)");
public static readonly Language OromoKenya = AddLanguage("om-KE", "Oromo (Kenya)"); public static readonly Language OromoKenya = AddLanguage("om-KE", "Oromo (Kenya)");
public static readonly Language OdiaIndia = AddLanguage("or-IN", "Odia (India)");
public static readonly Language PolishPoland = AddLanguage("pl-PL", "Polish (Poland)");
public static readonly Language PashtoAfghanistan = AddLanguage("ps-AF", "Pashto (Afghanistan)");
public static readonly Language PortugueseAngola = AddLanguage("pt-AO", "Portuguese (Angola)"); public static readonly Language PortugueseAngola = AddLanguage("pt-AO", "Portuguese (Angola)");
public static readonly Language PortugueseBrazil = AddLanguage("pt-BR", "Portuguese (Brazil)"); public static readonly Language PortugueseBrazil = AddLanguage("pt-BR", "Portuguese (Brazil)");
public static readonly Language PortugueseSwitzerland = AddLanguage("pt-CH", "Portuguese (Switzerland)"); public static readonly Language PortugueseSwitzerland = AddLanguage("pt-CH", "Portuguese (Switzerland)");
@ -463,6 +525,8 @@ namespace Squidex.Infrastructure
public static readonly Language PortuguesePortugal = AddLanguage("pt-PT", "Portuguese (Portugal)"); 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 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 PortugueseTimorLeste = AddLanguage("pt-TL", "Portuguese (Timor-Leste)");
public static readonly Language RomanshSwitzerland = AddLanguage("rm-CH", "Romansh (Switzerland)");
public static readonly Language RundiBurundi = AddLanguage("rn-BI", "Rundi (Burundi)");
public static readonly Language RomanianMoldova = AddLanguage("ro-MD", "Romanian (Moldova)"); public static readonly Language RomanianMoldova = AddLanguage("ro-MD", "Romanian (Moldova)");
public static readonly Language RomanianRomania = AddLanguage("ro-RO", "Romanian (Romania)"); public static readonly Language RomanianRomania = AddLanguage("ro-RO", "Romanian (Romania)");
public static readonly Language RussianBelarus = AddLanguage("ru-BY", "Russian (Belarus)"); public static readonly Language RussianBelarus = AddLanguage("ru-BY", "Russian (Belarus)");
@ -471,9 +535,15 @@ namespace Squidex.Infrastructure
public static readonly Language RussianMoldova = AddLanguage("ru-MD", "Russian (Moldova)"); public static readonly Language RussianMoldova = AddLanguage("ru-MD", "Russian (Moldova)");
public static readonly Language RussianRussia = AddLanguage("ru-RU", "Russian (Russia)"); public static readonly Language RussianRussia = AddLanguage("ru-RU", "Russian (Russia)");
public static readonly Language RussianUkraine = AddLanguage("ru-UA", "Russian (Ukraine)"); public static readonly Language RussianUkraine = AddLanguage("ru-UA", "Russian (Ukraine)");
public static readonly Language KinyarwandaRwanda = AddLanguage("rw-RW", "Kinyarwanda (Rwanda)");
public static readonly Language SanskritIndia = AddLanguage("sa-IN", "Sanskrit (India)");
public static readonly Language SamiNorthernFinland = AddLanguage("se-FI", "Sami, Northern (Finland)"); 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 SamiNorthernNorway = AddLanguage("se-NO", "Sami, Northern (Norway)");
public static readonly Language SamiNorthernSweden = AddLanguage("se-SE", "Sami, Northern (Sweden)"); public static readonly Language SamiNorthernSweden = AddLanguage("se-SE", "Sami, Northern (Sweden)");
public static readonly Language SangoCentralAfricanRepublic = AddLanguage("sg-CF", "Sango (Central African Republic)");
public static readonly Language SinhalaSriLanka = AddLanguage("si-LK", "Sinhala (Sri Lanka)");
public static readonly Language SlovakSlovakia = AddLanguage("sk-SK", "Slovak (Slovakia)");
public static readonly Language SlovenianSlovenia = AddLanguage("sl-SI", "Slovenian (Slovenia)");
public static readonly Language SomaliDjibouti = AddLanguage("so-DJ", "Somali (Djibouti)"); public static readonly Language SomaliDjibouti = AddLanguage("so-DJ", "Somali (Djibouti)");
public static readonly Language SomaliEthiopia = AddLanguage("so-ET", "Somali (Ethiopia)"); public static readonly Language SomaliEthiopia = AddLanguage("so-ET", "Somali (Ethiopia)");
public static readonly Language SomaliKenya = AddLanguage("so-KE", "Somali (Kenya)"); public static readonly Language SomaliKenya = AddLanguage("so-KE", "Somali (Kenya)");
@ -496,14 +566,26 @@ namespace Squidex.Infrastructure
public static readonly Language TamilSriLanka = AddLanguage("ta-LK", "Tamil (Sri Lanka)"); 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 TamilMalaysia = AddLanguage("ta-MY", "Tamil (Malaysia)");
public static readonly Language TamilSingapore = AddLanguage("ta-SG", "Tamil (Singapore)"); public static readonly Language TamilSingapore = AddLanguage("ta-SG", "Tamil (Singapore)");
public static readonly Language TeluguIndia = AddLanguage("te-IN", "Telugu (India)");
public static readonly Language ThaiThailand = AddLanguage("th-TH", "Thai (Thailand)");
public static readonly Language TigrinyaEritrea = AddLanguage("ti-ER", "Tigrinya (Eritrea)"); public static readonly Language TigrinyaEritrea = AddLanguage("ti-ER", "Tigrinya (Eritrea)");
public static readonly Language TigrinyaEthiopia = AddLanguage("ti-ET", "Tigrinya (Ethiopia)"); public static readonly Language TigrinyaEthiopia = AddLanguage("ti-ET", "Tigrinya (Ethiopia)");
public static readonly Language TurkmenTurkmenistan = AddLanguage("tk-TM", "Turkmen (Turkmenistan)");
public static readonly Language SetswanaBotswana = AddLanguage("tn-BW", "Setswana (Botswana)"); 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 SetswanaSouthAfrica = AddLanguage("tn-ZA", "Setswana (South Africa)");
public static readonly Language TonganTonga = AddLanguage("to-TO", "Tongan (Tonga)");
public static readonly Language TurkishCyprus = AddLanguage("tr-CY", "Turkish (Cyprus)"); public static readonly Language TurkishCyprus = AddLanguage("tr-CY", "Turkish (Cyprus)");
public static readonly Language TurkishTurkey = AddLanguage("tr-TR", "Turkish (Turkey)"); public static readonly Language TurkishTurkey = AddLanguage("tr-TR", "Turkish (Turkey)");
public static readonly Language XitsongaSouthAfrica = AddLanguage("ts-ZA", "Xitsonga (South Africa)");
public static readonly Language TatarRussia = AddLanguage("tt-RU", "Tatar (Russia)");
public static readonly Language UyghurChina = AddLanguage("ug-CN", "Uyghur (China)");
public static readonly Language UkrainianUkraine = AddLanguage("uk-UA", "Ukrainian (Ukraine)");
public static readonly Language UrduIndia = AddLanguage("ur-IN", "Urdu (India)"); public static readonly Language UrduIndia = AddLanguage("ur-IN", "Urdu (India)");
public static readonly Language UrduPakistan = AddLanguage("ur-PK", "Urdu (Pakistan)"); public static readonly Language UrduPakistan = AddLanguage("ur-PK", "Urdu (Pakistan)");
public static readonly Language VendaSouthAfrica = AddLanguage("ve-ZA", "Venda (South Africa)");
public static readonly Language VietnameseVietnam = AddLanguage("vi-VN", "Vietnamese (Vietnam)");
public static readonly Language WolofSenegal = AddLanguage("wo-SN", "Wolof (Senegal)");
public static readonly Language isiXhosaSouthAfrica = AddLanguage("xh-ZA", "isiXhosa (South Africa)");
public static readonly Language YorubaBenin = AddLanguage("yo-BJ", "Yoruba (Benin)"); public static readonly Language YorubaBenin = AddLanguage("yo-BJ", "Yoruba (Benin)");
public static readonly Language YorubaNigeria = AddLanguage("yo-NG", "Yoruba (Nigeria)"); 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 ChineseSimplifiedChina = AddLanguage("zh-CN", "Chinese (Simplified, China)");
@ -511,5 +593,6 @@ namespace Squidex.Infrastructure
public static readonly Language ChineseTraditionalMacaoSAR = AddLanguage("zh-MO", "Chinese (Traditional, Macao 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 ChineseSimplifiedSingapore = AddLanguage("zh-SG", "Chinese (Simplified, Singapore)");
public static readonly Language ChineseTraditionalTaiwan = AddLanguage("zh-TW", "Chinese (Traditional, Taiwan)"); public static readonly Language ChineseTraditionalTaiwan = AddLanguage("zh-TW", "Chinese (Traditional, Taiwan)");
public static readonly Language isiZuluSouthAfrica = AddLanguage("zu-ZA", "isiZulu (South Africa)");
} }
} }

2
src/Squidex.Web/Constants.cs

@ -23,6 +23,8 @@ namespace Squidex.Web
public static readonly string PortalPrefix = "/portal"; public static readonly string PortalPrefix = "/portal";
public static readonly string EmailScope = "email";
public static readonly string RoleScope = "role"; public static readonly string RoleScope = "role";
public static readonly string PermissionsScope = "permissions"; public static readonly string PermissionsScope = "permissions";

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

@ -51,6 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
/// <param name="version">The optional version of the asset.</param> /// <param name="version">The optional version of the asset.</param>
/// <param name="width">The target width of the asset, if it is an image.</param> /// <param name="width">The target width of the asset, if it is an image.</param>
/// <param name="height">The target height of the asset, if it is an image.</param> /// <param name="height">The target height of the asset, if it is an image.</param>
/// <param name="quality">Optional image quality, it is is an jpeg image.</param>
/// <param name="mode">The resize mode when the width and height is defined.</param> /// <param name="mode">The resize mode when the width and height is defined.</param>
/// <returns> /// <returns>
/// 200 => Asset found and content or (resized) image returned. /// 200 => Asset found and content or (resized) image returned.
@ -64,11 +65,12 @@ namespace Squidex.Areas.Api.Controllers.Assets
[FromQuery] long version = EtagVersion.Any, [FromQuery] long version = EtagVersion.Any,
[FromQuery] int? width = null, [FromQuery] int? width = null,
[FromQuery] int? height = null, [FromQuery] int? height = null,
[FromQuery] int? quality = null,
[FromQuery] string mode = null) [FromQuery] string mode = null)
{ {
var entity = await assetRepository.FindAssetAsync(id); var entity = await assetRepository.FindAssetAsync(id);
if (entity == null || entity.FileVersion < version || width == 0 || height == 0) if (entity == null || entity.FileVersion < version || width == 0 || height == 0 || quality == 0)
{ {
return NotFound(); return NotFound();
} }
@ -79,10 +81,15 @@ namespace Squidex.Areas.Api.Controllers.Assets
{ {
var assetId = entity.Id.ToString(); var assetId = entity.Id.ToString();
if (entity.IsImage && (width.HasValue || height.HasValue)) if (entity.IsImage && (width.HasValue || height.HasValue || quality.HasValue))
{ {
var assetSuffix = $"{width}_{height}_{mode}"; var assetSuffix = $"{width}_{height}_{mode}";
if (quality.HasValue)
{
assetSuffix += $"_{quality}";
}
try try
{ {
await assetStore.DownloadAsync(assetId, entity.FileVersion, assetSuffix, bodyStream); await assetStore.DownloadAsync(assetId, entity.FileVersion, assetSuffix, bodyStream);
@ -103,7 +110,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
using (Profiler.Trace("ResizeImage")) using (Profiler.Trace("ResizeImage"))
{ {
await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, destinationStream, width, height, mode); await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, destinationStream, width, height, mode, quality);
destinationStream.Position = 0; destinationStream.Position = 0;
} }

41
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -106,6 +106,45 @@ namespace Squidex.Areas.Api.Controllers.Contents
} }
} }
/// <summary>
/// Queries contents.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="ids">The optional ids of the content to fetch.</param>
/// <param name="archived">Indicates whether to query content items from the archive.</param>
/// <returns>
/// 200 => Contents retrieved.
/// 404 => App not found.
/// </returns>
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs
/// </remarks>
[HttpGet]
[Route("content/{app}/")]
[ApiPermission]
[ApiCosts(1)]
public async Task<IActionResult> GetAllContents(string app, [FromQuery] string ids, [FromQuery] bool archived = false)
{
var context = Context().WithArchived(archived);
var result = await contentQuery.QueryAsync(context, Q.Empty.WithIds(ids).Ids);
var response = new ContentsDto
{
Total = result.Count,
Items = result.Take(200).Select(x => ContentDto.FromContent(x, context)).ToArray()
};
if (controllerOptions.Value.EnableSurrogateKeys && response.Items.Length <= controllerOptions.Value.MaxItemsForSurrogateKeys)
{
Response.Headers["Surrogate-Key"] = response.Items.ToSurrogateKeys();
}
Response.Headers[HeaderNames.ETag] = response.Items.ToManyEtag();
return Ok(response);
}
/// <summary> /// <summary>
/// Queries contents. /// Queries contents.
/// </summary> /// </summary>
@ -124,7 +163,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
[Route("content/{app}/{name}/")] [Route("content/{app}/{name}/")]
[ApiPermission] [ApiPermission]
[ApiCosts(1)] [ApiCosts(1)]
public async Task<IActionResult> GetContents(string app, string name, [FromQuery] bool archived = false, [FromQuery] string ids = null) public async Task<IActionResult> GetContents(string app, string name, [FromQuery] string ids = null, [FromQuery] bool archived = false)
{ {
var context = Context().WithArchived(archived); var context = Context().WithArchived(archived);

11
src/Squidex/Areas/IdentityServer/Controllers/Extensions.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
@ -20,7 +21,14 @@ namespace Squidex.Areas.IdentityServer.Controllers
{ {
var externalLogin = await signInManager.GetExternalLoginInfoAsync(expectedXsrf); var externalLogin = await signInManager.GetExternalLoginInfoAsync(expectedXsrf);
externalLogin.ProviderDisplayName = externalLogin.Principal.FindFirst(ClaimTypes.Email).Value; var email = externalLogin.Principal.FindFirst(ClaimTypes.Email)?.Value;
if (string.IsNullOrWhiteSpace(email))
{
throw new InvalidOperationException("External provider does not provide email claim.");
}
externalLogin.ProviderDisplayName = email;
return externalLogin; return externalLogin;
} }
@ -28,6 +36,7 @@ namespace Squidex.Areas.IdentityServer.Controllers
public static async Task<List<ExternalProvider>> GetExternalProvidersAsync(this SignInManager<IdentityUser> signInManager) public static async Task<List<ExternalProvider>> GetExternalProvidersAsync(this SignInManager<IdentityUser> signInManager)
{ {
var externalSchemes = await signInManager.GetExternalAuthenticationSchemesAsync(); var externalSchemes = await signInManager.GetExternalAuthenticationSchemesAsync();
var externalProviders = var externalProviders =
externalSchemes.Where(x => x.Name != OpenIdConnectDefaults.AuthenticationScheme) externalSchemes.Where(x => x.Name != OpenIdConnectDefaults.AuthenticationScheme)
.Select(x => new ExternalProvider(x.Name, x.DisplayName)).ToList(); .Select(x => new ExternalProvider(x.Name, x.DisplayName)).ToList();

2
src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml

@ -36,7 +36,7 @@
<div class="form-group"> <div class="form-group">
<button class="btn external-button btn-block btn btn-@schema" type="submit" name="provider" value="@provider.AuthenticationScheme"> <button class="btn external-button btn-block btn btn-@schema" type="submit" name="provider" value="@provider.AuthenticationScheme">
<i class="icon-@schema external-icon"></i> @type with <strong>@provider.AuthenticationScheme</strong> <i class="icon-@schema external-icon"></i> @type with <strong>@provider.DisplayName</strong>
</button> </button>
</div> </div>
} }

1
src/Squidex/Config/Authentication/OidcServices.cs

@ -25,6 +25,7 @@ namespace Squidex.Config.Authentication
options.Authority = identityOptions.OidcAuthority; options.Authority = identityOptions.OidcAuthority;
options.ClientId = identityOptions.OidcClient; options.ClientId = identityOptions.OidcClient;
options.ClientSecret = identityOptions.OidcSecret; options.ClientSecret = identityOptions.OidcSecret;
options.Scope.Add(Constants.EmailScope);
options.Scope.Add(Constants.PermissionsScope); options.Scope.Add(Constants.PermissionsScope);
options.RequireHttpsMetadata = false; options.RequireHttpsMetadata = false;
}); });

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

@ -250,6 +250,9 @@ namespace Squidex.Config.Domain
services.AddTransientAs<Rebuilder>() services.AddTransientAs<Rebuilder>()
.AsSelf(); .AsSelf();
services.AddTransientAs<RebuildRunner>()
.AsSelf();
services.AddTransientAs<MigrationPath>() services.AddTransientAs<MigrationPath>()
.As<IMigrationPath>(); .As<IMigrationPath>();

31
src/Squidex/Config/Startup/RebuilderHost.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Migrate_01;
using Squidex.Infrastructure.Log;
namespace Squidex.Config.Startup
{
public sealed class RebuilderHost : SafeHostedService
{
private readonly RebuildRunner rebuildRunner;
public RebuilderHost(IApplicationLifetime lifetime, ISemanticLog log, RebuildRunner rebuildRunner)
: base(lifetime, log)
{
this.rebuildRunner = rebuildRunner;
}
protected override Task StartAsync(ISemanticLog log, CancellationToken ct)
{
return rebuildRunner.RunAsync(ct);
}
}
}

7
src/Squidex/Pipeline/Squid/SquidMiddleware.cs

@ -83,13 +83,16 @@ namespace Squidex.Pipeline.Squid
svg = svg.Replace("{{TEXT3}}", l3); svg = svg.Replace("{{TEXT3}}", l3);
svg = svg.Replace("[COLOR]", background); svg = svg.Replace("[COLOR]", background);
context.Response.StatusCode = 200;
context.Response.ContentType = "image/svg+xml"; context.Response.ContentType = "image/svg+xml";
context.Response.Headers["Cache-Control"] = "public, max-age=604800"; context.Response.Headers["Cache-Control"] = "public, max-age=604800";
await context.Response.WriteAsync(svg); await context.Response.WriteAsync(svg);
} }
else
await next(context); {
await next(context);
}
} }
private static (string, string, string) SplitText(string text) private static (string, string, string) SplitText(string text)

4
src/Squidex/WebStartup.cs

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Migrate_01;
using Squidex.Areas.Api; using Squidex.Areas.Api;
using Squidex.Areas.Api.Config.Swagger; using Squidex.Areas.Api.Config.Swagger;
using Squidex.Areas.Api.Controllers.Contents; using Squidex.Areas.Api.Controllers.Contents;
@ -91,6 +92,8 @@ namespace Squidex
config.GetSection("urls")); config.GetSection("urls"));
services.Configure<UsageOptions>( services.Configure<UsageOptions>(
config.GetSection("usage")); config.GetSection("usage"));
services.Configure<RebuildOptions>(
config.GetSection("rebuild"));
services.Configure<MyContentsControllerOptions>( services.Configure<MyContentsControllerOptions>(
config.GetSection("contentsController")); config.GetSection("contentsController"));
@ -107,6 +110,7 @@ namespace Squidex
services.AddHostedService<MigratorHost>(); services.AddHostedService<MigratorHost>();
services.AddHostedService<BackgroundHost>(); services.AddHostedService<BackgroundHost>();
services.AddHostedService<RebuilderHost>();
return services.BuildServiceProvider(); return services.BuildServiceProvider();
} }

23
src/Squidex/appsettings.json

@ -355,5 +355,28 @@
* The deepl api key if you want to support automated translations. * The deepl api key if you want to support automated translations.
*/ */
"deeplAuthKey": "" "deeplAuthKey": ""
},
"rebuild": {
/*
* Set to true to rebuild apps.
*/
"apps": false,
/*
* Set to true to rebuild assets.
*/
"assets": false,
/*
* Set to true to rebuild contents.
*/
"contents": false,
/*
* Set to true to rebuild rules.
*/
"rules": false,
/*
* Set to true to rebuild schemas.
*/
"schemas": false
} }
} }

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs

@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent
Fields.Json(1, "1", Partitioning.Invariant, Fields.Json(1, "1", Partitioning.Invariant,
new JsonFieldProperties()); new JsonFieldProperties());
Assert.Equal(JsonValue.Object(), DefaultValueFactory.CreateDefaultValue(field, now)); Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, now));
} }
[Fact] [Fact]

88
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -358,7 +358,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Theory] [Theory]
[MemberData(nameof(ManyIdDataApi))] [MemberData(nameof(ManyIdDataApi))]
public async Task Should_query_contents_by_id_from_repository_and_transform(bool archive, bool unpublished, params Status[] status) public async Task Should_query_contents_by_id_for_api_and_transform(bool archive, bool unpublished, params Status[] status)
{ {
const int count = 5, total = 200; const int count = 5, total = 200;
@ -380,6 +380,86 @@ namespace Squidex.Domain.Apps.Entities.Contents
.MustHaveHappened(count, Times.Exactly); .MustHaveHappened(count, Times.Exactly);
} }
[Theory]
[MemberData(nameof(ManyIdDataFrontend))]
public async Task Should_query_all_contents_by_id_for_frontend_and_transform(bool archive, bool unpublished, params Status[] status)
{
const int count = 5;
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims(true);
SetupSchema();
SetupScripting(ids.ToArray());
SetupContents(status, ids);
var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, ids);
Assert.Equal(ids, result.Select(x => x.Id).ToList());
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.Ignored, A<string>.Ignored))
.MustNotHaveHappened();
}
[Theory]
[MemberData(nameof(ManyIdDataApi))]
public async Task Should_query_all_contents_by_id_for_api_and_transform(bool archive, bool unpublished, params Status[] status)
{
const int count = 5;
var ids = Enumerable.Range(0, count).Select(x => Guid.NewGuid()).ToList();
SetupClaims();
SetupSchema();
SetupScripting(ids.ToArray());
SetupContents(status, ids);
var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, ids);
Assert.Equal(ids, result.Select(x => x.Id).ToList());
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.Ignored, A<string>.Ignored))
.MustHaveHappened(count, Times.Exactly);
}
[Fact]
public async Task Should_skip_contents_when_user_has_no_permission()
{
var ids = Enumerable.Range(0, 1).Select(x => Guid.NewGuid()).ToList();
SetupClaims(false, false);
SetupSchema();
SetupContents(new Status[0], ids);
var ctx = context;
var result = await sut.QueryAsync(ctx, ids);
Assert.Empty(result);
}
[Fact]
public async Task Should_not_call_repository_if_no_id_defined()
{
var ids = new List<Guid>();
SetupClaims(false, false);
SetupSchema();
var ctx = context;
var result = await sut.QueryAsync(ctx, ids);
Assert.Empty(result);
A.CallTo(() => contentRepository.QueryAsync(app, A<Status[]>.Ignored, A<HashSet<Guid>>.Ignored))
.MustNotHaveHappened();
}
private void SetupClaims(bool isFrontend = false, bool allowSchema = true) private void SetupClaims(bool isFrontend = false, bool allowSchema = true)
{ {
if (isFrontend) if (isFrontend)
@ -414,6 +494,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
.Returns(ResultList.Create(total, ids.Select(x => CreateContent(x)).Shuffle())); .Returns(ResultList.Create(total, ids.Select(x => CreateContent(x)).Shuffle()));
} }
private void SetupContents(Status[] status, List<Guid> ids)
{
A.CallTo(() => contentRepository.QueryAsync(app, A<Status[]>.That.IsSameSequenceAs(status), A<HashSet<Guid>>.Ignored))
.Returns(ids.Select(x => (CreateContent(x), schema)).ToList());
}
private void SetupSchema() private void SetupSchema()
{ {
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))

48
tests/Squidex.Infrastructure.Tests/Assets/ImageSharpAssetThumbnailGeneratorTests.cs

@ -15,16 +15,15 @@ namespace Squidex.Infrastructure.Assets
{ {
public class ImageSharpAssetThumbnailGeneratorTests public class ImageSharpAssetThumbnailGeneratorTests
{ {
private const string Image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTM0A1t6AAAADElEQVQYV2P4//8/AAX+Av6nNYGEAAAAAElFTkSuQmCC";
private readonly ImageSharpAssetThumbnailGenerator sut = new ImageSharpAssetThumbnailGenerator(); private readonly ImageSharpAssetThumbnailGenerator sut = new ImageSharpAssetThumbnailGenerator();
private readonly MemoryStream target = new MemoryStream();
[Fact] [Fact]
public async Task Should_return_same_image_if_no_size_is_passed_for_thumbnail() public async Task Should_return_same_image_if_no_size_is_passed_for_thumbnail()
{ {
var source = new MemoryStream(Convert.FromBase64String(Image)); var source = GetPng();
var target = new MemoryStream();
await sut.CreateThumbnailAsync(source, target, null, null, "resize"); await sut.CreateThumbnailAsync(source, target);
Assert.Equal(target.Length, source.Length); Assert.Equal(target.Length, source.Length);
} }
@ -32,23 +31,42 @@ namespace Squidex.Infrastructure.Assets
[Fact] [Fact]
public async Task Should_resize_image_to_target() public async Task Should_resize_image_to_target()
{ {
var source = new MemoryStream(Convert.FromBase64String(Image)); var source = GetPng();
var target = new MemoryStream();
await sut.CreateThumbnailAsync(source, target, 100, 100, "resize"); await sut.CreateThumbnailAsync(source, target, 1000, 1000, "resize");
Assert.True(target.Length > source.Length); Assert.True(target.Length > source.Length);
} }
[Fact]
public async Task Should_change_jpeg_quality_and_write_to_target()
{
var source = GetJpeg();
await sut.CreateThumbnailAsync(source, target, quality: 10);
Assert.True(target.Length < source.Length);
}
[Fact]
public async Task Should_change_png_quality_and_write_to_target()
{
var source = GetPng();
await sut.CreateThumbnailAsync(source, target, quality: 10);
Assert.True(target.Length < source.Length);
}
[Fact] [Fact]
public async Task Should_return_image_information_if_image_is_valid() public async Task Should_return_image_information_if_image_is_valid()
{ {
var source = new MemoryStream(Convert.FromBase64String(Image)); var source = GetPng();
var imageInfo = await sut.GetImageInfoAsync(source); var imageInfo = await sut.GetImageInfoAsync(source);
Assert.Equal(1, imageInfo.PixelHeight); Assert.Equal(600, imageInfo.PixelHeight);
Assert.Equal(1, imageInfo.PixelWidth); Assert.Equal(600, imageInfo.PixelWidth);
} }
[Fact] [Fact]
@ -60,5 +78,15 @@ namespace Squidex.Infrastructure.Assets
Assert.Null(imageInfo); Assert.Null(imageInfo);
} }
private Stream GetPng()
{
return GetType().Assembly.GetManifestResourceStream("Squidex.Infrastructure.Assets.Images.logo.png");
}
private Stream GetJpeg()
{
return GetType().Assembly.GetManifestResourceStream("Squidex.Infrastructure.Assets.Images.logo.jpg");
}
} }
} }

BIN
tests/Squidex.Infrastructure.Tests/Assets/Images/logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
tests/Squidex.Infrastructure.Tests/Assets/Images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

8
tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -6,6 +6,10 @@
<RootNamespace>Squidex.Infrastructure</RootNamespace> <RootNamespace>Squidex.Infrastructure</RootNamespace>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Assets\Images\logo.jpg" />
<None Remove="Assets\Images\logo.png" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Infrastructure.Azure\Squidex.Infrastructure.Azure.csproj" /> <ProjectReference Include="..\..\src\Squidex.Infrastructure.Azure\Squidex.Infrastructure.Azure.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure.GetEventStore\Squidex.Infrastructure.GetEventStore.csproj" /> <ProjectReference Include="..\..\src\Squidex.Infrastructure.GetEventStore\Squidex.Infrastructure.GetEventStore.csproj" />
@ -35,4 +39,8 @@
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Images\logo.jpg" />
<EmbeddedResource Include="Assets\Images\logo.png" />
</ItemGroup>
</Project> </Project>

3
tools/GenerateLanguages/Program.cs

@ -83,9 +83,6 @@ namespace GenerateLanguages
return CultureInfo.GetCultures(CultureTypes.SpecificCultures) return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(x => x.ToString().Length == 5) .Where(x => x.ToString().Length == 5)
.Where(x => languages.Any(l => l.Iso2Code.Equals(x.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase))) .Where(x => languages.Any(l => l.Iso2Code.Equals(x.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)))
.GroupBy(x => x.TwoLetterISOLanguageName)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.Select(x => (x.ToString(), x.EnglishName)); .Select(x => (x.ToString(), x.EnglishName));
} }

10
tools/Migrate_01/Migrations/BuildFullTextIndices.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
@ -27,7 +28,12 @@ namespace Migrate_01.Migrations
this.store = store; this.store = store;
} }
public async Task UpdateAsync() public Task UpdateAsync()
{
return UpdateAsync();
}
public async Task UpdateAsync(CancellationToken ct)
{ {
var snapshotStore = store.GetSnapshotStore<ContentState>(); var snapshotStore = store.GetSnapshotStore<ContentState>();
@ -40,7 +46,7 @@ namespace Migrate_01.Migrations
MaxDegreeOfParallelism = Environment.ProcessorCount * 2 MaxDegreeOfParallelism = Environment.ProcessorCount * 2
}); });
await snapshotStore.ReadAllAsync((state, version) => worker.SendAsync(state)); await snapshotStore.ReadAllAsync((state, version) => worker.SendAsync(state), ct);
worker.Complete(); worker.Complete();

24
tools/Migrate_01/RebuildOptions.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Migrate_01
{
public sealed class RebuildOptions
{
public bool Apps { get; set; }
public bool Assets { get; set; }
public bool Contents { get; set; }
public bool Indices { get; set; }
public bool Rules { get; set; }
public bool Schemas { get; set; }
}
}

66
tools/Migrate_01/RebuildRunner.cs

@ -0,0 +1,66 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Migrate_01.Migrations;
using Squidex.Infrastructure;
namespace Migrate_01
{
public sealed class RebuildRunner
{
private readonly Rebuilder rebuilder;
private readonly BuildFullTextIndices fullTextIndices;
private readonly RebuildOptions rebuildOptions;
public RebuildRunner(Rebuilder rebuilder, BuildFullTextIndices fullTextIndices, IOptions<RebuildOptions> rebuildOptions)
{
Guard.NotNull(rebuilder, nameof(rebuilder));
Guard.NotNull(rebuildOptions, nameof(rebuildOptions));
Guard.NotNull(fullTextIndices, nameof(fullTextIndices));
this.rebuilder = rebuilder;
this.rebuildOptions = rebuildOptions.Value;
this.fullTextIndices = fullTextIndices;
}
public async Task RunAsync(CancellationToken ct)
{
if (rebuildOptions.Apps)
{
await rebuilder.RebuildAppsAsync(ct);
}
if (rebuildOptions.Schemas)
{
await rebuilder.RebuildSchemasAsync(ct);
}
if (rebuildOptions.Rules)
{
await rebuilder.RebuildRulesAsync(ct);
}
if (rebuildOptions.Assets)
{
await rebuilder.RebuildAssetsAsync(ct);
}
if (rebuildOptions.Contents)
{
await rebuilder.RebuildContentAsync(ct);
}
if (rebuildOptions.Indices)
{
await fullTextIndices.UpdateAsync(ct);
}
}
}
}

25
tools/Migrate_01/Rebuilder.cs

@ -7,6 +7,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
@ -43,32 +44,32 @@ namespace Migrate_01
this.store = store; this.store = store;
} }
public Task RebuildAppsAsync() public Task RebuildAppsAsync(CancellationToken ct = default)
{ {
return RebuildManyAsync<AppState, AppGrain>("^app\\-"); return RebuildManyAsync<AppState, AppGrain>("^app\\-", ct);
} }
public Task RebuildSchemasAsync() public Task RebuildSchemasAsync(CancellationToken ct = default)
{ {
return RebuildManyAsync<SchemaState, SchemaGrain>("^schema\\-"); return RebuildManyAsync<SchemaState, SchemaGrain>("^schema\\-", ct);
} }
public Task RebuildRulesAsync() public Task RebuildRulesAsync(CancellationToken ct = default)
{ {
return RebuildManyAsync<RuleState, RuleGrain>("^rule\\-"); return RebuildManyAsync<RuleState, RuleGrain>("^rule\\-", ct);
} }
public Task RebuildAssetsAsync() public Task RebuildAssetsAsync(CancellationToken ct = default)
{ {
return RebuildManyAsync<AssetState, AssetGrain>("^asset\\-"); return RebuildManyAsync<AssetState, AssetGrain>("^asset\\-", ct);
} }
public Task RebuildContentAsync() public Task RebuildContentAsync(CancellationToken ct = default)
{ {
return RebuildManyAsync<ContentState, ContentGrain>("^content\\-"); return RebuildManyAsync<ContentState, ContentGrain>("^content\\-", ct);
} }
private async Task RebuildManyAsync<TState, TGrain>(string filter) where TState : IDomainState<TState>, new() private async Task RebuildManyAsync<TState, TGrain>(string filter, CancellationToken ct) where TState : IDomainState<TState>, new()
{ {
var handledIds = new HashSet<Guid>(); var handledIds = new HashSet<Guid>();
@ -113,7 +114,7 @@ namespace Migrate_01
{ {
await worker.SendAsync(id); await worker.SendAsync(id);
} }
}, filter); }, filter, ct);
worker.Complete(); worker.Complete();

Loading…
Cancel
Save