diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 162f433ce..7033c0e06 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -60,7 +60,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets Index .Ascending(x => x.IndexedAppId) .Ascending(x => x.IsDeleted) - .Ascending(x => x.FileHash)) + .Ascending(x => x.FileHash) + .Ascending(x => x.FileName) + .Ascending(x => x.FileSize)) }, ct); } @@ -129,15 +131,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets } } - public async Task> QueryByHashAsync(DomainId appId, string hash) + public async Task FindAssetAsync(DomainId appId, string hash, string fileName, long fileSize) { using (Profiler.TraceMethod()) { - var assetEntities = - await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.FileHash == hash) - .ToListAsync(); + var assetEntity = + await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.FileHash == hash && x.FileName == fileName && x.FileSize == fileSize) + .FirstOrDefaultAsync(); - return assetEntities.OfType().ToList(); + return assetEntity; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index 4d1033260..2e3e23dea 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -62,19 +62,20 @@ namespace Squidex.Domain.Apps.Entities.Assets if (!createAsset.Duplicate) { - var existings = await assetQuery.QueryByHashAsync(ctx, createAsset.AppId.Id, createAsset.FileHash); + var existing = + await assetQuery.FindByHashAsync(ctx, + createAsset.FileHash, + createAsset.File.FileName, + createAsset.File.FileSize); - foreach (var existing in existings) + if (existing != null) { - if (IsDuplicate(existing, createAsset.File)) - { - var result = new AssetCreatedResult(existing, true); + var result = new AssetCreatedResult(existing, true); - context.Complete(result); + context.Complete(result); - await next(context); - return; - } + await next(context); + return; } } @@ -149,11 +150,6 @@ namespace Squidex.Domain.Apps.Entities.Assets return null; } - private static bool IsDuplicate(IAssetEntity asset, AssetFile file) - { - return asset?.FileName == file.FileName && asset.FileSize == file.FileSize; - } - private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, string tempFile) { using (var uploadStream = command.File.OpenRead()) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs index 659a3ca19..8a0351b1b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs @@ -13,14 +13,14 @@ namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetQueryService { - Task> QueryByHashAsync(Context context, DomainId appId, string hash); - Task> QueryAsync(Context context, DomainId? parentId, Q query); Task> QueryAssetFoldersAsync(Context context, DomainId parentId); Task> FindAssetFolderAsync(DomainId appId, DomainId id); - Task FindAssetAsync(Context context, DomainId id); + Task FindByHashAsync(Context context, string hash, string fileName, long fileSize); + + Task FindAsync(Context context, DomainId id); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs index 43ef713c5..7329ab973 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -37,8 +37,24 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries this.queryParser = queryParser; } - public async Task FindAssetAsync(Context context, DomainId id) + public async Task FindByHashAsync(Context context, string hash, string fileName, long fileSize) { + Guard.NotNull(context, nameof(context)); + + var asset = await assetRepository.FindAssetAsync(context.App.Id, hash, fileName, fileSize); + + if (asset != null) + { + return await assetEnricher.EnrichAsync(asset, context); + } + + return null; + } + + public async Task FindAsync(Context context, DomainId id) + { + Guard.NotNull(context, nameof(context)); + var asset = await assetRepository.FindAssetAsync(context.App.Id, id); if (asset != null) @@ -78,15 +94,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries return assetFolders; } - public async Task> QueryByHashAsync(Context context, DomainId appId, string hash) - { - Guard.NotNull(hash, nameof(hash)); - - var assets = await assetRepository.QueryByHashAsync(appId, hash); - - return await assetEnricher.EnrichAsync(assets, context); - } - public async Task> QueryAsync(Context context, DomainId? parentId, Q query) { Guard.NotNull(context, nameof(context)); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs index 42b2f1778..884bea4e1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs @@ -14,8 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories { public interface IAssetRepository { - Task> QueryByHashAsync(DomainId appId, string hash); - Task> QueryAsync(DomainId appId, DomainId? parentId, ClrQuery query); Task> QueryAsync(DomainId appId, HashSet ids); @@ -24,6 +22,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories Task> QueryChildIdsAsync(DomainId appId, DomainId parentId); + Task FindAssetAsync(DomainId appId, string hash, string fileName, long fileSize); + Task FindAssetAsync(DomainId appId); Task FindAssetAsync(DomainId appId, DomainId id); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 0e1777fb9..67c23f6c8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Task> QueryAsync(Context context, string schemaIdOrName, Q query); - Task FindContentAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any); + Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any); Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 3341793f4..2c793e139 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries this.queryParser = queryParser; } - public async Task FindContentAsync(Context context, string schemaIdOrName, DomainId id, long version = -1) + public async Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = -1) { Guard.NotNull(context, nameof(context)); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs index a9d66c14b..38dfd97e3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries await maxRequests.WaitAsync(); try { - asset = await assetQuery.FindAssetAsync(context, id); + asset = await assetQuery.FindAsync(context, id); } finally { @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries await maxRequests.WaitAsync(); try { - content = await contentQuery.FindContentAsync(context, schemaId.ToString(), id); + content = await contentQuery.FindAsync(context, schemaId.ToString(), id); } finally { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 4de224d11..efb825f05 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -152,7 +152,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiCosts(1)] public async Task GetAsset(string app, DomainId id) { - var asset = await assetQuery.FindAssetAsync(Context, id); + var asset = await assetQuery.FindAsync(Context, id); if (asset == null) { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index f3362ca3f..a854a7ac6 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -278,7 +278,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContent(string app, string name, DomainId id) { - var content = await contentQuery.FindContentAsync(Context, name, id); + var content = await contentQuery.FindAsync(Context, name, id); var response = ContentDto.FromContent(Context, content, Resources); @@ -305,7 +305,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContentVersion(string app, string name, DomainId id, int version) { - var content = await contentQuery.FindContentAsync(Context, name, id, version); + var content = await contentQuery.FindAsync(Context, name, id, version); var response = ContentDto.FromContent(Context, content, Resources); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index a6d24ff65..b392c8199 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -70,8 +70,8 @@ namespace Squidex.Domain.Apps.Entities.Assets A.CallTo(() => assetEnricher.EnrichAsync(A._, requestContext)) .ReturnsLazily(() => SimpleMapper.Map(asset.Snapshot, new AssetEntity())); - A.CallTo(() => assetQuery.QueryByHashAsync(A.That.Matches(x => x.ShouldEnrichAsset()), AppId, A._)) - .Returns(new List()); + A.CallTo(() => assetQuery.FindByHashAsync(A._, A._, A._, A._)) + .Returns(Task.FromResult(null)); A.CallTo(() => grainFactory.GetGrain(Id.ToString(), null)) .Returns(asset); @@ -192,21 +192,6 @@ namespace Squidex.Domain.Apps.Entities.Assets Assert.False(result.IsDuplicate); } - [Fact] - public async Task Create_should_not_return_duplicate_result_if_file_with_same_hash_but_other_name_found() - { - var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); - var context = CreateContextForCommand(command); - - SetupSameHashAsset("other-name", file.FileSize, out _); - - await sut.HandleAsync(context); - - var result = context.Result(); - - Assert.False(result.IsDuplicate); - } - [Fact] public async Task Create_should_pass_through_duplicate() { @@ -224,19 +209,6 @@ namespace Squidex.Domain.Apps.Entities.Assets result.Should().BeEquivalentTo(duplicate, x => x.ExcludingMissingMembers()); } - [Fact] - public async Task Create_should_not_return_duplicate_result_if_file_with_same_hash_but_other_size_found() - { - var command = CreateCommand(new CreateAsset { AssetId = assetId, File = file }); - var context = CreateContextForCommand(command); - - SetupSameHashAsset(file.FileName, 12345, out _); - - await sut.HandleAsync(context); - - Assert.False(context.Result().IsDuplicate); - } - [Fact] public async Task Update_should_update_domain_object() { @@ -319,8 +291,8 @@ namespace Squidex.Domain.Apps.Entities.Assets FileSize = fileSize }; - A.CallTo(() => assetQuery.QueryByHashAsync(A.That.Matches(x => !x.ShouldEnrichAsset()), A._, A._)) - .Returns(new List { duplicate }); + A.CallTo(() => assetQuery.FindByHashAsync(A.That.Matches(x => x.ShouldEnrichAsset()), A._, A._, A._)) + .Returns(duplicate); } private void AssertMetadataEnriched() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs index cf1265526..55950b630 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb [Fact] public async Task Should_query_asset_by_hash() { - var assets = await _.AssetRepository.QueryByHashAsync(_.RandomAppId(), _.RandomValue()); + var assets = await _.AssetRepository.FindAssetAsync(_.RandomAppId(), _.RandomValue(), _.RandomValue(), 123); Assert.NotNull(assets); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs index e6a502187..10f570a79 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries A.CallTo(() => assetEnricher.EnrichAsync(found, requestContext)) .Returns(enriched); - var result = await sut.FindAssetAsync(requestContext, found.Id); + var result = await sut.FindAsync(requestContext, found.Id); Assert.Same(enriched, result); } @@ -62,15 +62,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries var enriched = new AssetEntity(); - A.CallTo(() => assetRepository.QueryByHashAsync(appId.Id, "hash")) - .Returns(new List { found }); + A.CallTo(() => assetRepository.FindAssetAsync(appId.Id, "hash", "name", 123)) + .Returns(found); - A.CallTo(() => assetEnricher.EnrichAsync(A>.That.IsSameSequenceAs(found), requestContext)) - .Returns(new List { enriched }); + A.CallTo(() => assetEnricher.EnrichAsync(found, requestContext)) + .Returns(enriched); - var result = await sut.QueryByHashAsync(requestContext, appId.Id, "hash"); + var result = await sut.FindByHashAsync(requestContext, "hash", "name", 123); - Assert.Same(enriched, result.Single()); + Assert.Same(enriched, result); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs index 903e0dd74..54f2e0206 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs @@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, A._)) .Returns(CreateContent(contentId)); - await Assert.ThrowsAsync(() => sut.FindContentAsync(ctx, schemaId.Name, contentId)); + await Assert.ThrowsAsync(() => sut.FindAsync(ctx, schemaId.Name, contentId)); } [Fact] @@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, A._)) .Returns(null); - await Assert.ThrowsAsync(async () => await sut.FindContentAsync(ctx, schemaId.Name, contentId)); + await Assert.ThrowsAsync(async () => await sut.FindAsync(ctx, schemaId.Name, contentId)); } [Theory] @@ -145,7 +145,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => contentRepository.FindContentAsync(ctx.App, schema, contentId, scope)) .Returns(content); - var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId); + var result = await sut.FindAsync(ctx, schemaId.Name, contentId); Assert.Equal(contentTransformed, result!.Data); Assert.Equal(content.Id, result.Id); @@ -161,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries A.CallTo(() => contentVersionLoader.GetAsync(appId.Id, contentId, 13)) .Returns(content); - var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId, 13); + var result = await sut.FindAsync(ctx, schemaId.Name, contentId, 13); Assert.Equal(contentTransformed, result!.Data); Assert.Equal(content.Id, result.Id);