mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
409 changed files with 4694 additions and 5421 deletions
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// MongoAppEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Entities.Apps.State; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps |
|||
{ |
|||
public sealed class MongoAppEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public int Version { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public string Name { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public string[] UserIds { get; set; } |
|||
|
|||
[BsonJson] |
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public AppState State { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
// ==========================================================================
|
|||
// MongoAppRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Entities.Apps.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Apps.State; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps |
|||
{ |
|||
public sealed class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, ISnapshotStore<AppState, Guid> |
|||
{ |
|||
public MongoAppRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "States_Apps"; |
|||
} |
|||
|
|||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection) |
|||
{ |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.UserIds)); |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); |
|||
} |
|||
|
|||
public async Task<Guid> FindAppIdByNameAsync(string name) |
|||
{ |
|||
var appEntity = |
|||
await Collection.Find(x => x.Name == name).Only(x => x.Id) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
return appEntity != null ? Guid.Parse(appEntity["_id"].AsString) : Guid.Empty; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<Guid>> QueryUserAppIdsAsync(string userId) |
|||
{ |
|||
var appEntities = |
|||
await Collection.Find(x => x.UserIds.Contains(userId)).Only(x => x.Id) |
|||
.ToListAsync(); |
|||
|
|||
return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); |
|||
} |
|||
|
|||
public async Task<(AppState Value, long Version)> ReadAsync(Guid key) |
|||
{ |
|||
var existing = |
|||
await Collection.Find(x => x.Id == key) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
return (existing.State, existing.Version); |
|||
} |
|||
|
|||
return (null, EtagVersion.NotFound); |
|||
} |
|||
|
|||
public async Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion) |
|||
{ |
|||
try |
|||
{ |
|||
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, |
|||
Update |
|||
.Set(x => x.UserIds, value.Contributors.Keys.ToArray()) |
|||
.Set(x => x.Name, value.Name) |
|||
.Set(x => x.State, value) |
|||
.Set(x => x.Version, newVersion), |
|||
Upsert); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
var existingVersion = |
|||
await Collection.Find(x => x.Id == key) |
|||
.Project<MongoAppEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync(); |
|||
|
|||
if (existingVersion != null) |
|||
{ |
|||
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// MongoAssetEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Entities.Assets.State; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|||
{ |
|||
public sealed class MongoAssetEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public AssetState State { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public int Version { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
// ==========================================================================
|
|||
// MongoContentRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.OData.UriParser; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.ConvertContent; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents; |
|||
using Squidex.Domain.Apps.Entities.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Contents.State; |
|||
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
|||
{ |
|||
public partial class MongoContentRepository : MongoRepositoryBase<MongoContentEntity>, |
|||
IEventConsumer, |
|||
IContentRepository, |
|||
ISnapshotStore<ContentState, Guid> |
|||
{ |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) |
|||
: base(database) |
|||
{ |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
|
|||
this.appProvider = appProvider; |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "States_Contents"; |
|||
} |
|||
|
|||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection) |
|||
{ |
|||
await collection.Indexes.CreateOneAsync( |
|||
Index |
|||
.Ascending(x => x.Id) |
|||
.Ascending(x => x.Version)); |
|||
|
|||
await collection.Indexes.CreateOneAsync( |
|||
Index |
|||
.Ascending(x => x.Id) |
|||
.Descending(x => x.Version)); |
|||
|
|||
await collection.Indexes.CreateOneAsync( |
|||
Index |
|||
.Ascending(x => x.SchemaId) |
|||
.Descending(x => x.IsLatest) |
|||
.Descending(x => x.LastModified)); |
|||
|
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds)); |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status)); |
|||
await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText)); |
|||
} |
|||
|
|||
public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion) |
|||
{ |
|||
var documentId = $"{key}_{newVersion}"; |
|||
|
|||
var schema = await appProvider.GetSchemaAsync(value.AppId, value.SchemaId, true); |
|||
|
|||
if (schema == null) |
|||
{ |
|||
throw new InvalidOperationException($"Cannot find schema {value.SchemaId}"); |
|||
} |
|||
|
|||
var idData = value.Data?.ToIdModel(schema.SchemaDef, true); |
|||
|
|||
var document = SimpleMapper.Map(value, new MongoContentEntity |
|||
{ |
|||
DocumentId = documentId, |
|||
DataText = idData?.ToFullText(), |
|||
DataByIds = idData, |
|||
IsLatest = true, |
|||
ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef), |
|||
}); |
|||
|
|||
try |
|||
{ |
|||
await Collection.InsertOneAsync(document); |
|||
await Collection.UpdateManyAsync(x => x.Id == value.Id && x.Version < value.Version, Update.Set(x => x.IsLatest, false)); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
var existingVersion = |
|||
await Collection.Find(x => x.Id == value.Id && x.IsLatest).Only(x => x.Id, x => x.Version) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existingVersion != null) |
|||
{ |
|||
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key) |
|||
{ |
|||
var contentEntity = |
|||
await Collection.Find(x => x.Id == key && x.IsLatest) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (contentEntity != null) |
|||
{ |
|||
var schema = await appProvider.GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId, true); |
|||
|
|||
if (schema == null) |
|||
{ |
|||
throw new InvalidOperationException($"Cannot find schema {contentEntity.SchemaId}"); |
|||
} |
|||
|
|||
contentEntity?.ParseData(schema.SchemaDef); |
|||
|
|||
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); |
|||
} |
|||
|
|||
return (null, EtagVersion.NotFound); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) |
|||
{ |
|||
IFindFluent<MongoContentEntity, MongoContentEntity> cursor; |
|||
try |
|||
{ |
|||
cursor = |
|||
Collection |
|||
.Find(odataQuery, schema.Id, schema.SchemaDef, status) |
|||
.Take(odataQuery) |
|||
.Skip(odataQuery) |
|||
.Sort(odataQuery, schema.SchemaDef); |
|||
} |
|||
catch (NotSupportedException) |
|||
{ |
|||
throw new ValidationException("This odata operation is not supported."); |
|||
} |
|||
catch (NotImplementedException) |
|||
{ |
|||
throw new ValidationException("This odata operation is not supported."); |
|||
} |
|||
|
|||
var contentEntities = await cursor.ToListAsync(); |
|||
|
|||
foreach (var entity in contentEntities) |
|||
{ |
|||
entity.ParseData(schema.SchemaDef); |
|||
} |
|||
|
|||
return contentEntities; |
|||
} |
|||
|
|||
public async Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) |
|||
{ |
|||
IFindFluent<MongoContentEntity, MongoContentEntity> cursor; |
|||
try |
|||
{ |
|||
cursor = Collection.Find(odataQuery, schema.Id, schema.SchemaDef, status); |
|||
} |
|||
catch (NotSupportedException) |
|||
{ |
|||
throw new ValidationException("This odata operation is not supported."); |
|||
} |
|||
catch (NotImplementedException) |
|||
{ |
|||
throw new ValidationException("This odata operation is not supported."); |
|||
} |
|||
|
|||
return await cursor.CountAsync(); |
|||
} |
|||
|
|||
public async Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) |
|||
{ |
|||
var contentsCount = |
|||
await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest) |
|||
.CountAsync(); |
|||
|
|||
return contentsCount; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids) |
|||
{ |
|||
var contentEntities = |
|||
await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest) |
|||
.ToListAsync(); |
|||
|
|||
foreach (var entity in contentEntities) |
|||
{ |
|||
entity.ParseData(schema.SchemaDef); |
|||
} |
|||
|
|||
return contentEntities.OfType<IContentEntity>().ToList(); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds) |
|||
{ |
|||
var contentEntities = |
|||
await Collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Only(x => x.Id) |
|||
.ToListAsync(); |
|||
|
|||
return contentIds.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList(); |
|||
} |
|||
|
|||
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version) |
|||
{ |
|||
var contentEntity = |
|||
await Collection.Find(x => x.Id == id && x.Version >= version).SortBy(x => x.Version) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
contentEntity?.ParseData(schema.SchemaDef); |
|||
|
|||
return contentEntity; |
|||
} |
|||
|
|||
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) |
|||
{ |
|||
var contentEntity = |
|||
await Collection.Find(x => x.Id == id && x.IsLatest) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
contentEntity?.ParseData(schema.SchemaDef); |
|||
|
|||
return contentEntity; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// ==========================================================================
|
|||
// MongoContentRepository_EventHandling.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Domain.Apps.Events.Contents; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents |
|||
{ |
|||
public partial class MongoContentRepository |
|||
{ |
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "^(content-)|(asset-)"; } |
|||
} |
|||
|
|||
public override Task ClearAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public Task On(Envelope<IEvent> @event) |
|||
{ |
|||
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
|||
} |
|||
|
|||
protected Task On(AssetDeleted @event) |
|||
{ |
|||
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(ContentDeleted @event) |
|||
{ |
|||
return 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)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// MongoRuleEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Entities.Rules.State; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules |
|||
{ |
|||
public sealed class MongoRuleEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public int Version { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public bool IsDeleted { get; set; } |
|||
|
|||
[BsonJson] |
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public RuleState State { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// ==========================================================================
|
|||
// MongoRuleRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Rules.State; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules |
|||
{ |
|||
public sealed class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, ISnapshotStore<RuleState, Guid> |
|||
{ |
|||
public MongoRuleRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "States_Rules"; |
|||
} |
|||
|
|||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection) |
|||
{ |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)); |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsDeleted)); |
|||
} |
|||
|
|||
public async Task<(RuleState Value, long Version)> ReadAsync(Guid key) |
|||
{ |
|||
var existing = |
|||
await Collection.Find(x => x.Id == key) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
return (existing.State, existing.Version); |
|||
} |
|||
|
|||
return (null, EtagVersion.NotFound); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId) |
|||
{ |
|||
var ruleEntities = |
|||
await Collection.Find(x => x.AppId == appId && !x.IsDeleted).Only(x => x.Id) |
|||
.ToListAsync(); |
|||
|
|||
return ruleEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); |
|||
} |
|||
|
|||
public async Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion) |
|||
{ |
|||
try |
|||
{ |
|||
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, |
|||
Update |
|||
.Set(x => x.State, value) |
|||
.Set(x => x.AppId, value.AppId) |
|||
.Set(x => x.IsDeleted, value.IsDeleted) |
|||
.Set(x => x.Version, newVersion), |
|||
Upsert); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
var existingVersion = |
|||
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existingVersion != null) |
|||
{ |
|||
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Entities.Schemas.State; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas |
|||
{ |
|||
public sealed class MongoSchemaEntity |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public Guid Id { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public string Name { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public int Version { get; set; } |
|||
|
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[BsonJson] |
|||
[BsonElement] |
|||
[BsonRequired] |
|||
public SchemaState State { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Schemas.State; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas |
|||
{ |
|||
public sealed class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ISnapshotStore<SchemaState, Guid> |
|||
{ |
|||
public MongoSchemaRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "States_Schemas"; |
|||
} |
|||
|
|||
protected override async Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection) |
|||
{ |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)); |
|||
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); |
|||
} |
|||
|
|||
public async Task<(SchemaState Value, long Version)> ReadAsync(Guid key) |
|||
{ |
|||
var existing = |
|||
await Collection.Find(x => x.Id == key) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existing != null) |
|||
{ |
|||
return (existing.State, existing.Version); |
|||
} |
|||
|
|||
return (null, EtagVersion.NotFound); |
|||
} |
|||
|
|||
public async Task<Guid> FindSchemaIdAsync(Guid appId, string name) |
|||
{ |
|||
var schemaEntity = |
|||
await Collection.Find(x => x.Name == name).Only(x => x.Id) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
return schemaEntity != null ? Guid.Parse(schemaEntity["_id"].AsString) : Guid.Empty; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId) |
|||
{ |
|||
var schemaEntities = |
|||
await Collection.Find(x => x.AppId == appId).Only(x => x.Id) |
|||
.ToListAsync(); |
|||
|
|||
return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); |
|||
} |
|||
|
|||
public async Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion) |
|||
{ |
|||
try |
|||
{ |
|||
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, |
|||
Update |
|||
.Set(x => x.State, value) |
|||
.Set(x => x.AppId, value.AppId) |
|||
.Set(x => x.Name, value.Name) |
|||
.Set(x => x.Version, newVersion), |
|||
Upsert); |
|||
} |
|||
catch (MongoWriteException ex) |
|||
{ |
|||
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
var existingVersion = |
|||
await Collection.Find(x => x.Id == key).Only(x => x.Version) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (existingVersion != null) |
|||
{ |
|||
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// ==========================================================================
|
|||
// AppProvider.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Apps.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Rules; |
|||
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Domain.Apps.Entities.Schemas.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public sealed class AppProvider : IAppProvider |
|||
{ |
|||
private readonly IAppRepository appRepository; |
|||
private readonly IRuleRepository ruleRepository; |
|||
private readonly ISchemaRepository schemaRepository; |
|||
private readonly IStateFactory stateFactory; |
|||
|
|||
public AppProvider( |
|||
IAppRepository appRepository, |
|||
ISchemaRepository schemaRepository, |
|||
IStateFactory stateFactory, |
|||
IRuleRepository ruleRepository) |
|||
{ |
|||
Guard.NotNull(appRepository, nameof(appRepository)); |
|||
Guard.NotNull(schemaRepository, nameof(schemaRepository)); |
|||
Guard.NotNull(stateFactory, nameof(stateFactory)); |
|||
Guard.NotNull(ruleRepository, nameof(ruleRepository)); |
|||
|
|||
this.appRepository = appRepository; |
|||
this.schemaRepository = schemaRepository; |
|||
this.stateFactory = stateFactory; |
|||
this.ruleRepository = ruleRepository; |
|||
} |
|||
|
|||
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id) |
|||
{ |
|||
var app = await stateFactory.GetSingleAsync<AppDomainObject>(appId); |
|||
|
|||
if (IsNotFound(app)) |
|||
{ |
|||
return (null, null); |
|||
} |
|||
|
|||
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id); |
|||
|
|||
return IsNotFound(false, schema) ? (null, null) : (app.State, schema.State); |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string appName) |
|||
{ |
|||
var appId = await GetAppIdAsync(appName); |
|||
|
|||
if (appId == Guid.Empty) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var app = await stateFactory.GetSingleAsync<AppDomainObject>(appId); |
|||
|
|||
return IsNotFound(app) ? null : app.State; |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, string name, bool provideDeleted = false) |
|||
{ |
|||
var schemaId = await GetSchemaIdAsync(appId, name); |
|||
|
|||
if (schemaId == Guid.Empty) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(schemaId); |
|||
|
|||
return IsNotFound(provideDeleted, schema) ? null : schema.State; |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false) |
|||
{ |
|||
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id); |
|||
|
|||
return IsNotFound(provideDeleted, schema) ? null : schema.State; |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId) |
|||
{ |
|||
var ids = await schemaRepository.QuerySchemaIdsAsync(appId); |
|||
|
|||
var schemas = |
|||
await Task.WhenAll( |
|||
ids.Select(id => stateFactory.GetSingleAsync<SchemaDomainObject>(id))); |
|||
|
|||
return schemas.Select(a => (ISchemaEntity)a.State).ToList(); |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId) |
|||
{ |
|||
var ids = await ruleRepository.QueryRuleIdsAsync(appId); |
|||
|
|||
var rules = |
|||
await Task.WhenAll( |
|||
ids.Select(id => stateFactory.GetSingleAsync<RuleDomainObject>(id))); |
|||
|
|||
return rules.Select(a => (IRuleEntity)a.State).ToList(); |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetUserApps(string userId) |
|||
{ |
|||
var ids = await appRepository.QueryUserAppIdsAsync(userId); |
|||
|
|||
var apps = |
|||
await Task.WhenAll( |
|||
ids.Select(id => stateFactory.GetSingleAsync<AppDomainObject>(id))); |
|||
|
|||
return apps.Select(a => (IAppEntity)a.State).ToList(); |
|||
} |
|||
|
|||
private Task<Guid> GetAppIdAsync(string name) |
|||
{ |
|||
return appRepository.FindAppIdByNameAsync(name); |
|||
} |
|||
|
|||
private Task<Guid> GetSchemaIdAsync(Guid appId, string name) |
|||
{ |
|||
return schemaRepository.FindSchemaIdAsync(appId, name); |
|||
} |
|||
|
|||
private static bool IsNotFound(AppDomainObject app) |
|||
{ |
|||
return app.Version < 0; |
|||
} |
|||
|
|||
private static bool IsNotFound(bool provideDeleted, SchemaDomainObject schema) |
|||
{ |
|||
return schema.Version < 0 || (schema.State.IsDeleted && !provideDeleted); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// IAppRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Repositories |
|||
{ |
|||
public interface IAppRepository |
|||
{ |
|||
Task<Guid> FindAppIdByNameAsync(string name); |
|||
|
|||
Task<IReadOnlyList<Guid>> QueryUserAppIdsAsync(string userId); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
// ==========================================================================
|
|||
// AppState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.State |
|||
{ |
|||
public class AppState : DomainObjectState<AppState>, |
|||
IAppEntity |
|||
{ |
|||
private static readonly LanguagesConfig English = LanguagesConfig.Build(Language.EN); |
|||
|
|||
[JsonProperty] |
|||
public string Name { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public AppPlan Plan { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public AppClients Clients { get; set; } = AppClients.Empty; |
|||
|
|||
[JsonProperty] |
|||
public AppContributors Contributors { get; set; } = AppContributors.Empty; |
|||
|
|||
[JsonProperty] |
|||
public LanguagesConfig LanguagesConfig { get; set; } = English; |
|||
|
|||
protected void On(AppCreated @event) |
|||
{ |
|||
SimpleMapper.Map(@event, this); |
|||
} |
|||
|
|||
protected void On(AppPlanChanged @event) |
|||
{ |
|||
Plan = @event.PlanId == null ? null : new AppPlan(@event.Actor, @event.PlanId); |
|||
} |
|||
|
|||
protected void On(AppContributorAssigned @event) |
|||
{ |
|||
Contributors = Contributors.Assign(@event.ContributorId, @event.Permission); |
|||
} |
|||
|
|||
protected void On(AppContributorRemoved @event) |
|||
{ |
|||
Contributors = Contributors.Remove(@event.ContributorId); |
|||
} |
|||
|
|||
protected void On(AppClientAttached @event) |
|||
{ |
|||
Clients = Clients.Add(@event.Id, @event.Secret); |
|||
} |
|||
|
|||
protected void On(AppClientUpdated @event) |
|||
{ |
|||
Clients = Clients.Update(@event.Id, @event.Permission); |
|||
} |
|||
|
|||
protected void On(AppClientRenamed @event) |
|||
{ |
|||
Clients = Clients.Rename(@event.Id, @event.Name); |
|||
} |
|||
|
|||
protected void On(AppClientRevoked @event) |
|||
{ |
|||
Clients = Clients.Revoke(@event.Id); |
|||
} |
|||
|
|||
protected void On(AppLanguageAdded @event) |
|||
{ |
|||
LanguagesConfig = LanguagesConfig.Set(new LanguageConfig(@event.Language)); |
|||
} |
|||
|
|||
protected void On(AppLanguageRemoved @event) |
|||
{ |
|||
LanguagesConfig = LanguagesConfig.Remove(@event.Language); |
|||
} |
|||
|
|||
protected void On(AppLanguageUpdated @event) |
|||
{ |
|||
LanguagesConfig = LanguagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, @event.Fallback)); |
|||
|
|||
if (@event.IsMaster) |
|||
{ |
|||
LanguagesConfig = LanguagesConfig.MakeMaster(@event.Language); |
|||
} |
|||
} |
|||
|
|||
public AppState Apply(Envelope<IEvent> @event) |
|||
{ |
|||
var payload = (SquidexEvent)@event.Payload; |
|||
|
|||
return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// AssetState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.ValidateContent; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Assets; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets.State |
|||
{ |
|||
public class AssetState : DomainObjectState<AssetState>, |
|||
IAssetEntity, |
|||
IAssetInfo, |
|||
IUpdateableEntityWithAppRef |
|||
{ |
|||
[JsonProperty] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string FileName { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string MimeType { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public long FileVersion { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public long FileSize { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public long TotalSize { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public bool IsImage { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public int? PixelWidth { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public int? PixelHeight { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public bool IsDeleted { get; set; } |
|||
|
|||
Guid IAssetInfo.AssetId |
|||
{ |
|||
get { return Id; } |
|||
} |
|||
|
|||
protected void On(AssetCreated @event) |
|||
{ |
|||
SimpleMapper.Map(@event, this); |
|||
|
|||
TotalSize += @event.FileSize; |
|||
} |
|||
|
|||
protected void On(AssetUpdated @event) |
|||
{ |
|||
SimpleMapper.Map(@event, this); |
|||
|
|||
TotalSize += @event.FileSize; |
|||
} |
|||
|
|||
protected void On(AssetRenamed @event) |
|||
{ |
|||
FileName = @event.FileName; |
|||
} |
|||
|
|||
protected void On(AssetDeleted @event) |
|||
{ |
|||
IsDeleted = true; |
|||
} |
|||
|
|||
public AssetState Apply(Envelope<IEvent> @event) |
|||
{ |
|||
var payload = (SquidexEvent)@event.Payload; |
|||
|
|||
return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue