// ========================================================================== // MongoAssetRepository.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.Bson; using MongoDB.Driver; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { public sealed class MongoAssetRepository : MongoRepositoryBase, IAssetRepository, ISnapshotStore { public MongoAssetRepository(IMongoDatabase database) : base(database) { } protected override string CollectionName() { return "States_Assets"; } protected override Task SetupCollectionAsync(IMongoCollection collection) { return collection.Indexes.CreateOneAsync( Index .Ascending(x => x.State.AppId) .Ascending(x => x.State.IsDeleted) .Ascending(x => x.State.FileName) .Ascending(x => x.State.MimeType) .Descending(x => x.State.LastModified)); } public async Task<(AssetState 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> QueryAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null, int take = 10, int skip = 0) { var filter = CreateFilter(appId, mimeTypes, ids, query); var assetEntities = await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.State.LastModified) .ToListAsync(); return assetEntities.Select(x => x.State).ToList(); } public async Task CountAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null) { var filter = CreateFilter(appId, mimeTypes, ids, query); var assetsCount = await Collection.Find(filter) .CountAsync(); return assetsCount; } public async Task FindAssetAsync(Guid id) { var (state, etag) = await ReadAsync(id); return state; } private static FilterDefinition CreateFilter(Guid appId, ICollection mimeTypes, ICollection ids, string query) { var filters = new List> { Filter.Eq(x => x.State.AppId, appId), Filter.Eq(x => x.State.IsDeleted, false) }; if (ids != null && ids.Count > 0) { filters.Add(Filter.In(x => x.Id, ids)); } if (mimeTypes != null && mimeTypes.Count > 0) { filters.Add(Filter.In(x => x.State.MimeType, mimeTypes)); } if (!string.IsNullOrWhiteSpace(query)) { filters.Add(Filter.Regex(x => x.State.FileName, new BsonRegularExpression(query, "i"))); } var filter = Filter.And(filters); return filter; } public async Task WriteAsync(Guid key, AssetState 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.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; } } } } }