diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
index 40a0621c0..bc977433c 100644
--- a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
+++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
@@ -20,37 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
this.assetThumbnailGenerator = assetThumbnailGenerator;
}
- private sealed class TempAssetFile : AssetFile, IDisposable
- {
- public Stream Stream { get; }
-
- public TempAssetFile(AssetFile source)
- : base(source.FileName, source.MimeType, source.FileSize)
- {
- var tempPath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
-
- var tempStream = new FileStream(tempPath,
- FileMode.Create,
- FileAccess.ReadWrite,
- FileShare.None, 4096,
- FileOptions.DeleteOnClose);
-
- Stream = tempStream;
- }
-
- public override void Dispose()
- {
- Stream.Dispose();
- }
-
- public override Stream OpenRead()
- {
- Stream.Position = 0;
-
- return Stream;
- }
- }
-
public async Task EnhanceAsync(UploadAssetCommand command)
{
if (command.Type == AssetType.Unknown || command.Type == AssetType.Image)
@@ -66,18 +35,27 @@ namespace Squidex.Domain.Apps.Entities.Assets
if (imageInfo != null)
{
- var isSwapped = imageInfo.IsRotatedOrSwapped;
+ var isSwapped = imageInfo.Orientation > ImageOrientation.TopLeft;
- if (isSwapped)
+ if (command.File != null && isSwapped)
{
var tempFile = new TempAssetFile(command.File);
await using (var uploadStream = command.File.OpenRead())
{
- imageInfo = await assetThumbnailGenerator.FixOrientationAsync(uploadStream, mimeType, tempFile.Stream);
+ await using (var tempStream = tempFile.OpenWrite())
+ {
+ await assetThumbnailGenerator.FixOrientationAsync(uploadStream, mimeType, tempStream);
+ }
}
- command.File.Dispose();
+ await using (var tempStream = tempFile.OpenRead())
+ {
+ imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(tempStream, mimeType) ?? imageInfo;
+ }
+
+ await command.File.DisposeAsync();
+
command.File = tempFile;
}
diff --git a/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs
index ccde0b8d4..49ba98fc2 100644
--- a/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs
+++ b/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs
@@ -6,7 +6,6 @@
// ==========================================================================
using Orleans;
-using Squidex.Infrastructure.States;
using Squidex.Log;
namespace Squidex.Infrastructure.Orleans
diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
index 3cba22f72..e1a45f2e3 100644
--- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
+++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
@@ -31,9 +31,9 @@
-
+
-
+
diff --git a/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs b/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs
index 057e81231..e28772c28 100644
--- a/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs
+++ b/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs
@@ -8,24 +8,31 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.Extensions.DependencyInjection;
using Squidex.Log;
namespace Squidex.Web.Pipeline
{
public sealed class ActionContextLogAppender : ILogAppender
{
- private readonly IActionContextAccessor actionContextAccessor;
- private readonly IHttpContextAccessor httpContextAccessor;
+ private readonly IServiceProvider services;
- public ActionContextLogAppender(IActionContextAccessor actionContextAccessor, IHttpContextAccessor httpContextAccessor)
+ public ActionContextLogAppender(IServiceProvider services)
{
- this.actionContextAccessor = actionContextAccessor;
-
- this.httpContextAccessor = httpContextAccessor;
+ this.services = services;
}
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{
+ var httpContextAccessor = services.GetService();
+
+ if (string.IsNullOrEmpty(httpContextAccessor?.HttpContext?.Request?.Method))
+ {
+ return;
+ }
+
+ var actionContext = services.GetRequiredService()?.ActionContext;
+
try
{
var httpContext = httpContextAccessor.HttpContext;
@@ -37,7 +44,7 @@ namespace Squidex.Web.Pipeline
var requestId = GetRequestId(httpContext);
- var logContext = (requestId, context: httpContext, actionContextAccessor);
+ var logContext = (requestId, context: httpContext, actionContext);
writer.WriteObject("web", logContext, (ctx, w) =>
{
@@ -45,7 +52,7 @@ namespace Squidex.Web.Pipeline
w.WriteProperty("requestPath", ctx.context.Request.Path);
w.WriteProperty("requestMethod", ctx.context.Request.Method);
- var actionContext = ctx.actionContextAccessor.ActionContext;
+ var actionContext = ctx.actionContext;
if (actionContext != null)
{
diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppImageController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppImageController.cs
new file mode 100644
index 000000000..5881f08d4
--- /dev/null
+++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppImageController.cs
@@ -0,0 +1,162 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Net.Http.Headers;
+using Squidex.Assets;
+using Squidex.Domain.Apps.Entities;
+using Squidex.Domain.Apps.Entities.Apps;
+using Squidex.Infrastructure;
+using Squidex.Infrastructure.Commands;
+using Squidex.Web;
+
+namespace Squidex.Areas.Api.Controllers.Apps
+{
+ ///
+ /// Manages and configures apps.
+ ///
+ [ApiExplorerSettings(GroupName = nameof(Apps))]
+ public sealed class AppImageController : ApiController
+ {
+ private readonly IAppImageStore appImageStore;
+ private readonly IAppProvider appProvider;
+ private readonly IAssetStore assetStore;
+ private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
+
+ public AppImageController(ICommandBus commandBus,
+ IAppImageStore appImageStore,
+ IAppProvider appProvider,
+ IAssetStore assetStore,
+ IAssetThumbnailGenerator assetThumbnailGenerator)
+ : base(commandBus)
+ {
+ this.appImageStore = appImageStore;
+ this.appProvider = appProvider;
+ this.assetStore = assetStore;
+ this.assetThumbnailGenerator = assetThumbnailGenerator;
+ }
+
+ ///
+ /// Get the app image.
+ ///
+ /// The name of the app.
+ ///
+ /// 200 => App image found and content or (resized) image returned.
+ /// 404 => App not found.
+ ///
+ [HttpGet]
+ [Route("apps/{app}/image")]
+ [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
+ [AllowAnonymous]
+ [ApiCosts(0)]
+ public IActionResult GetImage(string app)
+ {
+ if (App.Image == null)
+ {
+ return NotFound();
+ }
+
+ var etag = App.Image.Etag;
+
+ Response.Headers[HeaderNames.ETag] = etag;
+
+ var callback = new FileCallback(async (body, range, ct) =>
+ {
+ var resizedAsset = $"{App.Id}_{etag}_Resized";
+
+ try
+ {
+ await assetStore.DownloadAsync(resizedAsset, body, ct: ct);
+ }
+ catch (AssetNotFoundException)
+ {
+ await ResizeAsync(resizedAsset, App.Image.MimeType, body, ct);
+ }
+ });
+
+ return new FileCallbackResult(App.Image.MimeType, callback)
+ {
+ ErrorAs404 = true
+ };
+ }
+
+ private async Task ResizeAsync(string resizedAsset, string mimeType, Stream target,
+ CancellationToken ct)
+ {
+#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
+#pragma warning disable MA0040 // Flow the cancellation token
+ using var activity = Telemetry.Activities.StartActivity("Resize");
+
+ await using var assetOriginal = new TempAssetFile(resizedAsset, mimeType, 0);
+ await using var assetResized = new TempAssetFile(resizedAsset, mimeType, 0);
+
+ var resizeOptions = new ResizeOptions
+ {
+ TargetWidth = 50,
+ TargetHeight = 50
+ };
+
+ using (Telemetry.Activities.StartActivity("Read"))
+ {
+ await using (var originalStream = assetOriginal.OpenWrite())
+ {
+ await appImageStore.DownloadAsync(App.Id, originalStream);
+ }
+ }
+
+ using (Telemetry.Activities.StartActivity("Resize"))
+ {
+ try
+ {
+ await using (var originalStream = assetOriginal.OpenRead())
+ {
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await assetThumbnailGenerator.CreateThumbnailAsync(originalStream, mimeType, resizeStream, resizeOptions);
+ }
+ }
+ }
+ catch
+ {
+ await using (var originalStream = assetOriginal.OpenRead())
+ {
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await originalStream.CopyToAsync(resizeStream);
+ }
+ }
+ }
+ }
+
+ using (Telemetry.Activities.StartActivity("Save"))
+ {
+ try
+ {
+ await using (var resizeStream = assetResized.OpenRead())
+ {
+ await assetStore.UploadAsync(resizedAsset, resizeStream);
+ }
+ }
+ catch (AssetAlreadyExistsException)
+ {
+ return;
+ }
+ }
+
+ using (Telemetry.Activities.StartActivity("Write"))
+ {
+ await using (var resizeStream = assetResized.OpenRead())
+ {
+ await resizeStream.CopyToAsync(target, ct);
+ }
+ }
+#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
+#pragma warning restore MA0040 // Flow the cancellation token
+ }
+ }
+}
diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
index 635eb0ffc..a5b0713f8 100644
--- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
+++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
@@ -5,15 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models;
-using Squidex.Assets;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
-using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security;
using Squidex.Infrastructure.Translations;
@@ -29,28 +26,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiExplorerSettings(GroupName = nameof(Apps))]
public sealed class AppsController : ApiController
{
- private static readonly ResizeOptions ResizeOptions = new ResizeOptions
- {
- TargetWidth = 50,
- TargetHeight = 50,
- Mode = ResizeMode.Crop
- };
- private readonly IAppImageStore appImageStore;
private readonly IAppProvider appProvider;
- private readonly IAssetStore assetStore;
- private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
-
- public AppsController(ICommandBus commandBus,
- IAppImageStore appImageStore,
- IAppProvider appProvider,
- IAssetStore assetStore,
- IAssetThumbnailGenerator assetThumbnailGenerator)
+
+ public AppsController(ICommandBus commandBus, IAppProvider appProvider)
: base(commandBus)
{
- this.appImageStore = appImageStore;
this.appProvider = appProvider;
- this.assetStore = assetStore;
- this.assetThumbnailGenerator = assetThumbnailGenerator;
}
///
@@ -185,84 +166,6 @@ namespace Squidex.Areas.Api.Controllers.Apps
return Ok(response);
}
- ///
- /// Get the app image.
- ///
- /// The name of the app.
- ///
- /// 200 => App image found and content or (resized) image returned.
- /// 404 => App not found.
- ///
- [HttpGet]
- [Route("apps/{app}/image")]
- [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
- [AllowAnonymous]
- [ApiCosts(0)]
- public IActionResult GetImage(string app)
- {
- if (App.Image == null)
- {
- return NotFound();
- }
-
- var etag = App.Image.Etag;
-
- Response.Headers[HeaderNames.ETag] = etag;
-
- var callback = new FileCallback(async (body, range, ct) =>
- {
- var resizedAsset = $"{App.Id}_{etag}_Resized";
-
- try
- {
- await assetStore.DownloadAsync(resizedAsset, body, ct: ct);
- }
- catch (AssetNotFoundException)
- {
- using (Telemetry.Activities.StartActivity("Resize"))
- {
- await using (var destinationStream = GetTempStream())
- {
- await ResizeAsync(resizedAsset, App.Image.MimeType, destinationStream);
-
- await destinationStream.CopyToAsync(body, ct);
- }
- }
- }
- });
-
- return new FileCallbackResult(App.Image.MimeType, callback)
- {
- ErrorAs404 = true
- };
- }
-
- private async Task ResizeAsync(string resizedAsset, string mimeType, FileStream destinationStream)
- {
-#pragma warning disable MA0040 // Flow the cancellation token
- await using (var sourceStream = GetTempStream())
- {
- using (Telemetry.Activities.StartActivity("ResizeDownload"))
- {
- await appImageStore.DownloadAsync(App.Id, sourceStream);
- sourceStream.Position = 0;
- }
-
- using (Telemetry.Activities.StartActivity("ResizeImage"))
- {
- await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, mimeType, destinationStream, ResizeOptions);
- destinationStream.Position = 0;
- }
-
- using (Telemetry.Activities.StartActivity("ResizeUpload"))
- {
- await assetStore.UploadAsync(resizedAsset, destinationStream);
- destinationStream.Position = 0;
- }
- }
-#pragma warning restore MA0040 // Flow the cancellation token
- }
-
///
/// Remove the app image.
///
@@ -335,18 +238,5 @@ namespace Squidex.Areas.Api.Controllers.Apps
return new UploadAppImage { File = file.ToAssetFile() };
}
-
- private static FileStream GetTempStream()
- {
- var tempFileName = Path.GetTempFileName();
-
- return new FileStream(tempFileName,
- FileMode.Create,
- FileAccess.ReadWrite,
- FileShare.Delete, 1024 * 16,
- FileOptions.Asynchronous |
- FileOptions.DeleteOnClose |
- FileOptions.SequentialScan);
- }
}
}
diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
index bdf942b4f..c3321a22b 100644
--- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
+++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
@@ -135,7 +135,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
if (app.Image != null)
{
- AddGetLink("image", resources.Url(x => nameof(x.GetImage), values));
+ AddGetLink("image", resources.Url(x => nameof(x.GetImage), values));
}
if (isContributor)
diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
index 87add2bd5..f2f5627da 100644
--- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
+++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
@@ -135,10 +135,6 @@ namespace Squidex.Areas.Api.Controllers.Assets
return NotFound();
}
- var resizeOptions = request.ToResizeOptions(asset);
-
- FileCallback callback;
-
Response.Headers[HeaderNames.ETag] = asset.FileVersion.ToString(CultureInfo.InvariantCulture);
if (request.CacheDuration > 0)
@@ -146,27 +142,33 @@ namespace Squidex.Areas.Api.Controllers.Assets
Response.Headers[HeaderNames.CacheControl] = $"public,max-age={request.CacheDuration}";
}
+ var resizeOptions = request.ToResizeOptions(asset);
+
var contentLength = (long?)null;
+ var contentCallback = (FileCallback?)null;
if (asset.Type == AssetType.Image && resizeOptions.IsValid)
{
- callback = async (bodyStream, range, ct) =>
+ contentCallback = async (body, range, ct) =>
{
+ var suffix = resizeOptions.ToString();
+
if (request.ForceResize)
{
- await ResizeAsync(asset, bodyStream, resizeOptions, true, ct);
+ using (Telemetry.Activities.StartActivity("Resize"))
+ {
+ await ResizeAsync(asset, suffix, body, resizeOptions, true, ct);
+ }
}
else
{
try
{
- var suffix = resizeOptions.ToString();
-
- await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, bodyStream, ct: ct);
+ await DownloadAsync(asset, body, suffix, range, ct);
}
catch (AssetNotFoundException)
{
- await ResizeAsync(asset, bodyStream, resizeOptions, false, ct);
+ await ResizeAsync(asset, suffix, body, resizeOptions, false, ct);
}
}
};
@@ -175,13 +177,13 @@ namespace Squidex.Areas.Api.Controllers.Assets
{
contentLength = asset.FileSize;
- callback = async (bodyStream, range, ct) =>
+ contentCallback = async (body, range, ct) =>
{
- await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, null, bodyStream, range, ct);
+ await DownloadAsync(asset, body, null, range, ct);
};
}
- return new FileCallbackResult(asset.MimeType, callback)
+ return new FileCallbackResult(asset.MimeType, contentCallback)
{
EnableRangeProcessing = contentLength > 0,
ErrorAs404 = true,
@@ -192,78 +194,78 @@ namespace Squidex.Areas.Api.Controllers.Assets
};
}
- private async Task ResizeAsync(IAssetEntity asset, Stream bodyStream, ResizeOptions resizeOptions, bool overwrite,
+ private async Task DownloadAsync(IAssetEntity asset, Stream bodyStream, string? suffix, BytesRange range,
CancellationToken ct)
{
- using (Telemetry.Activities.StartActivity("Resize"))
- {
- await using (var destinationStream = GetTempStream())
- {
- // Do not use cancellation for the resize process because it is valuable to complete it.
- await ResizeAsync(asset, resizeOptions, destinationStream, overwrite);
-
- await destinationStream.CopyToAsync(bodyStream, ct);
- }
- }
+ await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, bodyStream, range, ct);
}
- private async Task ResizeAsync(IAssetEntity asset, ResizeOptions resizeOptions, FileStream stream, bool overwrite)
+ private async Task ResizeAsync(IAssetEntity asset, string suffix, Stream target, ResizeOptions resizeOptions, bool overwrite,
+ CancellationToken ct)
{
+#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
#pragma warning disable MA0040 // Flow the cancellation token
- var suffix = resizeOptions.ToString();
+ using var activity = Telemetry.Activities.StartActivity("Resize");
+
+ await using var assetOriginal = new TempAssetFile(asset.FileName, asset.MimeType, 0);
+ await using var assetResized = new TempAssetFile(asset.FileName, asset.MimeType, 0);
- await using (var sourceStream = GetTempStream())
+ using (Telemetry.Activities.StartActivity("Read"))
{
- using (Telemetry.Activities.StartActivity("ResizeDownload"))
+ await using (var originalStream = assetOriginal.OpenWrite())
{
- await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, null, sourceStream);
- sourceStream.Position = 0;
+ await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, null, originalStream);
}
+ }
- using (Telemetry.Activities.StartActivity("ResizeImage"))
+ using (Telemetry.Activities.StartActivity("Resize"))
+ {
+ try
{
- try
+ await using (var originalStream = assetOriginal.OpenRead())
{
- await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, asset.MimeType, stream, resizeOptions);
- stream.Position = 0;
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await assetThumbnailGenerator.CreateThumbnailAsync(originalStream, asset.MimeType, resizeStream, resizeOptions);
+ }
}
- catch
+ }
+ catch
+ {
+ await using (var originalStream = assetOriginal.OpenRead())
{
- sourceStream.Position = 0;
- await sourceStream.CopyToAsync(stream);
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await originalStream.CopyToAsync(resizeStream);
+ }
}
}
+ }
+ using (Telemetry.Activities.StartActivity("Save"))
+ {
try
{
- using (Telemetry.Activities.StartActivity("ResizeUpload"))
+ await using (var resizeStream = assetResized.OpenRead())
{
- await assetFileStore.UploadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, stream, overwrite);
- stream.Position = 0;
+ await assetFileStore.UploadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, resizeStream, overwrite);
}
}
catch (AssetAlreadyExistsException)
{
- stream.Position = 0;
+ return;
}
}
-#pragma warning restore MA0040 // Flow the cancellation token
- }
-
- private static FileStream GetTempStream()
- {
- var tempFileName = Path.GetTempFileName();
- const int bufferSize = 16 * 1024;
-
- return new FileStream(tempFileName,
- FileMode.Create,
- FileAccess.ReadWrite,
- FileShare.Delete,
- bufferSize,
- FileOptions.Asynchronous |
- FileOptions.DeleteOnClose |
- FileOptions.SequentialScan);
+ using (Telemetry.Activities.StartActivity("Write"))
+ {
+ await using (var resizeStream = assetResized.OpenRead())
+ {
+ await resizeStream.CopyToAsync(target, ct);
+ }
+ }
+#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
+#pragma warning restore MA0040 // Flow the cancellation token
}
}
}
diff --git a/backend/src/Squidex/Areas/Api/Startup.cs b/backend/src/Squidex/Areas/Api/Startup.cs
index 6c5e0f33e..480252943 100644
--- a/backend/src/Squidex/Areas/Api/Startup.cs
+++ b/backend/src/Squidex/Areas/Api/Startup.cs
@@ -15,18 +15,18 @@ namespace Squidex.Areas.Api
{
public static void ConfigureApi(this IApplicationBuilder app)
{
- app.Map(Constants.PrefixApi, appApi =>
+ app.Map(Constants.PrefixApi, builder =>
{
- appApi.UseAccessTokenQueryString();
+ builder.UseAccessTokenQueryString();
- appApi.UseRouting();
+ builder.UseRouting();
- appApi.UseAuthentication();
- appApi.UseAuthorization();
+ builder.UseAuthentication();
+ builder.UseAuthorization();
- appApi.UseSquidexOpenApi();
+ builder.UseSquidexOpenApi();
- appApi.UseEndpoints(endpoints =>
+ builder.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs
index 258da762e..9cecb17da 100644
--- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs
+++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs
@@ -20,18 +20,13 @@ using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation;
using Squidex.Shared.Identity;
using Squidex.Shared.Users;
+using Squidex.Web;
namespace Squidex.Areas.IdentityServer.Controllers.Profile
{
[Authorize]
public sealed class ProfileController : IdentityServerController
{
- private static readonly ResizeOptions ResizeOptions = new ResizeOptions
- {
- TargetWidth = 128,
- TargetHeight = 128,
- Mode = ResizeMode.Crop
- };
private readonly IUserPictureStore userPictureStore;
private readonly IUserService userService;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
@@ -156,31 +151,46 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile
throw new ValidationException(T.Get("validation.onlyOneFile"));
}
- using (var thumbnailStream = new MemoryStream())
+ await UploadResizedAsync(files[0], id, HttpContext.RequestAborted);
+
+ var update = new UserValues
{
- try
- {
- var file = files[0];
+ PictureUrl = SquidexClaimTypes.PictureUrlStore
+ };
- await using (var stream = file.OpenReadStream())
- {
- await assetThumbnailGenerator.CreateThumbnailAsync(stream, file.ContentType, thumbnailStream, ResizeOptions,
- HttpContext.RequestAborted);
- }
+ await userService.UpdateAsync(id, update, ct: HttpContext.RequestAborted);
+ }
- thumbnailStream.Position = 0;
- }
- catch
+ private async Task UploadResizedAsync(IFormFile file, string id,
+ CancellationToken ct)
+ {
+ await using var assetResized = new TempAssetFile(file.ToAssetFile());
+
+ var resizeOptions = new ResizeOptions
+ {
+ TargetWidth = 128,
+ TargetHeight = 128
+ };
+
+ try
+ {
+ await using (var originalStream = file.OpenReadStream())
{
- throw new ValidationException(T.Get("validation.notAnImage"));
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await assetThumbnailGenerator.CreateThumbnailAsync(originalStream, file.ContentType, resizeStream, resizeOptions, ct);
+ }
}
-
- await userPictureStore.UploadAsync(id, thumbnailStream, HttpContext.RequestAborted);
+ }
+ catch
+ {
+ throw new ValidationException(T.Get("validation.notAnImage"));
}
- var update = new UserValues { PictureUrl = SquidexClaimTypes.PictureUrlStore };
-
- await userService.UpdateAsync(id, update, ct: HttpContext.RequestAborted);
+ await using (var resizeStream = assetResized.OpenWrite())
+ {
+ await userPictureStore.UploadAsync(id, resizeStream, ct);
+ }
}
private async Task MakeChangeAsync(Func action, string successMessage, TModel? model = null) where TModel : class
diff --git a/backend/src/Squidex/Areas/IdentityServer/Startup.cs b/backend/src/Squidex/Areas/IdentityServer/Startup.cs
index 8f6b895e2..77dd04cee 100644
--- a/backend/src/Squidex/Areas/IdentityServer/Startup.cs
+++ b/backend/src/Squidex/Areas/IdentityServer/Startup.cs
@@ -15,23 +15,23 @@ namespace Squidex.Areas.IdentityServer
{
var environment = app.ApplicationServices.GetRequiredService();
- app.Map(Constants.PrefixIdentityServer, identityApp =>
+ app.Map(Constants.PrefixIdentityServer, builder =>
{
if (environment.IsDevelopment())
{
- identityApp.UseDeveloperExceptionPage();
+ builder.UseDeveloperExceptionPage();
}
else
{
- identityApp.UseExceptionHandler("/error");
+ builder.UseExceptionHandler("/error");
}
- identityApp.UseRouting();
+ builder.UseRouting();
- identityApp.UseAuthentication();
- identityApp.UseAuthorization();
+ builder.UseAuthentication();
+ builder.UseAuthorization();
- identityApp.UseEndpoints(endpoints =>
+ builder.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
diff --git a/backend/src/Squidex/Areas/OrleansDashboard/Startup.cs b/backend/src/Squidex/Areas/OrleansDashboard/Startup.cs
index 37a292eb1..a4f51fb59 100644
--- a/backend/src/Squidex/Areas/OrleansDashboard/Startup.cs
+++ b/backend/src/Squidex/Areas/OrleansDashboard/Startup.cs
@@ -15,13 +15,13 @@ namespace Squidex.Areas.OrleansDashboard
{
public static void ConfigureOrleansDashboard(this IApplicationBuilder app)
{
- app.Map(Constants.PrefixOrleans, orleansApp =>
+ app.Map(Constants.PrefixOrleans, builder =>
{
- orleansApp.UseAuthentication();
- orleansApp.UseAuthorization();
+ builder.UseAuthentication();
+ builder.UseAuthorization();
- orleansApp.UseMiddleware();
- orleansApp.UseOrleansDashboard();
+ builder.UseMiddleware();
+ builder.UseOrleansDashboard();
});
}
}
diff --git a/backend/src/Squidex/Areas/Portal/Startup.cs b/backend/src/Squidex/Areas/Portal/Startup.cs
index b1a5933e7..9c94d2137 100644
--- a/backend/src/Squidex/Areas/Portal/Startup.cs
+++ b/backend/src/Squidex/Areas/Portal/Startup.cs
@@ -14,13 +14,13 @@ namespace Squidex.Areas.Portal
{
public static void ConfigurePortal(this IApplicationBuilder app)
{
- app.Map(Constants.PrefixPortal, portalApp =>
+ app.Map(Constants.PrefixPortal, builder =>
{
- portalApp.UseAuthentication();
- portalApp.UseAuthorization();
+ builder.UseAuthentication();
+ builder.UseAuthorization();
- portalApp.UseMiddleware();
- portalApp.UseMiddleware();
+ builder.UseMiddleware();
+ builder.UseMiddleware();
});
}
}
diff --git a/backend/src/Squidex/Config/Domain/AppsServices.cs b/backend/src/Squidex/Config/Domain/AppsServices.cs
index a5d5a52f9..62bc19d53 100644
--- a/backend/src/Squidex/Config/Domain/AppsServices.cs
+++ b/backend/src/Squidex/Config/Domain/AppsServices.cs
@@ -15,6 +15,7 @@ using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.EventSourcing;
+using Squidex.Infrastructure.Log;
namespace Squidex.Config.Domain
{
@@ -40,6 +41,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.As();
+ services.AddSingletonAs()
+ .As().As();
+
services.AddSingletonAs()
.As();
diff --git a/backend/src/Squidex/Config/Domain/AssetServices.cs b/backend/src/Squidex/Config/Domain/AssetServices.cs
index 00e26f5af..5d0789ecb 100644
--- a/backend/src/Squidex/Config/Domain/AssetServices.cs
+++ b/backend/src/Squidex/Config/Domain/AssetServices.cs
@@ -9,8 +9,6 @@ using FluentFTP;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
using Squidex.Assets;
-using Squidex.Assets.ImageMagick;
-using Squidex.Assets.ImageSharp;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
@@ -178,16 +176,6 @@ namespace Squidex.Config.Domain
}
});
- var thumbnailGenerator = new CompositeThumbnailGenerator(
- new IAssetThumbnailGenerator[]
- {
- new ImageSharpThumbnailGenerator(),
- new ImageMagickThumbnailGenerator()
- });
-
- services.AddSingletonAs(c => thumbnailGenerator)
- .As();
-
services.AddSingletonAs(c => new DelegateInitializer(
c.GetRequiredService().GetType().Name,
c.GetRequiredService().InitializeAsync))
diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs
index 888e386b0..b22f6d456 100644
--- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs
+++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs
@@ -25,6 +25,7 @@ using Squidex.Domain.Apps.Entities.Tags;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing.Grains;
+using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.UsageTracking;
@@ -60,6 +61,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs>()
.AsSelf();
+ services.AddSingletonAs()
+ .AsOptional();
+
services.AddSingletonAs()
.As();
diff --git a/backend/src/Squidex/Config/Domain/LoggingServices.cs b/backend/src/Squidex/Config/Domain/LoggingServices.cs
index b2a41a590..f03497357 100644
--- a/backend/src/Squidex/Config/Domain/LoggingServices.cs
+++ b/backend/src/Squidex/Config/Domain/LoggingServices.cs
@@ -7,8 +7,6 @@
#define LOG_ALL_IDENTITY_SERVER_NONE
-using Squidex.Domain.Apps.Entities;
-using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure.Log;
using Squidex.Log;
using Squidex.Web.Pipeline;
@@ -43,12 +41,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.As();
-
- services.AddSingletonAs()
- .As().As();
-
- services.AddSingletonAs()
- .AsOptional();
}
private static void AddFilters(this ILoggingBuilder builder)
diff --git a/backend/src/Squidex/Config/Domain/ResizeServices.cs b/backend/src/Squidex/Config/Domain/ResizeServices.cs
new file mode 100644
index 000000000..dd456814e
--- /dev/null
+++ b/backend/src/Squidex/Config/Domain/ResizeServices.cs
@@ -0,0 +1,45 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using Squidex.Assets;
+using Squidex.Assets.ImageMagick;
+using Squidex.Assets.ImageSharp;
+using Squidex.Assets.Remote;
+
+namespace Squidex.Config.Domain
+{
+ public static class ResizeServices
+ {
+ public static void AddSquidexImageResizing(this IServiceCollection services, IConfiguration config)
+ {
+ var thumbnailGenerator = new CompositeThumbnailGenerator(
+ new IAssetThumbnailGenerator[]
+ {
+ new ImageSharpThumbnailGenerator(),
+ new ImageMagickThumbnailGenerator()
+ });
+
+ var resizerUrl = config.GetValue("assets:resizerUrl");
+
+ if (!string.IsNullOrWhiteSpace(resizerUrl))
+ {
+ services.AddHttpClient("Resize", options =>
+ {
+ options.BaseAddress = new Uri(resizerUrl);
+ });
+
+ services.AddSingletonAs(c => new RemoteThumbnailGenerator(c.GetRequiredService(), thumbnailGenerator))
+ .As();
+ }
+ else
+ {
+ services.AddSingletonAs(c => thumbnailGenerator)
+ .As();
+ }
+ }
+ }
+}
diff --git a/backend/src/Squidex/Config/Domain/RuleServices.cs b/backend/src/Squidex/Config/Domain/RuleServices.cs
index b2389fce7..539bd4294 100644
--- a/backend/src/Squidex/Config/Domain/RuleServices.cs
+++ b/backend/src/Squidex/Config/Domain/RuleServices.cs
@@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
+using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Extensions;
using Squidex.Domain.Apps.Core.Scripting;
@@ -102,6 +103,11 @@ namespace Squidex.Config.Domain
services.AddSingletonAs>()
.AsSelf();
+
+ services.AddInitializer("Serializer (Rules)", registry =>
+ {
+ RuleActionConverter.Mapping = registry.Actions.ToDictionary(x => x.Key, x => x.Value.Type);
+ }, -1);
}
}
}
diff --git a/backend/src/Squidex/Config/Domain/SerializationInitializer.cs b/backend/src/Squidex/Config/Domain/SerializationInitializer.cs
deleted file mode 100644
index 310af8162..000000000
--- a/backend/src/Squidex/Config/Domain/SerializationInitializer.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschraenkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using Newtonsoft.Json;
-using Squidex.Areas.Api.Controllers.Rules.Models;
-using Squidex.Domain.Apps.Core.HandleRules;
-using Squidex.Hosting;
-using Squidex.Infrastructure.Json;
-using Squidex.Infrastructure.MongoDb;
-using Squidex.Infrastructure.Orleans;
-
-namespace Squidex.Config.Domain
-{
- public sealed class SerializationInitializer : IInitializable
- {
- private readonly JsonSerializer jsonNetSerializer;
- private readonly IJsonSerializer jsonSerializer;
- private readonly RuleRegistry ruleRegistry;
-
- public int Order => -1;
-
- public SerializationInitializer(JsonSerializer jsonNetSerializer, IJsonSerializer jsonSerializer, RuleRegistry ruleRegistry)
- {
- this.jsonNetSerializer = jsonNetSerializer;
- this.jsonSerializer = jsonSerializer;
-
- this.ruleRegistry = ruleRegistry;
- }
-
- public Task InitializeAsync(
- CancellationToken ct)
- {
- SetupBson();
- SetupOrleans();
- SetupActions();
-
- return Task.CompletedTask;
- }
-
- private void SetupActions()
- {
- RuleActionConverter.Mapping = ruleRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type);
- }
-
- private void SetupBson()
- {
- BsonJsonConvention.Register(jsonNetSerializer);
- }
-
- private void SetupOrleans()
- {
- J.DefaultSerializer = jsonSerializer;
- }
- }
-}
diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs
index b507e1914..f88e3e59f 100644
--- a/backend/src/Squidex/Config/Domain/SerializationServices.cs
+++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs
@@ -35,8 +35,9 @@ namespace Squidex.Config.Domain
{
public static class SerializationServices
{
- private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
+ private static JsonSerializerSettings ConfigureJson(TypeNameHandling typeNameHandling, JsonSerializerSettings? settings = null)
{
+ settings ??= new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
settings.ContractResolver = new ConverterContractResolver(
@@ -86,9 +87,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.As();
- services.AddSingletonAs()
- .AsSelf();
-
services.AddSingletonAs()
.AsSelf();
@@ -97,7 +95,7 @@ namespace Squidex.Config.Domain
services.AddSingletonAs(c =>
{
- var serializerSettings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.Auto);
+ var serializerSettings = ConfigureJson(TypeNameHandling.Auto, new JsonSerializerSettings());
var typeNameRegistry = c.GetService();
@@ -118,7 +116,7 @@ namespace Squidex.Config.Domain
{
options.AllowInputFormatterExceptionMessages = false;
- ConfigureJson(options.SerializerSettings, TypeNameHandling.None);
+ ConfigureJson(TypeNameHandling.None, options.SerializerSettings);
});
return builder;
@@ -128,9 +126,7 @@ namespace Squidex.Config.Domain
{
builder.Services.AddSingleton(c =>
{
- var settings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.None);
-
- var serializer = new NewtonsoftJsonSerializer(settings);
+ var serializer = new NewtonsoftJsonSerializer(ConfigureJson(TypeNameHandling.None));
return new DefaultDocumentWriter(serializer);
});
diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs
index 7716dda5b..831a71d32 100644
--- a/backend/src/Squidex/Config/Domain/StoreServices.cs
+++ b/backend/src/Squidex/Config/Domain/StoreServices.cs
@@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Identity;
using Migrations.Migrations.MongoDb;
using MongoDB.Driver;
+using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
@@ -38,6 +39,7 @@ using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Migrations;
+using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking;
@@ -160,12 +162,19 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.AsOptional().As();
}
+
+ services.AddInitializer("Serializer (BSON)", jsonNetSerializer =>
+ {
+ BsonJsonConvention.Register(jsonNetSerializer);
+ }, -1);
}
});
- services.AddSingleton(typeof(IStore<>), typeof(Store<>));
+ services.AddSingleton(typeof(IStore<>),
+ typeof(Store<>));
- services.AddSingleton(typeof(IPersistenceFactory<>), typeof(Store<>));
+ services.AddSingleton(typeof(IPersistenceFactory<>),
+ typeof(Store<>));
}
private static IMongoClient GetClient(string configuration)
diff --git a/backend/src/Squidex/Config/Orleans/OrleansServices.cs b/backend/src/Squidex/Config/Orleans/OrleansServices.cs
index 02fa456bb..6ada3e54b 100644
--- a/backend/src/Squidex/Config/Orleans/OrleansServices.cs
+++ b/backend/src/Squidex/Config/Orleans/OrleansServices.cs
@@ -17,6 +17,7 @@ using OrleansDashboard;
using Squidex.Domain.Apps.Entities;
using Squidex.Hosting.Configuration;
using Squidex.Infrastructure;
+using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Orleans;
using Squidex.Web;
@@ -28,18 +29,23 @@ namespace Squidex.Config.Orleans
{
builder.AddOrleansPubSub();
- builder.ConfigureServices(siloServices =>
+ builder.ConfigureServices(services =>
{
- siloServices.AddSingletonAs()
+ services.AddScoped(typeof(IGrainState<>), typeof(Infrastructure.Orleans.GrainState<>));
+
+ services.AddSingletonAs()
.As();
- siloServices.AddSingletonAs()
+ services.AddSingletonAs()
.As();
- siloServices.AddScopedAs()
+ services.AddScopedAs()
.As();
- siloServices.AddScoped(typeof(IGrainState<>), typeof(Infrastructure.Orleans.GrainState<>));
+ services.AddInitializer("Serializer (Orleans)", serializer =>
+ {
+ J.DefaultSerializer = serializer;
+ }, -1);
});
builder.ConfigureApplicationParts(parts =>
diff --git a/backend/src/Squidex/Config/Web/WebExtensions.cs b/backend/src/Squidex/Config/Web/WebExtensions.cs
index 7b362c930..27225cc18 100644
--- a/backend/src/Squidex/Config/Web/WebExtensions.cs
+++ b/backend/src/Squidex/Config/Web/WebExtensions.cs
@@ -44,15 +44,27 @@ namespace Squidex.Config.Web
return app;
}
- public static IApplicationBuilder UseSquidexTracking(this IApplicationBuilder app)
+ public static IApplicationBuilder UseSquidexLogging(this IApplicationBuilder app)
{
- app.UseMiddleware();
app.UseMiddleware();
+
+ return app;
+ }
+
+ public static IApplicationBuilder UseSquidexUsage(this IApplicationBuilder app)
+ {
app.UseMiddleware();
return app;
}
+ public static IApplicationBuilder UseSquidexExceptionHandling(this IApplicationBuilder app)
+ {
+ app.UseMiddleware();
+
+ return app;
+ }
+
public static IApplicationBuilder UseSquidexHealthCheck(this IApplicationBuilder app)
{
var serializer = app.ApplicationServices.GetRequiredService();
diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs
index 23edaef7f..33a7be3ac 100644
--- a/backend/src/Squidex/Config/Web/WebServices.cs
+++ b/backend/src/Squidex/Config/Web/WebServices.cs
@@ -27,9 +27,6 @@ namespace Squidex.Config.Web
{
public static void AddSquidexMvcWithPlugins(this IServiceCollection services, IConfiguration config)
{
- services.AddDefaultWebServices(config);
- services.AddDefaultForwardRules();
-
services.AddSingletonAs(c => new ExposedValues(c.GetRequiredService>().Value, config, typeof(WebServices).Assembly))
.AsSelf();
diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj
index f55a965cf..be2c518ed 100644
--- a/backend/src/Squidex/Squidex.csproj
+++ b/backend/src/Squidex/Squidex.csproj
@@ -72,14 +72,14 @@
-
-
-
-
-
+
+
+
+
+
-
+
diff --git a/backend/src/Squidex/Startup.cs b/backend/src/Squidex/Startup.cs
index 4823d21a5..224d63364 100644
--- a/backend/src/Squidex/Startup.cs
+++ b/backend/src/Squidex/Startup.cs
@@ -33,12 +33,16 @@ namespace Squidex
{
services.AddHttpClient();
services.AddMemoryCache();
+ services.AddHealthChecks();
services.AddNonBreakingSameSiteCookies();
+ services.AddDefaultWebServices(config);
+ services.AddDefaultForwardRules();
+ services.AddSquidexImageResizing(config);
+ services.AddSquidexAssetInfrastructure(config);
+ services.AddSquidexSerializers();
services.AddSquidexMvcWithPlugins(config);
-
services.AddSquidexApps(config);
- services.AddSquidexAssetInfrastructure(config);
services.AddSquidexAssets(config);
services.AddSquidexAuthentication(config);
services.AddSquidexBackups();
@@ -62,7 +66,6 @@ namespace Squidex
services.AddSquidexRules(config);
services.AddSquidexSchemas();
services.AddSquidexSearch();
- services.AddSquidexSerializers();
services.AddSquidexStoreServices(config);
services.AddSquidexSubscriptions(config);
services.AddSquidexTelemetry(config);
@@ -77,10 +80,12 @@ namespace Squidex
app.UseDefaultPathBase();
app.UseDefaultForwardRules();
- app.UseSquidexCacheKeys();
app.UseSquidexHealthCheck();
app.UseSquidexRobotsTxt();
- app.UseSquidexTracking();
+ app.UseSquidexCacheKeys();
+ app.UseSquidexExceptionHandling();
+ app.UseSquidexUsage();
+ app.UseSquidexLogging();
app.UseSquidexLocalization();
app.UseSquidexLocalCache();
app.UseSquidexCors();
diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json
index 4d1538ab7..e35b1cf3a 100644
--- a/backend/src/Squidex/appsettings.json
+++ b/backend/src/Squidex/appsettings.json
@@ -254,7 +254,10 @@
// Create one folder per app.
//
// WARNING: If you change this parameter, previous assets are not available anymore.
- "folderPerApp": false
+ "folderPerApp": false,
+
+ // Points to another Squidex instance, which should be configured as resizer.
+ "resizerUrl": ""
},
"logging": {
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs
index 12d723367..0a18ddf28 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs
@@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
var file = new NoopAssetFile();
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A._, file.MimeType, default))
- .Returns(new ImageInfo(100, 100, false, ImageFormat.PNG));
+ .Returns(new ImageInfo(100, 100, ImageOrientation.None, ImageFormat.PNG));
await HandleAsync(new UploadAppImage { File = file }, None.Value);
diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
index 5ae393426..0942d8555 100644
--- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
+++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs
@@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var command = new CreateAsset { File = file };
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, default))
- .Returns(new ImageInfo(800, 600, false, ImageFormat.PNG));
+ .Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG));
await sut.EnhanceAsync(command);
@@ -72,11 +72,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var command = new CreateAsset { File = file };
- A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, default))
- .Returns(new ImageInfo(600, 800, true, ImageFormat.PNG));
+ A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(A._, file.MimeType, default))
+ .Returns(new ImageInfo(800, 600, ImageOrientation.None, ImageFormat.PNG));
- A.CallTo(() => assetThumbnailGenerator.FixOrientationAsync(stream, file.MimeType, A._, default))
- .Returns(new ImageInfo(800, 600, true, ImageFormat.PNG));
+ A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream, file.MimeType, default))
+ .Returns(new ImageInfo(600, 800, ImageOrientation.BottomRight, ImageFormat.PNG)).Once();
await sut.EnhanceAsync(command);
diff --git a/backend/tests/docker-compose.yml b/backend/tests/docker-compose.yml
index ff834456a..f343019e8 100644
--- a/backend/tests/docker-compose.yml
+++ b/backend/tests/docker-compose.yml
@@ -21,6 +21,16 @@ services:
- SCRIPTING__TIMEOUTSCRIPT=00:00:10
- SCRIPTING__TIMEOUTEXECUTION=00:00:10
- GRAPHQL__CACHEDURATION=0
+ - ASSETS_RESIZERURL=http://resizer:8081
+ networks:
+ - internal
+ depends_on:
+ - mongo
+
+ resizer:
+ image: squidex/resizer:dev-2
+ ports:
+ - "8081:80"
networks:
- internal
depends_on: