diff --git a/.gitignore b/.gitignore
index 4c398ec98..dc1259a51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ node_modules/
**/wwwroot/scripts/**/*.*
/src/Squidex/appsettings.Development.json
+/src/Squidex/Assets
diff --git a/src/Squidex.Core/Squidex.Core.csproj b/src/Squidex.Core/Squidex.Core.csproj
index d5cb482ce..315417db2 100644
--- a/src/Squidex.Core/Squidex.Core.csproj
+++ b/src/Squidex.Core/Squidex.Core.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
index c15226c63..a572a853a 100644
--- a/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
+++ b/src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
@@ -15,6 +15,6 @@ namespace Squidex.Infrastructure.Assets
{
Task GetImageInfoAsync(Stream input);
- Task GetThumbnailOrNullAsync(Stream input, int dimension);
+ Task CreateThumbnailAsync(Stream input, int? width, int? height, string mode);
}
}
diff --git a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
index ab60f7b2f..a97e9ff58 100644
--- a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
+++ b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using System.IO;
using System.Threading.Tasks;
using ImageSharp;
@@ -22,24 +23,46 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
Configuration.Default.AddImageFormat(new PngFormat());
}
- public Task GetThumbnailOrNullAsync(Stream input, int dimension)
+ public Task CreateThumbnailAsync(Stream input, int? width, int? height, string mode)
{
return Task.Run(() =>
{
+ if (width == null && height == null)
+ {
+ return input;
+ }
+
+ if (!Enum.TryParse(mode, true, out var resizeMode))
+ {
+ resizeMode = ResizeMode.Max;
+ }
+
+ var w = width ?? int.MaxValue;
+ var h = height ?? int.MaxValue;
+
var result = new MemoryStream();
- var options =
- new ResizeOptions
+ using (var sourceImage = Image.Load(input))
+ {
+ if (w >= sourceImage.Width && h >= sourceImage.Height && resizeMode == ResizeMode.Crop)
{
- Size = new Size(dimension, dimension),
- Mode = ResizeMode.Max
- };
+ resizeMode = ResizeMode.BoxPad;
+ }
- var image = new Image(input).Resize(options);
+ var options =
+ new ResizeOptions
+ {
+ Size = new Size(w, h),
+ Mode = resizeMode
+ };
+
+ sourceImage.MetaData.Quality = 0;
+ sourceImage.Resize(options).Save(result);
+ }
- image.Save(result);
+ result.Position = 0;
- return (Stream)result;
+ return result;
});
}
@@ -47,23 +70,22 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
{
return Task.Run(() =>
{
+ ImageInfo imageInfo = null;
try
{
- var image = new Image(input);
+ var image = Image.Load(input);
if (image.Width > 0 && image.Height > 0)
{
- return new ImageInfo(image.Width, image.Height);
- }
- else
- {
- return null;
+ imageInfo = new ImageInfo(image.Width, image.Height);
}
}
catch
{
- return null;
+ imageInfo = null;
}
+
+ return imageInfo;
});
}
}
diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
index ebc95d857..a7f263d4b 100644
--- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
+++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
@@ -8,7 +8,7 @@
True
-
+
diff --git a/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
index 37c1493ab..9be371dbe 100644
--- a/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
+++ b/src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
@@ -26,12 +26,17 @@ namespace Squidex.Read.MongoDb.Assets
{
}
+ protected override string CollectionName()
+ {
+ return "Projections_Assets";
+ }
+
public async Task> QueryAsync(Guid appId, HashSet mimeTypes = null, string query = null, int take = 10, int skip = 0)
{
var filter = CreateFilter(appId, mimeTypes, query);
var assets =
- await Collection.Find(filter).Skip(skip).Limit(take).ToListAsync();
+ await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified).ToListAsync();
return assets.OfType().ToList();
}
diff --git a/src/Squidex/Assets/776fe199-0beb-4d64-b10c-01bdda742123_0 b/src/Squidex/Assets/776fe199-0beb-4d64-b10c-01bdda742123_0
deleted file mode 100644
index ebcf056cc..000000000
Binary files a/src/Squidex/Assets/776fe199-0beb-4d64-b10c-01bdda742123_0 and /dev/null differ
diff --git a/src/Squidex/Assets/84ede32b-edd6-4859-8ca2-4de831a5d305_0 b/src/Squidex/Assets/84ede32b-edd6-4859-8ca2-4de831a5d305_0
deleted file mode 100644
index ebcf056cc..000000000
Binary files a/src/Squidex/Assets/84ede32b-edd6-4859-8ca2-4de831a5d305_0 and /dev/null differ
diff --git a/src/Squidex/Assets/c1a2f5ee-df8e-4930-932d-031bcbdf959d_0 b/src/Squidex/Assets/c1a2f5ee-df8e-4930-932d-031bcbdf959d_0
deleted file mode 100644
index ebcf056cc..000000000
Binary files a/src/Squidex/Assets/c1a2f5ee-df8e-4930-932d-031bcbdf959d_0 and /dev/null differ
diff --git a/src/Squidex/Config/Swagger/XmlTagProcessor.cs b/src/Squidex/Config/Swagger/XmlTagProcessor.cs
index 19847d1bb..e2a13d080 100644
--- a/src/Squidex/Config/Swagger/XmlTagProcessor.cs
+++ b/src/Squidex/Config/Swagger/XmlTagProcessor.cs
@@ -20,7 +20,7 @@ namespace Squidex.Config.Swagger
{
public sealed class XmlTagProcessor : IOperationProcessor, IDocumentProcessor
{
- public void Process(DocumentProcessorContext context)
+ public Task ProcessAsync(DocumentProcessorContext context)
{
foreach (var controllerType in context.ControllerTypes)
{
@@ -46,6 +46,8 @@ namespace Squidex.Config.Swagger
}
}
}
+
+ return TaskHelper.Done;
}
public Task ProcessAsync(OperationProcessorContext context)
diff --git a/src/Squidex/Controllers/Api/Assets/AssetContentController.cs b/src/Squidex/Controllers/Api/Assets/AssetContentController.cs
new file mode 100644
index 000000000..59b90d435
--- /dev/null
+++ b/src/Squidex/Controllers/Api/Assets/AssetContentController.cs
@@ -0,0 +1,107 @@
+// ==========================================================================
+// AssetContentController.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using NSwag.Annotations;
+using Squidex.Infrastructure.Assets;
+using Squidex.Infrastructure.CQRS.Commands;
+using Squidex.Pipeline;
+using Squidex.Read.Assets.Repositories;
+
+#pragma warning disable 1573
+
+namespace Squidex.Controllers.Api.Assets
+{
+ ///
+ /// Uploads and retrieves assets.
+ ///
+ [ApiExceptionFilter]
+ [ServiceFilter(typeof(AppFilterAttribute))]
+ [SwaggerTag("Assets")]
+ public class AssetContentController : ControllerBase
+ {
+ private readonly IAssetStore assetStorage;
+ private readonly IAssetRepository assetRepository;
+ private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
+
+ public AssetContentController(
+ ICommandBus commandBus,
+ IAssetStore assetStorage,
+ IAssetRepository assetRepository,
+ IAssetThumbnailGenerator assetThumbnailGenerator)
+ : base(commandBus)
+ {
+ this.assetStorage = assetStorage;
+ this.assetRepository = assetRepository;
+ this.assetThumbnailGenerator = assetThumbnailGenerator;
+ }
+
+ ///
+ /// Gets the content of the asset.
+ ///
+ /// The name of the app.
+ /// The id of the asset.
+ /// The resize mode.
+ /// The target width of the image.
+ /// The target width of the image.
+ ///
+ /// 200 => Asset content returned.
+ /// 404 => App or Asset not found.
+ ///
+ [HttpGet]
+ [Route("assets/{id}/")]
+ public async Task GetAssetContent(string app, Guid id, [FromQuery] int? width = null, [FromQuery] int? height = null, [FromQuery] string mode = null)
+ {
+ var asset = await assetRepository.FindAssetAsync(id);
+
+ if (asset == null)
+ {
+ return NotFound();
+ }
+
+ Stream content;
+
+ if (asset.IsImage && (width.HasValue || height.HasValue))
+ {
+ var name = $"{asset.Id}_{asset.Version}_{width}_{height}_{mode}";
+
+ content = await assetStorage.GetAssetAsync(name);
+
+ if (content == null)
+ {
+ var fullSizeContent = await assetStorage.GetAssetAsync($"{asset.Id}_{asset.Version}");
+
+ if (fullSizeContent == null)
+ {
+ return NotFound();
+ }
+
+ content = await assetThumbnailGenerator.CreateThumbnailAsync(fullSizeContent, width, height, mode);
+
+ await assetStorage.UploadAssetAsync(name, content);
+
+ content.Position = 0;
+ }
+ }
+ else
+ {
+ content = await assetStorage.GetAssetAsync($"{asset.Id}_{asset.Version}");
+ }
+
+ if (content == null)
+ {
+ return NotFound();
+ }
+
+ return new FileStreamResult(content, asset.MimeType);
+ }
+ }
+}
diff --git a/src/Squidex/Controllers/Api/Assets/AssetController.cs b/src/Squidex/Controllers/Api/Assets/AssetController.cs
index ff7c734d4..71a4f4099 100644
--- a/src/Squidex/Controllers/Api/Assets/AssetController.cs
+++ b/src/Squidex/Controllers/Api/Assets/AssetController.cs
@@ -105,7 +105,6 @@ namespace Squidex.Controllers.Api.Assets
[HttpPost]
[Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetDto), 201)]
- [ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
public async Task PostAsset(string app, List files)
{
diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj
index 06046590b..a517add11 100644
--- a/src/Squidex/Squidex.csproj
+++ b/src/Squidex/Squidex.csproj
@@ -33,7 +33,7 @@
-
+
@@ -57,9 +57,9 @@
-
+
-
+
diff --git a/src/Squidex/app/features/assets/pages/asset.component.html b/src/Squidex/app/features/assets/pages/asset.component.html
index b54fbdcb8..056377cbf 100644
--- a/src/Squidex/app/features/assets/pages/asset.component.html
+++ b/src/Squidex/app/features/assets/pages/asset.component.html
@@ -1,7 +1,12 @@
-