Browse Source

References handled

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
8117547473
  1. 59
      src/Squidex.Core/ContentExtensions.cs
  2. 32
      src/Squidex.Core/Schemas/AssetsField.cs
  3. 21
      src/Squidex.Core/Schemas/IReferenceField.cs
  4. 25
      src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
  5. 37
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
  6. 56
      src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  7. 11
      src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs
  8. 2
      src/Squidex.Read/Contents/Repositories/IContentRepository.cs
  9. 2
      src/Squidex.Read/Users/UserManagerExtensions.cs
  10. 8
      src/Squidex/Controllers/Api/Assets/AssetsController.cs
  11. 8
      src/Squidex/Controllers/Api/Users/UserManagementController.cs
  12. 10
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  13. 4
      src/Squidex/app/features/content/pages/contents/content-item.component.ts

59
src/Squidex.Core/ContentExtensions.cs

@ -6,12 +6,16 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.Core
{
public static class ContentExtensions
@ -48,5 +52,60 @@ namespace Squidex.Core
errors.Add(error);
}
}
public static IEnumerable<Guid> GetReferencedIds(this ContentData data, Schema schema)
{
var foundReferences = new HashSet<Guid>();
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldData = data.GetOrDefault(field.Id.ToString());
if (fieldData == null)
{
continue;
}
foreach (var partitionValue in fieldData.Where(x => x.Value != null))
{
var ids = referenceField.GetReferencedIds(partitionValue.Value);
foreach (var id in ids.Where(x => foundReferences.Add(x)))
{
yield return id;
}
}
}
}
}
public static ContentData ToCleanedReferences(this ContentData data, Schema schema, ISet<Guid> deletedReferencedIds)
{
var result = new ContentData(data);
foreach (var field in schema.Fields)
{
if (field is IReferenceField referenceField)
{
var fieldData = data.GetOrDefault(field.Id.ToString());
if (fieldData == null)
{
continue;
}
foreach (var partitionValue in fieldData.Where(x => x.Value != null).ToList())
{
var newValue = referenceField.RemoveDeletedReferences(partitionValue.Value, deletedReferencedIds);
fieldData[partitionValue.Key] = newValue;
}
}
}
return result;
}
}
}

32
src/Squidex.Core/Schemas/AssetsField.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq;
using NJsonSchema;
@ -15,8 +16,9 @@ using Squidex.Core.Schemas.Validators;
namespace Squidex.Core.Schemas
{
public sealed class AssetsField : Field<AssetsFieldProperties>
public sealed class AssetsField : Field<AssetsFieldProperties>, IReferenceField
{
private static readonly Guid[] EmptyIds = new Guid[0];
private readonly IAssetTester assetTester;
public AssetsField(long id, string name, Partitioning partitioning, IAssetTester assetTester)
@ -35,6 +37,34 @@ namespace Squidex.Core.Schemas
yield return new AssetsValidator(assetTester, Properties.IsRequired);
}
public IEnumerable<Guid> GetReferencedIds(JToken value)
{
Guid[] assetIds;
try
{
assetIds = value?.ToObject<Guid[]>() ?? EmptyIds;
}
catch
{
assetIds = EmptyIds;
}
return assetIds;
}
public JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds)
{
if (value == null)
{
return null;
}
var oldAssetIds = GetReferencedIds(value).ToArray();
var newAssetIds = oldAssetIds.Where(x => !deletedReferencedIds.Contains(x)).ToList();
return newAssetIds.Count != oldAssetIds.Length ? JToken.FromObject(newAssetIds) : value;
}
public override object ConvertValue(JToken value)
{
return new AssetsValue(value.ToObject<Guid[]>());

21
src/Squidex.Core/Schemas/IReferenceField.cs

@ -0,0 +1,21 @@
// ==========================================================================
// IReferenceField.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace Squidex.Core.Schemas
{
public interface IReferenceField
{
IEnumerable<Guid> GetReferencedIds(JToken value);
JToken RemoveDeletedReferences(JToken value, ISet<Guid> deletedReferencedIds);
}
}

25
src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs

@ -7,12 +7,14 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Core;
using Squidex.Core.Contents;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
@ -47,6 +49,10 @@ namespace Squidex.Read.MongoDb.Contents
[BsonElement]
public Guid AppId { get; set; }
[BsonRequired]
[BsonElement]
public Guid SchemaId { get; set; }
[BsonRequired]
[BsonElement]
public RefToken CreatedBy { get; set; }
@ -59,6 +65,14 @@ namespace Squidex.Read.MongoDb.Contents
[BsonElement]
public BsonDocument Data { get; set; }
[BsonRequired]
[BsonElement]
public List<Guid> ReferencedIds { get; set; } = new List<Guid>();
[BsonRequired]
[BsonElement]
public List<Guid> ReferencedIdsDeleted { get; set; } = new List<Guid>();
ContentData IContentEntity.Data
{
get
@ -73,7 +87,9 @@ namespace Squidex.Read.MongoDb.Contents
{
var jsonString = Data.ToJson(Settings);
contentData = JsonConvert.DeserializeObject<ContentData>(jsonString).ToNameModel(schema, true);
contentData = JsonConvert.DeserializeObject<ContentData>(jsonString);
contentData = contentData.ToCleanedReferences(schema, new HashSet<Guid>(ReferencedIdsDeleted));
contentData = contentData.ToNameModel(schema, true);
}
else
{
@ -83,9 +99,11 @@ namespace Squidex.Read.MongoDb.Contents
public void SetData(Schema schema, ContentData newContentData)
{
newContentData = newContentData?.ToIdModel(schema, true);
if (newContentData != null)
{
var jsonString = JsonConvert.SerializeObject(newContentData.ToIdModel(schema, true));
var jsonString = JsonConvert.SerializeObject(newContentData);
Data = BsonDocument.Parse(jsonString);
}
@ -94,6 +112,8 @@ namespace Squidex.Read.MongoDb.Contents
Data = null;
}
ReferencedIds = newContentData?.GetReferencedIds(schema).ToList() ?? new List<Guid>();
Text = ExtractText(newContentData);
}
@ -103,6 +123,7 @@ namespace Squidex.Read.MongoDb.Contents
{
return string.Empty;
}
var stringBuilder = new StringBuilder();
foreach (var text in data.Values.SelectMany(x => x.Values).Where(x => x != null).OfType<JValue>())

37
src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs

@ -31,6 +31,22 @@ namespace Squidex.Read.MongoDb.Contents
private readonly ISchemaProvider schemas;
private readonly EdmModelBuilder modelBuilder;
protected static FilterDefinitionBuilder<MongoContentEntity> Filter
{
get
{
return Builders<MongoContentEntity>.Filter;
}
}
protected static UpdateDefinitionBuilder<MongoContentEntity> Update
{
get
{
return Builders<MongoContentEntity>.Update;
}
}
protected static IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys
{
get
@ -54,7 +70,7 @@ namespace Squidex.Read.MongoDb.Contents
{
List<IContentEntity> result = null;
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
@ -63,7 +79,12 @@ namespace Squidex.Read.MongoDb.Contents
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schemaEntity.Schema);
cursor =
collection
.Find(parser, schemaEntity.Id, schemaEntity.Schema, nonPublished)
.Take(parser)
.Skip(parser)
.Sort(parser, schemaEntity.Schema);
}
catch (NotSupportedException)
{
@ -95,7 +116,7 @@ namespace Squidex.Read.MongoDb.Contents
{
var result = 0L;
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{
IFindFluent<MongoContentEntity, MongoContentEntity> cursor;
try
@ -104,7 +125,7 @@ namespace Squidex.Read.MongoDb.Contents
var parser = model.ParseQuery(odataQuery);
cursor = collection.Find(parser, schemaEntity.Schema, nonPublished);
cursor = collection.Find(parser, schemaEntity.Id, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
@ -125,11 +146,11 @@ namespace Squidex.Read.MongoDb.Contents
return result;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity)
{
MongoContentEntity result = null;
await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
await ForSchemaAsync(appEntity.Id, schemaId, async (collection, schemaEntity) =>
{
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
@ -139,9 +160,9 @@ namespace Squidex.Read.MongoDb.Contents
return result;
}
private async Task ForSchemaAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)
private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)
{
var collection = GetCollection(schemaId);
var collection = GetCollection(appId);
var schemaEntity = await schemas.FindSchemaByIdAsync(schemaId, true);

56
src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -10,6 +10,7 @@ using System;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Events.Assets;
using Squidex.Events.Contents;
using Squidex.Events.Schemas;
using Squidex.Infrastructure.CQRS.Events;
@ -24,14 +25,6 @@ namespace Squidex.Read.MongoDb.Contents
{
public partial class MongoContentRepository
{
private static UpdateDefinitionBuilder<MongoContentEntity> Update
{
get
{
return Builders<MongoContentEntity>.Update;
}
}
public string Name
{
get { return GetType().Name; }
@ -39,7 +32,7 @@ namespace Squidex.Read.MongoDb.Contents
public string EventsFilter
{
get { return "^(content-)|(schema-)"; }
get { return "^(content-)|(schema-)|(asset-)"; }
}
public async Task ClearAsync()
@ -68,8 +61,9 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, async collection =>
return ForAppIdAsync(@event.AppId.Id, async collection =>
{
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished));
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.Text));
});
@ -77,10 +71,12 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.CreateAsync(@event, headers, x =>
{
x.SchemaId = @event.SchemaId.Id;
SimpleMapper.Map(@event, x);
x.SetData(schemaEntity.Schema, @event.Data);
@ -90,7 +86,7 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.UpdateAsync(@event, headers, x =>
{
@ -101,7 +97,7 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentPublished @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
return ForAppIdAsync(@event.AppId.Id, collection =>
{
return collection.UpdateAsync(@event, headers, x =>
{
@ -112,7 +108,7 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentUnpublished @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
return ForAppIdAsync(@event.AppId.Id, collection =>
{
return collection.UpdateAsync(@event, headers, x =>
{
@ -121,32 +117,42 @@ namespace Squidex.Read.MongoDb.Contents
});
}
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
protected Task On(AssetDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
return ForAppIdAsync(@event.AppId.Id, collection =>
{
return collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
return collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, @event.AssetId),
Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.AssetId)),
Update.AddToSet(x => x.ReferencedIdsDeleted, @event.AssetId));
});
}
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
return ForAppIdAsync(@event.SchemaId.Id, async collection =>
{
return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}")));
await collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, @event.ContentId),
Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.ContentId)),
Update.AddToSet(x => x.ReferencedIdsDeleted, @event.ContentId));
await collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
});
}
private Task ForSchemaIdAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Task> action)
private Task ForAppIdAsync(Guid appId, Func<IMongoCollection<MongoContentEntity>, Task> action)
{
var collection = GetCollection(schemaId);
var collection = GetCollection(appId);
return action(collection);
}
private IMongoCollection<MongoContentEntity> GetCollection(Guid schemaId)
private IMongoCollection<MongoContentEntity> GetCollection(Guid appId)
{
var name = $"{Prefix}{schemaId}";
var name = $"{Prefix}{appId}";
return database.GetCollection<MongoContentEntity>(name);
}

11
src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs

@ -59,16 +59,19 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
return cursor;
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Schema schema, bool nonPublished)
public static IFindFluent<MongoContentEntity, MongoContentEntity> Find(this IMongoCollection<MongoContentEntity> cursor, ODataUriParser query, Guid schemaId, Schema schema, bool nonPublished)
{
var filter = BuildQuery(query, schema, nonPublished);
var filter = BuildQuery(query, schemaId, schema, nonPublished);
return cursor.Find(filter);
}
public static FilterDefinition<MongoContentEntity> BuildQuery(ODataUriParser query, Schema schema, bool nonPublished)
public static FilterDefinition<MongoContentEntity> BuildQuery(ODataUriParser query, Guid schemaId, Schema schema, bool nonPublished)
{
var filters = new List<FilterDefinition<MongoContentEntity>>();
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.SchemaId, schemaId)
};
if (!nonPublished)
{

2
src/Squidex.Read/Contents/Repositories/IContentRepository.cs

@ -19,6 +19,6 @@ namespace Squidex.Read.Contents.Repositories
Task<long> CountAsync(Guid schemaId, bool nonPublished, string odataQuery, IAppEntity appEntity);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity);
}
}

2
src/Squidex.Read/Users/UserManagerExtensions.cs

@ -28,7 +28,7 @@ namespace Squidex.Read.Users
return Task.FromResult<IReadOnlyList<IUser>>(users);
}
public static Task<long> CountAsync(this UserManager<IUser> userManager, string email = null)
public static Task<long> CountByEmailAsync(this UserManager<IUser> userManager, string email = null)
{
var count = QueryUsers(userManager, email).LongCount();

8
src/Squidex/Controllers/Api/Assets/AssetsController.cs

@ -100,15 +100,15 @@ namespace Squidex.Controllers.Api.Assets
}
}
var taskForAssets = assetRepository.QueryAsync(AppId, mimeTypeList, idsList, query, take, skip);
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, idsList, query);
var taskForItems = assetRepository.QueryAsync(AppId, mimeTypeList, idsList, query, take, skip);
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, idsList, query);
await Task.WhenAll(taskForAssets, taskForCount);
await Task.WhenAll(taskForItems, taskForCount);
var response = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForAssets.Result.Select(x => SimpleMapper.Map(x, new AssetDto())).ToArray()
Items = taskForItems.Result.Select(x => SimpleMapper.Map(x, new AssetDto())).ToArray()
};
return Ok(response);

8
src/Squidex/Controllers/Api/Users/UserManagementController.cs

@ -40,15 +40,15 @@ namespace Squidex.Controllers.Api.Users
[ApiCosts(0)]
public async Task<IActionResult> GetUsers([FromQuery] string query = null, [FromQuery] int skip = 0, [FromQuery] int take = 10)
{
var taskForUsers = userManager.QueryByEmailAsync(query, take, skip);
var taskForCount = userManager.CountAsync(query);
var taskForItems = userManager.QueryByEmailAsync(query, take, skip);
var taskForCount = userManager.CountByEmailAsync(query);
await Task.WhenAll(taskForUsers, taskForCount);
await Task.WhenAll(taskForItems, taskForCount);
var response = new UsersDto
{
Total = taskForCount.Result,
Items = taskForUsers.Result.Select(x => SimpleMapper.Map(x, new UserDto { DisplayName = x.DisplayName(), PictureUrl = x.PictureUrl() })).ToArray()
Items = taskForItems.Result.Select(x => SimpleMapper.Map(x, new UserDto { DisplayName = x.DisplayName(), PictureUrl = x.PictureUrl() })).ToArray()
};
return Ok(response);

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

@ -54,15 +54,15 @@ namespace Squidex.Controllers.ContentApi
var query = Request.QueryString.ToString();
var taskForContents = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, query, App);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query, App);
var taskForItems = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, query, App);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query, App);
await Task.WhenAll(taskForContents, taskForCount);
await Task.WhenAll(taskForItems, taskForCount);
var response = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForContents.Result.Take(200).Select(x =>
Items = taskForItems.Result.Take(200).Select(x =>
{
var itemModel = SimpleMapper.Map(x, new ContentDto());
@ -90,7 +90,7 @@ namespace Squidex.Controllers.ContentApi
return NotFound();
}
var entity = await contentRepository.FindContentAsync(schemaEntity.Id, id);
var entity = await contentRepository.FindContentAsync(schemaEntity.Id, id, App);
if (entity == null)
{

4
src/Squidex/app/features/content/pages/contents/content-item.component.ts

@ -100,9 +100,9 @@ export class ContentItemComponent extends AppComponentBase implements OnInit, On
value = value ? '✔' : '-';
}else if (properties.fieldType === 'Assets') {
try {
value = `${value.length} Assets`;
value = `${value.length} Asset(s)`;
} catch (ex) {
value = '0 Assets';
value = '0 Asset(s)';
}
} else if (properties.fieldType === 'DateTime') {
try {

Loading…
Cancel
Save