Browse Source

Extracted service.

pull/107/head
Sebastian Stehle 9 years ago
parent
commit
982c284cf0
  1. 4
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs
  2. 148
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs
  3. 168
      src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs
  4. 34
      src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs
  5. 25
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs
  6. 4
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs
  7. 73
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/QueryContext.cs
  8. 2
      src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs
  9. 26
      src/Squidex.Domain.Apps.Read/Contents/IContentQueryService.cs
  10. 8
      src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs
  11. 115
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  12. 8
      src/Squidex/Pipeline/GraphQLUrlGenerator.cs
  13. 6
      tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs

4
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs

@ -94,10 +94,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{
return contentData;
}
set
{
contentData = value;
}
}
public void ParseData(Schema schema)

148
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs

@ -10,12 +10,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData;
using Microsoft.OData.UriParser;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Read.Schemas;
@ -30,7 +29,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
private readonly ISchemaProvider schemas;
private readonly EdmModelBuilder modelBuilder;
protected static FilterDefinitionBuilder<MongoContentEntity> Filter
{
@ -64,123 +62,89 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
}
}
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas, EdmModelBuilder modelBuilder)
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas)
{
Guard.NotNull(database, nameof(database));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas));
this.schemas = schemas;
this.database = database;
this.modelBuilder = modelBuilder;
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery)
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, bool nonPublished, HashSet<Guid> ids, ODataUriParser odataQuery)
{
var contentEntities = (List<IContentEntity>)null;
var collection = GetCollection(appEntity.Id);
await ForSchemaAsync(app.Id, schemaId, async (collection, schemaEntity) =>
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var model = modelBuilder.BuildEdmModel(schemaEntity, app);
var parser = model.ParseQuery(odataQuery);
cursor =
collection
.Find(parser, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished)
.Take(parser)
.Skip(parser)
.Sort(parser, schemaEntity.Schema);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (ODataException ex)
{
throw new ValidationException($"Failed to parse query: {ex.Message}", ex);
}
var entities = await cursor.ToListAsync();
foreach (var entity in entities)
{
entity.ParseData(schemaEntity.Schema);
}
contentEntities = entities.OfType<IContentEntity>().ToList();
});
return contentEntities;
cursor =
collection
.Find(odataQuery, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished)
.Take(odataQuery)
.Skip(odataQuery)
.Sort(odataQuery, schemaEntity.Schema);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
var entities = await cursor.ToListAsync();
foreach (var entity in entities)
{
entity.ParseData(schemaEntity.Schema);
}
return entities;
}
public async Task<long> CountAsync(IAppEntity app, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery)
public Task<long> CountAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, bool nonPublished, HashSet<Guid> ids, ODataUriParser odataQuery)
{
var contentsCount = 0L;
var collection = GetCollection(appEntity.Id);
await ForSchemaAsync(app.Id, schemaId, async (collection, schemaEntity) =>
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
{
var model = modelBuilder.BuildEdmModel(schemaEntity, app);
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (ODataException ex)
{
throw new ValidationException($"Failed to parse query: {ex.Message}", ex);
}
contentsCount = await cursor.CountAsync();
});
return contentsCount;
cursor = collection.Find(odataQuery, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
throw new ValidationException("This odata operation is not supported");
}
catch (NotImplementedException)
{
throw new ValidationException("This odata operation is not supported");
}
return cursor.CountAsync();
}
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds)
{
var contentEntities = (List<BsonDocument>)null;
var collection = GetCollection(appId);
await ForAppIdAsync(appId, async collection =>
{
contentEntities =
await collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Project<BsonDocument>(Projection.Include(x => x.Id))
.ToListAsync();
});
var contentEntities =
await collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Project<BsonDocument>(Projection.Include(x => x.Id))
.ToListAsync();
return contentIds.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IContentEntity> FindContentAsync(IAppEntity app, Guid schemaId, Guid id)
public async Task<IContentEntity> FindContentAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, Guid id)
{
var contentEntity = (MongoContentEntity)null;
var collection = GetCollection(appEntity.Id);
await ForSchemaAsync(app.Id, schemaId, async (collection, schemaEntity) =>
{
contentEntity =
await collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
var contentEntity =
await collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schemaEntity.Schema);
});
contentEntity?.ParseData(schemaEntity.Schema);
return contentEntity;
}

168
src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs

@ -0,0 +1,168 @@
// ==========================================================================
// ContentQueryService.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.OData;
using Microsoft.OData.UriParser;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
// ReSharper disable InvertIf
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Squidex.Domain.Apps.Read.Contents
{
public sealed class ContentQueryService : IContentQueryService
{
private readonly IContentRepository contentRepository;
private readonly ISchemaProvider schemas;
private readonly IScriptEngine scriptEngine;
private readonly EdmModelBuilder modelBuilder;
public ContentQueryService(
IContentRepository contentRepository,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
EdmModelBuilder modelBuilder)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas));
this.contentRepository = contentRepository;
this.schemas = schemas;
this.scriptEngine = scriptEngine;
this.modelBuilder = modelBuilder;
}
public async Task<(ISchemaEntity SchemaEntity, IContentEntity ContentEntity)> FindContentAsync(IAppEntity appEntity, string schemaIdOrName, ClaimsPrincipal user, Guid id)
{
Guard.NotNull(appEntity, nameof(appEntity));
Guard.NotNull(user, nameof(user));
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName));
var schemaEntity = await FindSchemaAsync(appEntity, schemaIdOrName);
var contentEntity = await contentRepository.FindContentAsync(appEntity, schemaEntity, id);
if (contentEntity == null)
{
throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity));
}
contentEntity = TransformContent(user, schemaEntity, new List<IContentEntity> { contentEntity })[0];
return (schemaEntity, contentEntity);
}
public async Task<(ISchemaEntity SchemaEntity, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity appEntity, string schemaIdOrName, ClaimsPrincipal user, HashSet<Guid> ids, string query)
{
Guard.NotNull(appEntity, nameof(appEntity));
Guard.NotNull(user, nameof(user));
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName));
var schemaEntity = await FindSchemaAsync(appEntity, schemaIdOrName);
var parsedQuery = ParseQuery(appEntity, query, schemaEntity);
var isFrontendClient = user.IsInClient("squidex-frontend");
var taskForItems = contentRepository.QueryAsync(appEntity, schemaEntity, isFrontendClient, ids, parsedQuery);
var taskForCount = contentRepository.CountAsync(appEntity, schemaEntity, isFrontendClient, ids, parsedQuery);
await Task.WhenAll(taskForItems, taskForCount);
var list = TransformContent(user, schemaEntity, taskForItems.Result.ToList());
return (schemaEntity, taskForCount.Result, list);
}
private List<IContentEntity> TransformContent(ClaimsPrincipal user, ISchemaEntity schemaEntity, List<IContentEntity> contentEntities)
{
var scriptText = schemaEntity.ScriptQuery;
if (!string.IsNullOrWhiteSpace(scriptText))
{
for (var i = 0; i < contentEntities.Count; i++)
{
var contentEntity = contentEntities[i];
var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = contentEntity.Data, ContentId = contentEntity.Id }, scriptText);
contentEntities[i] = SimpleMapper.Map(contentEntity, new Content { Data = contentData });
}
}
return contentEntities;
}
private ODataUriParser ParseQuery(IAppEntity appEntity, string query, ISchemaEntity schemaEntity)
{
try
{
var model = modelBuilder.BuildEdmModel(schemaEntity, appEntity);
return model.ParseQuery(query);
}
catch (ODataException ex)
{
throw new ValidationException($"Failed to parse query: {ex.Message}", ex);
}
}
public async Task<ISchemaEntity> FindSchemaAsync(IEntity appEntity, string schemaIdOrName)
{
Guard.NotNull(appEntity, nameof(appEntity));
ISchemaEntity schema = null;
if (Guid.TryParse(schemaIdOrName, out var id))
{
schema = await schemas.FindSchemaByIdAsync(id);
}
if (schema == null)
{
schema = await schemas.FindSchemaByNameAsync(appEntity.Id, schemaIdOrName);
}
if (schema == null)
{
throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity));
}
return schema;
}
private sealed class Content : IContentEntity
{
public Guid Id { get; set; }
public Guid AppId { get; set; }
public long Version { get; set; }
public bool IsPublished { get; set; }
public Instant Created { get; set; }
public Instant LastModified { get; set; }
public RefToken CreatedBy { get; set; }
public RefToken LastModifiedBy { get; set; }
public NamedContentData Data { get; set; }
}
}
}

34
src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs

@ -0,0 +1,34 @@
// ==========================================================================
// EdmModelExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Read.Contents.Edm
{
public static class EdmModelExtensions
{
public static ODataUriParser ParseQuery(this IEdmModel model, string query)
{
query = query ?? string.Empty;
var path = model.EntityContainer.EntitySets().First().Path.Path.Split('.').Last();
if (query.StartsWith("?"))
{
query = query.Substring(1);
}
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
return parser;
}
}
}

25
src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs

@ -11,13 +11,10 @@ using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
@ -30,12 +27,10 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService, IEventConsumer
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60);
private readonly IContentRepository contentRepository;
private readonly IContentQueryService contentQuery;
private readonly IGraphQLUrlGenerator urlGenerator;
private readonly IAssetRepository assetRepository;
private readonly ISchemaRepository schemaRepository;
private readonly ISchemaProvider schemas;
private readonly IScriptEngine scriptEngine;
public string Name
{
@ -49,26 +44,20 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
public CachingGraphQLService(IMemoryCache cache,
IAssetRepository assetRepository,
IContentRepository contentRepository,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator,
ISchemaRepository schemaRepository,
ISchemaProvider schemas,
IScriptEngine scriptEngine)
ISchemaRepository schemaRepository)
: base(cache)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(schemaRepository, nameof(schemaRepository));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(contentQuery, nameof(urlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery));
this.assetRepository = assetRepository;
this.contentRepository = contentRepository;
this.contentQuery = contentQuery;
this.urlGenerator = urlGenerator;
this.schemaRepository = schemaRepository;
this.schemas = schemas;
this.scriptEngine = scriptEngine;
}
public Task ClearAsync()
@ -92,7 +81,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
Guard.NotNull(query, nameof(query));
var modelContext = await GetModelAsync(app);
var queryContext = new QueryContext(app, assetRepository, contentRepository, urlGenerator, schemas, scriptEngine, user);
var queryContext = new QueryContext(app, assetRepository, contentQuery, urlGenerator, user);
return await modelContext.ExecuteAsync(queryContext, query);
}

4
src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs

@ -16,9 +16,9 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
bool CanGenerateAssetSourceUrl { get; }
string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity);
string GenerateAssetUrl(IAppEntity app, IAssetEntity assetEntity);
string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity);
string GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset);
string GenerateAssetSourceUrl(IAppEntity appEntity, IAssetEntity assetEntity);

73
src/Squidex.Domain.Apps.Read/Contents/GraphQL/QueryContext.cs

@ -29,12 +29,10 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>();
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>();
private readonly IContentRepository contentRepository;
private readonly IContentQueryService contentQuery;
private readonly IAssetRepository assetRepository;
private readonly IGraphQLUrlGenerator urlGenerator;
private readonly IScriptEngine scriptEngine;
private readonly ISchemaProvider schemas;
private readonly IAppEntity app;
private readonly IAppEntity appEntity;
private readonly ClaimsPrincipal user;
public IGraphQLUrlGenerator UrlGenerator
@ -43,31 +41,23 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
}
public QueryContext(
IAppEntity app,
IAppEntity appEntity,
IAssetRepository assetRepository,
IContentRepository contentRepository,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
ClaimsPrincipal user)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(appEntity, nameof(appEntity));
Guard.NotNull(user, nameof(user));
Guard.NotNull(app, nameof(app));
this.contentRepository = contentRepository;
this.assetRepository = assetRepository;
this.schemas = schemas;
this.scriptEngine = scriptEngine;
this.appEntity = appEntity;
this.contentQuery = contentQuery;
this.urlGenerator = urlGenerator;
this.user = user;
this.app = app;
}
public async Task<IAssetEntity> FindAssetAsync(Guid id)
@ -93,18 +83,11 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
if (content == null)
{
var schema = await schemas.FindSchemaByIdAsync(schemaId).ConfigureAwait(false);
content = (await contentQuery.FindContentAsync(appEntity, schemaId.ToString(), user, id).ConfigureAwait(false)).ContentEntity;
if (schema != null)
if (content != null)
{
content = await contentRepository.FindContentAsync(app, schemaId, id).ConfigureAwait(false);
if (content != null)
{
content.Data = scriptEngine.Transform(new ScriptContext { Data = content.Data, ContentId = content.Id, User = user }, schema.ScriptQuery);
cachedContents[content.Id] = content;
}
cachedContents[content.Id] = content;
}
}
@ -113,7 +96,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
public async Task<IReadOnlyList<IAssetEntity>> QueryAssetsAsync(string query, int skip = 0, int take = 10)
{
var assets = await assetRepository.QueryAsync(app.Id, null, null, query, take, skip);
var assets = await assetRepository.QueryAsync(appEntity.Id, null, null, query, take, skip);
foreach (var asset in assets)
{
@ -125,23 +108,14 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
public async Task<IReadOnlyList<IContentEntity>> QueryContentsAsync(Guid schemaId, string query)
{
var result = new List<IContentEntity>();
var contents = (await contentQuery.QueryWithCountAsync(appEntity, schemaId.ToString(), user, null, query).ConfigureAwait(false)).Items;
var schema = await schemas.FindSchemaByIdAsync(schemaId).ConfigureAwait(false);
if (schema != null)
foreach (var content in contents)
{
result.AddRange(await contentRepository.QueryAsync(app, schemaId, false, null, query).ConfigureAwait(false));
foreach (var content in result)
{
content.Data = scriptEngine.Transform(new ScriptContext { Data = content.Data, ContentId = content.Id, User = user }, schema.ScriptQuery);
cachedContents[content.Id] = content;
}
cachedContents[content.Id] = content;
}
return result;
return contents;
}
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(JToken value)
@ -159,7 +133,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
if (notLoadedAssets.Count > 0)
{
var assets = await assetRepository.QueryAsync(app.Id, null, notLoadedAssets, null, int.MaxValue).ConfigureAwait(false);
var assets = await assetRepository.QueryAsync(appEntity.Id, null, notLoadedAssets, null, int.MaxValue).ConfigureAwait(false);
foreach (var asset in assets)
{
@ -185,18 +159,11 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
if (notLoadedContents.Count > 0)
{
var schema = await schemas.FindSchemaByIdAsync(schemaId).ConfigureAwait(false);
var contents = (await contentQuery.QueryWithCountAsync(appEntity, schemaId.ToString(), user, notLoadedContents, null).ConfigureAwait(false)).Items;
if (schema != null)
foreach (var content in contents)
{
var contents = await contentRepository.QueryAsync(app, schemaId, false, notLoadedContents, null).ConfigureAwait(false);
foreach (var content in contents)
{
content.Data = scriptEngine.Transform(new ScriptContext { Data = content.Data, ContentId = content.Id, User = user }, schema.ScriptQuery);
cachedContents[content.Id] = content;
}
cachedContents[content.Id] = content;
}
}

2
src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs

@ -14,6 +14,6 @@ namespace Squidex.Domain.Apps.Read.Contents
{
bool IsPublished { get; }
NamedContentData Data { get; set; }
NamedContentData Data { get;}
}
}

26
src/Squidex.Domain.Apps.Read/Contents/IContentQueryService.cs

@ -0,0 +1,26 @@
// ==========================================================================
// IContentQueryService.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read.Contents
{
public interface IContentQueryService
{
Task<(ISchemaEntity SchemaEntity, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity appEntity, string schemaIdOrName, ClaimsPrincipal user, HashSet<Guid> ids, string query);
Task<(ISchemaEntity SchemaEntity, IContentEntity ContentEntity)> FindContentAsync(IAppEntity appEntity, string schemaIdOrName, ClaimsPrincipal user, Guid id);
Task<ISchemaEntity> FindSchemaAsync(IEntity appEntity, string schemaIdOrName);
}
}

8
src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs

@ -9,18 +9,20 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.OData.UriParser;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read.Contents.Repositories
{
public interface IContentRepository
{
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery);
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, bool nonPublished, HashSet<Guid> ids, ODataUriParser odataQuery);
Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds);
Task<long> CountAsync(IAppEntity app, Guid schemaId, bool nonPublished, HashSet<Guid> ids, string odataQuery);
Task<long> CountAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, bool nonPublished, HashSet<Guid> ids, ODataUriParser odataQuery);
Task<IContentEntity> FindContentAsync(IAppEntity app, Guid schemaId, Guid id);
Task<IContentEntity> FindContentAsync(IAppEntity appEntity, ISchemaEntity schemaEntity, Guid id);
}
}

115
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -16,6 +16,7 @@ using NSwag.Annotations;
using Squidex.Controllers.ContentApi.Models;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas;
@ -38,23 +39,15 @@ namespace Squidex.Controllers.ContentApi
[SwaggerIgnore]
public sealed class ContentsController : ControllerBase
{
private readonly ISchemaProvider schemas;
private readonly IScriptEngine scriptEngine;
private readonly IContentRepository contentRepository;
private readonly IGraphQLService graphQL;
public ContentsController(
ICommandBus commandBus,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
IContentRepository contentRepository,
IGraphQLService graphQL)
private readonly IContentQueryService contentQuery;
private readonly IGraphQLService graphQl;
public ContentsController(ICommandBus commandBus, IContentQueryService contentQuery, IGraphQLService graphQl)
: base(commandBus)
{
this.graphQL = graphQL;
this.schemas = schemas;
this.scriptEngine = scriptEngine;
this.contentRepository = contentRepository;
this.contentQuery = contentQuery;
this.graphQl = graphQl;
}
[MustBeAppReader]
@ -64,7 +57,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(2)]
public async Task<IActionResult> PostGraphQL([FromBody] GraphQLQuery query)
{
var result = await graphQL.QueryAsync(App, User, query);
var result = await graphQl.QueryAsync(App, User, query);
if (result.Errors?.Length > 0)
{
@ -82,8 +75,6 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(2)]
public async Task<IActionResult> GetContents(string name, [FromQuery] string ids = null)
{
var schemaEntity = await FindSchemaAsync(name);
var idsList = new HashSet<Guid>();
if (!string.IsNullOrWhiteSpace(ids))
@ -99,34 +90,18 @@ namespace Squidex.Controllers.ContentApi
var isFrontendClient = User.IsFrontendClient();
var query = Request.QueryString.ToString();
var taskForItems = contentRepository.QueryAsync(App, schemaEntity.Id, isFrontendClient, idsList, query);
var taskForCount = contentRepository.CountAsync(App, schemaEntity.Id, isFrontendClient, idsList, query);
await Task.WhenAll(taskForItems, taskForCount);
var scriptText = schemaEntity.ScriptQuery;
var hasScript = !string.IsNullOrWhiteSpace(scriptText);
var contents = await contentQuery.QueryWithCountAsync(App, name, User, idsList, Request.QueryString.ToString());
var response = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForItems.Result.Take(200).Select(item =>
Total = contents.Total,
Items = contents.Items.Take(200).Select(item =>
{
var itemModel = SimpleMapper.Map(item, new ContentDto());
if (item.Data != null)
{
var data = item.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, !isFrontendClient);
if (hasScript && !isFrontendClient)
{
data = scriptEngine.Transform(new ScriptContext { Data = data, ContentId = item.Id, User = User }, scriptText);
}
itemModel.Data = data;
itemModel.Data = item.Data.ToApiModel(contents.SchemaEntity.Schema, App.LanguagesConfig, null, !isFrontendClient);
}
return itemModel;
@ -142,39 +117,18 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> GetContent(string name, Guid id)
{
var schemaEntity = await FindSchemaAsync(name);
var content = await contentQuery.FindContentAsync(App, name, User, id);
var entity = await contentRepository.FindContentAsync(App, schemaEntity.Id, id);
var response = SimpleMapper.Map(content.ContentEntity, new ContentDto());
if (entity == null)
{
return NotFound();
}
var response = SimpleMapper.Map(entity, new ContentDto());
if (entity.Data != null)
if (content.ContentEntity.Data != null)
{
var isFrontendClient = User.IsFrontendClient();
var data = entity.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, !isFrontendClient);
if (!isFrontendClient)
{
var scriptText = schemaEntity.ScriptQuery;
var hasScript = !string.IsNullOrWhiteSpace(scriptText);
if (hasScript)
{
data = scriptEngine.Transform(new ScriptContext { Data = data, ContentId = entity.Id, User = User }, scriptText);
}
}
response.Data = data;
response.Data = content.ContentEntity.Data.ToApiModel(content.SchemaEntity.Schema, App.LanguagesConfig, null, !isFrontendClient);
}
Response.Headers["ETag"] = new StringValues(entity.Version.ToString());
Response.Headers["ETag"] = new StringValues(content.ContentEntity.Version.ToString());
return Ok(response);
}
@ -185,7 +139,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PostContent(string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new CreateContent { ContentId = Guid.NewGuid(), User = User, Data = request.ToCleaned(), Publish = publish };
@ -203,7 +157,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PutContent(string name, Guid id, [FromBody] NamedContentData request)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new UpdateContent { ContentId = id, User = User, Data = request.ToCleaned() };
@ -221,7 +175,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PatchContent(string name, Guid id, [FromBody] NamedContentData request)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new PatchContent { ContentId = id, User = User, Data = request.ToCleaned() };
@ -239,7 +193,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> PublishContent(string name, Guid id)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new PublishContent { ContentId = id, User = User };
@ -254,7 +208,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> UnpublishContent(string name, Guid id)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new UnpublishContent { ContentId = id, User = User };
@ -269,7 +223,7 @@ namespace Squidex.Controllers.ContentApi
[ApiCosts(1)]
public async Task<IActionResult> DeleteContent(string name, Guid id)
{
await FindSchemaAsync(name);
await contentQuery.FindSchemaAsync(App, name);
var command = new DeleteContent { ContentId = id, User = User };
@ -277,26 +231,5 @@ namespace Squidex.Controllers.ContentApi
return NoContent();
}
private async Task<ISchemaEntity> FindSchemaAsync(string name)
{
ISchemaEntity schemaEntity;
if (Guid.TryParse(name, out var schemaId))
{
schemaEntity = await schemas.FindSchemaByIdAsync(schemaId);
}
else
{
schemaEntity = await schemas.FindSchemaByNameAsync(AppId, name);
}
if (schemaEntity == null || !schemaEntity.IsPublished)
{
throw new DomainObjectNotFoundException(name, typeof(ISchemaEntity));
}
return schemaEntity;
}
}
}

8
src/Squidex/Pipeline/GraphQLUrlGenerator.cs

@ -34,17 +34,17 @@ namespace Squidex.Pipeline
CanGenerateAssetSourceUrl = allowAssetSourceUrl;
}
public string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity)
public string GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset)
{
if (!assetEntity.IsImage)
if (!asset.IsImage)
{
return null;
}
return urlsOptions.BuildUrl($"api/assets/{assetEntity.Id}?version={assetEntity.Version}&width=100&mode=Max");
return urlsOptions.BuildUrl($"api/assets/{asset.Id}?version={asset.Version}&width=100&mode=Max");
}
public string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity)
public string GenerateAssetUrl(IAppEntity app, IAssetEntity assetEntity)
{
return urlsOptions.BuildUrl($"api/assets/{assetEntity.Id}?version={assetEntity.Version}");
}

6
tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs

@ -17,14 +17,14 @@ namespace Squidex.Domain.Apps.Read.Contents.TestData
{
public bool CanGenerateAssetSourceUrl { get; } = true;
public string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity)
public string GenerateAssetUrl(IAppEntity app, IAssetEntity assetEntity)
{
return $"assets/{assetEntity.Id}";
}
public string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity)
public string GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset)
{
return $"assets/{assetEntity.Id}?width=100";
return $"assets/{asset.Id}?width=100";
}
public string GenerateAssetSourceUrl(IAppEntity appEntity, IAssetEntity assetEntity)

Loading…
Cancel
Save