Merge branch 'newgen' of https://github.com/ceee/zero into newgen
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
Support for ASP.NET Core Identity was added to your project.
|
||||
|
||||
For setup and configuration information, see https://go.microsoft.com/fwlink/?linkid=2116645.
|
||||
@@ -69,7 +69,6 @@ public static class ValidatorExtensions
|
||||
/// Check if this reference exists and is an entity which can be referenced
|
||||
/// </summary>
|
||||
public static IRuleBuilderOptions<T, string> Exists<T, TCollection>(this IRuleBuilder<T, string> ruleBuilder, IRavenOperations ops)
|
||||
where T : ZeroIdEntity
|
||||
where TCollection : ZeroIdEntity
|
||||
{
|
||||
return ruleBuilder.MustAsync(async (entity, id, context, cancellation) =>
|
||||
|
||||
@@ -111,7 +111,8 @@ public partial class RavenOperations : IRavenOperations
|
||||
/// <inheritdoc />
|
||||
public virtual T WhenActive<T>(T model) where T : ZeroIdEntity, new()
|
||||
{
|
||||
return model != null && (model is not ZeroEntity || (model as ZeroEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default;
|
||||
return model; // TODO should we really use this? I tried to get data in a custom backend but couldn't because of this
|
||||
//return model != null && (model is not ZeroEntity || (model as ZeroEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ internal class ZeroRavenModule : ZeroModule
|
||||
services.AddTransient<IRavenOperations, RavenOperations>();
|
||||
services.AddScoped<IInterceptors, Interceptors>();
|
||||
|
||||
services.AddScoped<IZeroIdentityStoreDbProvider, RavenIdentityStoreDbProvider>();
|
||||
services.AddScoped<IZeroMediaStoreDbProvider, RavenMediaStoreDbProvider>();
|
||||
services.AddScoped<IZeroNumberStoreDbProvider, RavenNumberStoreDbProvider>();
|
||||
services.Replace<IZeroIdentityStoreDbProvider, RavenIdentityStoreDbProvider>(ServiceLifetime.Scoped);
|
||||
services.Replace<IZeroMediaStoreDbProvider, RavenMediaStoreDbProvider>(ServiceLifetime.Scoped);
|
||||
services.Replace<IZeroNumberStoreDbProvider, RavenNumberStoreDbProvider>(ServiceLifetime.Scoped);
|
||||
|
||||
services.AddOptions<FlavorOptions>();
|
||||
services.AddOptions<RavenOptions>().Bind(configuration.GetSection("Zero:Raven"));
|
||||
|
||||
@@ -3,23 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31912.275
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Commerce", "plugins\zero.Commerce\zero.Commerce.csproj", "{63A8AD15-15F0-4555-BD62-C38B4465FFC1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Stories", "plugins\zero.Stories\zero.Stories.csproj", "{C23CF90A-DB90-427F-816C-0E2FE20E29D0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Forms", "plugins\zero.Forms\zero.Forms.csproj", "{A8E5F34F-5400-4F92-96C6-7F91645BC220}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{6FAA92A8-CA1A-48EA-9857-C9092971B4D1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Core", "zero.Core\zero.Core.csproj", "{CED2A7FA-8D35-4A2A-AF57-8B95307547CB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zero", "zero\zero.csproj", "{33CD6E46-CD81-42C3-9019-C0EAD557EE99}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero", "zero\zero.csproj", "{33CD6E46-CD81-42C3-9019-C0EAD557EE99}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "legacy", "legacy", "{93A1CCEB-A323-445E-8116-4575C5939B46}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zero.Raven", "zero.Raven\zero.Raven.csproj", "{DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Raven", "zero.Raven\zero.Raven.csproj", "{DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zero.Raven.IdentityDemo", "zero.Raven.IdentityDemo\zero.Raven.IdentityDemo.csproj", "{05C829D6-3052-40DB-9282-3BDFCDAE0F05}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Raven.IdentityDemo", "zero.Raven.IdentityDemo\zero.Raven.IdentityDemo.csproj", "{05C829D6-3052-40DB-9282-3BDFCDAE0F05}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -27,18 +19,6 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{63A8AD15-15F0-4555-BD62-C38B4465FFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{63A8AD15-15F0-4555-BD62-C38B4465FFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{63A8AD15-15F0-4555-BD62-C38B4465FFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{63A8AD15-15F0-4555-BD62-C38B4465FFC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C23CF90A-DB90-427F-816C-0E2FE20E29D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C23CF90A-DB90-427F-816C-0E2FE20E29D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C23CF90A-DB90-427F-816C-0E2FE20E29D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C23CF90A-DB90-427F-816C-0E2FE20E29D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A8E5F34F-5400-4F92-96C6-7F91645BC220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A8E5F34F-5400-4F92-96C6-7F91645BC220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8E5F34F-5400-4F92-96C6-7F91645BC220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8E5F34F-5400-4F92-96C6-7F91645BC220}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -60,9 +40,6 @@ Global
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{63A8AD15-15F0-4555-BD62-C38B4465FFC1} = {6FAA92A8-CA1A-48EA-9857-C9092971B4D1}
|
||||
{C23CF90A-DB90-427F-816C-0E2FE20E29D0} = {6FAA92A8-CA1A-48EA-9857-C9092971B4D1}
|
||||
{A8E5F34F-5400-4F92-96C6-7F91645BC220} = {6FAA92A8-CA1A-48EA-9857-C9092971B4D1}
|
||||
{CED2A7FA-8D35-4A2A-AF57-8B95307547CB} = {93A1CCEB-A323-445E-8116-4575C5939B46}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace zero.Communication;
|
||||
|
||||
@@ -26,6 +27,13 @@ public class MessageAggregator : IMessageAggregator
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe<TMessage>(Expression<Func<TMessage, Task>> handle) where TMessage : class, IMessage
|
||||
{
|
||||
Subscription.Add(new InlineMessageSubscription<TMessage>(handle));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Activate<TMessage, TMessageHandler>()
|
||||
where TMessage : class, IMessage
|
||||
@@ -43,6 +51,11 @@ public interface IMessageAggregator
|
||||
/// </summary>
|
||||
Task Publish<TMessage>(TMessage message) where TMessage : class, IMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to a message
|
||||
/// </summary>
|
||||
void Subscribe<TMessage>(Expression<Func<TMessage, Task>> handle) where TMessage : class, IMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes the specified handler to the spified message type
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace zero.Communication;
|
||||
@@ -33,6 +34,38 @@ internal class MessageSubscription<TMessage, TMessageHandler> : IMessageSubscrip
|
||||
}
|
||||
|
||||
|
||||
internal class InlineMessageSubscription<TMessage> : IMessageSubscription
|
||||
where TMessage : class, IMessage
|
||||
{
|
||||
private Expression<Func<TMessage, Task>> _handler;
|
||||
|
||||
public InlineMessageSubscription(Expression<Func<TMessage, Task>> handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
|
||||
public bool CanDeliver<T>()
|
||||
{
|
||||
return typeof(TMessage).GetTypeInfo().IsAssignableFrom(typeof(T));
|
||||
}
|
||||
|
||||
public async Task Deliver(IServiceProvider serviceProvider, object message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
if (!(message is TMessage))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(message)} must be of type '{typeof(TMessage).FullName}'");
|
||||
}
|
||||
|
||||
await _handler.Compile()((TMessage)message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IMessageSubscription
|
||||
{
|
||||
bool CanDeliver<TMessage>();
|
||||
|
||||
@@ -9,6 +9,9 @@ public class ZeroOptions : IZeroOptions
|
||||
/// <inheritdoc />
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Language { get; set; } = "en_US";
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan TokenExpiration { get; set; }
|
||||
|
||||
@@ -43,6 +46,11 @@ public interface IZeroOptions
|
||||
/// </summary>
|
||||
string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ISO Code for language
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expiration of a generated change token for an entity
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace zero.Localization;
|
||||
@@ -11,7 +8,7 @@ public class ConfigurationLocalizer : Localizer
|
||||
private IConfiguration _configuration;
|
||||
|
||||
|
||||
public ConfigurationLocalizer(IConfiguration configuration, IOptionsMonitor<LocalizationOptions> options) : base()
|
||||
public ConfigurationLocalizer(IConfiguration configuration, ICultureResolver cultureResolver, IOptionsMonitor<LocalizationOptions> options) : base(cultureResolver)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
@@ -19,7 +16,7 @@ public class ConfigurationLocalizer : Localizer
|
||||
|
||||
protected override Translation LoadTranslation(string key)
|
||||
{
|
||||
IConfigurationSection section = _configuration.GetSection($"Zero:Localization:{key}");
|
||||
IConfigurationSection section = _configuration.GetSection($"Zero:Localization:{LanguageCode}:{key}");
|
||||
|
||||
if (section == null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace zero.Localization;
|
||||
|
||||
public class CultureChangeMessage : IMessage
|
||||
{
|
||||
public CultureInfo Culture { get; set; }
|
||||
}
|
||||
@@ -1,57 +1,117 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace zero.Localization;
|
||||
|
||||
public class CultureResolver : ICultureResolver
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public CultureInfo Current { get; protected set; }
|
||||
|
||||
protected ILogger<CultureResolver> Logger { get; private set; }
|
||||
|
||||
protected IMessageAggregator MessageAggregator { get; private set; }
|
||||
|
||||
public CultureResolver(ILogger<CultureResolver> logger)
|
||||
|
||||
public CultureResolver(ILogger<CultureResolver> logger, IMessageAggregator messageAggregator)
|
||||
{
|
||||
Logger = logger;
|
||||
MessageAggregator = messageAggregator;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<CultureInfo> Resolve(IZeroContext context)
|
||||
{
|
||||
//var session = context.Store.Session();
|
||||
//Language language = await session.Query<Language>().FirstOrDefaultAsync();
|
||||
string isoCode = "de-AT";
|
||||
if (!TryConvert(context.Options.Language, out CultureInfo culture))
|
||||
{
|
||||
culture = CultureInfo.CurrentCulture;
|
||||
}
|
||||
|
||||
Set(culture);
|
||||
|
||||
return Task.FromResult(culture);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryConvert(string isoCode, out CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
CultureInfo culture = CultureInfo.CreateSpecificCulture(isoCode);
|
||||
culture = CultureInfo.CreateSpecificCulture(isoCode.Replace('_', '-'));
|
||||
|
||||
if (culture.ThreeLetterISOLanguageName.IsNullOrEmpty())
|
||||
{
|
||||
throw new Exception("ThreeLetterISOLanguageName is empty");
|
||||
}
|
||||
if (culture.ThreeLetterISOLanguageName == "ivl")
|
||||
{
|
||||
throw new Exception("Invariant language is not allowed");
|
||||
}
|
||||
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
CultureInfo.CurrentUICulture = culture;
|
||||
ValidatorOptions.Global.LanguageManager.Culture = culture;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not create culture from Language code {code}", isoCode);
|
||||
return Task.FromResult(CultureInfo.CurrentCulture);
|
||||
Logger.LogWarning(ex, "Could not create culture from Language code {code}", isoCode);
|
||||
culture = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return Task.FromResult(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Set(CultureInfo culture)
|
||||
{
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
CultureInfo.CurrentUICulture = culture;
|
||||
ValidatorOptions.Global.LanguageManager.Culture = culture;
|
||||
Current = culture;
|
||||
MessageAggregator.Publish(new CultureChangeMessage()
|
||||
{
|
||||
Culture = culture
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(Expression<Func<CultureChangeMessage, Task>> handle)
|
||||
{
|
||||
MessageAggregator.Subscribe(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface ICultureResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Current culture
|
||||
/// </summary>
|
||||
CultureInfo Current { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the current application from either the backoffice user (in case it is backoffice request)
|
||||
/// or the domain (in case it is frontend request).
|
||||
/// </summary>
|
||||
Task<CultureInfo> Resolve(IZeroContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert an ISO code to a culture
|
||||
/// </summary>
|
||||
bool TryConvert(string isoCode, out CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Set a new culture for this request
|
||||
/// </summary>
|
||||
void Set(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to culture change
|
||||
/// </summary>
|
||||
void Subscribe(Expression<Func<CultureChangeMessage, Task>> handle);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace zero.Localization;
|
||||
@@ -7,6 +8,25 @@ public abstract class Localizer : ILocalizer
|
||||
{
|
||||
protected ConcurrentDictionary<string, string> Cache { get; private set; } = new();
|
||||
|
||||
protected ICultureResolver CultureResolver { get; private set; }
|
||||
|
||||
protected string LanguageCode { get; set; }
|
||||
|
||||
|
||||
public Localizer(ICultureResolver cultureResolver)
|
||||
{
|
||||
CultureResolver = cultureResolver;
|
||||
CultureResolver.Subscribe(msg => OnCultureChange(msg.Culture));
|
||||
OnCultureChange(CultureResolver.Current);
|
||||
}
|
||||
|
||||
|
||||
Task OnCultureChange(CultureInfo culture)
|
||||
{
|
||||
LanguageCode = culture.Name.Split(new char[] { '_', '-' })[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Text(string key) => Text(key, null);
|
||||
@@ -83,7 +103,7 @@ public abstract class Localizer : ILocalizer
|
||||
}
|
||||
|
||||
public interface ILocalizer
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
@@ -13,4 +13,33 @@ public interface IZeroMediaStoreDbProvider
|
||||
Task<Result<T>> Update<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
|
||||
Task<Result<T>> Delete<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
}
|
||||
|
||||
|
||||
public class EmptyZeroMediaStoreDbProvider : IZeroMediaStoreDbProvider
|
||||
{
|
||||
public Task<Result<T>> Create<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Result<T>> Delete<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Find<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IList<T>> FindAll<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Result<T>> Update<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Web;
|
||||
using SixLabors.ImageSharp.Web.Commands;
|
||||
using SixLabors.ImageSharp.Web.Processors;
|
||||
using System.Globalization;
|
||||
|
||||
namespace zero.Media.ImageSharp.Processors;
|
||||
|
||||
public class RotateWebProcessor : IImageWebProcessor
|
||||
{
|
||||
// <summary>
|
||||
/// The command constant for quality.
|
||||
/// </summary>
|
||||
public const string Rotate = "rotate";
|
||||
|
||||
/// <summary>
|
||||
/// The reusable collection of commands.
|
||||
/// </summary>
|
||||
private static readonly IEnumerable<string> RotateCommands
|
||||
= new[] { Rotate };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<string> Commands { get; } = RotateCommands;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FormattedImage Process(
|
||||
FormattedImage image,
|
||||
ILogger logger,
|
||||
CommandCollection commands,
|
||||
CommandParser parser,
|
||||
CultureInfo culture)
|
||||
{
|
||||
if (commands.Contains(Rotate))
|
||||
{
|
||||
float degrees = parser.ParseValue<float>(commands.GetValueOrDefault(Rotate), culture);
|
||||
|
||||
if (degrees != 0)
|
||||
{
|
||||
image.Image.Mutate(x => x.Rotate(degrees));
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) => true;
|
||||
}
|
||||
@@ -83,6 +83,10 @@ public class MediaManagement : IMediaManagement
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<Result<Media>> DeleteFile(Media file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return Result<Media>.Success();
|
||||
}
|
||||
await FileSystem.DeleteDirectory(file.FileId, true);
|
||||
return await Db.Delete(file);
|
||||
}
|
||||
@@ -90,11 +94,11 @@ public class MediaManagement : IMediaManagement
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<Result<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, Action<Media> onBeforeSave = null, CancellationToken cancellationToken = default)
|
||||
public virtual async Task<Result<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, Action<Media> onBeforeSave = null, bool persist = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Result<Media> result = await Creator.UploadFile(fileStream, filename, folderId, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
if (!result.IsSuccess || !persist)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -180,7 +184,7 @@ public interface IMediaManagement
|
||||
/// <summary>
|
||||
/// Uploads a file and persists it
|
||||
/// </summary>
|
||||
Task<Result<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, Action<Media> onBeforeSave = null, CancellationToken cancellationToken = default);
|
||||
Task<Result<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, Action<Media> onBeforeSave = null, bool persist = true, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get a media folder by id
|
||||
|
||||
@@ -36,20 +36,20 @@ namespace zero.Media
|
||||
/// <summary>
|
||||
/// Uploads a file and persists it
|
||||
/// </summary>
|
||||
public static async Task<Result<Media>> UploadFile(this IMediaManagement media, IFormFile formFile, string folderId = null, Action<Media> onBeforeSave = null, CancellationToken cancellationToken = default)
|
||||
public static async Task<Result<Media>> UploadFile(this IMediaManagement media, IFormFile formFile, string folderId = null, Action<Media> onBeforeSave = null, bool persist = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using Stream stream = formFile.OpenReadStream();
|
||||
return await media.UploadFile(stream, formFile.FileName, folderId, onBeforeSave, cancellationToken);
|
||||
return await media.UploadFile(stream, formFile.FileName, folderId, onBeforeSave, persist, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a file and persists it
|
||||
/// </summary>
|
||||
public static async Task<Result<Media>> UploadFile(this IMediaManagement media, byte[] fileBytes, string filename, string folderId = null, Action<Media> onBeforeSave = null, CancellationToken cancellationToken = default)
|
||||
public static async Task<Result<Media>> UploadFile(this IMediaManagement media, byte[] fileBytes, string filename, string folderId = null, Action<Media> onBeforeSave = null, bool persist = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using Stream stream = new MemoryStream(fileBytes);
|
||||
return await media.UploadFile(stream, filename, folderId, onBeforeSave, cancellationToken);
|
||||
return await media.UploadFile(stream, filename, folderId, onBeforeSave, persist, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Routing;
|
||||
using SixLabors.ImageSharp.Web.Caching;
|
||||
using SixLabors.ImageSharp.Web.DependencyInjection;
|
||||
using zero.Media.ImageSharp;
|
||||
using zero.Numbers;
|
||||
using zero.Media.ImageSharp.Processors;
|
||||
|
||||
namespace zero.Media;
|
||||
|
||||
@@ -20,6 +22,7 @@ internal class ZeroMediaModule : ZeroModule
|
||||
.SetRequestParser<PresetRequestParser>()
|
||||
.ClearProviders()
|
||||
.AddProvider<PhysicalFileProvider>()
|
||||
.AddProcessor<RotateWebProcessor>()
|
||||
.Configure<PhysicalFileSystemCacheOptions>(configuration.GetSection("Zero:Media:ImageSharp:Cache"));
|
||||
|
||||
//configuration.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "Config/imaging.json"), true, true);
|
||||
@@ -35,6 +38,7 @@ internal class ZeroMediaModule : ZeroModule
|
||||
services.AddScoped<IMediaCreator, MediaCreator>();
|
||||
services.AddScoped<IMediaManagement, MediaManagement>();
|
||||
services.AddScoped<IMediaMetadataCache, MediaMetadataCache>();
|
||||
services.AddScoped<IZeroMediaStoreDbProvider, EmptyZeroMediaStoreDbProvider>();
|
||||
|
||||
services.AddOptions<MediaOptions>().Bind(configuration.GetSection("Zero:Media")).Configure(opts =>
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq.Expressions;
|
||||
using zero.Numbers;
|
||||
|
||||
namespace zero.Numbers;
|
||||
|
||||
@@ -15,4 +16,38 @@ public interface IZeroNumberStoreDbProvider
|
||||
Task<Result<T>> Update<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
|
||||
Task<Result<T>> Delete<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
}
|
||||
|
||||
|
||||
public class EmptyZeroNumberStoreDbProvider : IZeroNumberStoreDbProvider
|
||||
{
|
||||
public Task<Result<T>> Create<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Result<T>> Delete<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Find<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IList<T>> FindAll<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Load<T>(string id, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Result<T>> Update<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
+26
-2
@@ -22,7 +22,14 @@ public class Numbers : INumbers
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> Next(string alias, DateTimeOffset? date = null, bool store = true)
|
||||
public Task<string> Next(string alias, DateTimeOffset? date = null, bool store = true)
|
||||
{
|
||||
return Next(alias, date.HasValue ? DateOnly.FromDateTime(date.Value.Date) : null, store);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> Next(string alias, DateOnly? date = null, bool store = true)
|
||||
{
|
||||
Number number = await Get(alias);
|
||||
string key = GetCounterKey(number);
|
||||
@@ -92,10 +99,17 @@ public class Numbers : INumbers
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Compile(string template, long value, int minLength, DateTimeOffset? date = null)
|
||||
{
|
||||
return Compile(template, value, minLength, date.HasValue ? DateOnly.FromDateTime(date.Value.Date) : null);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Compile(string template, long value, int minLength, DateOnly? date = null)
|
||||
{
|
||||
string output = template;
|
||||
MatchCollection matches = templateRegex.Matches(output);
|
||||
DateTimeOffset usedDate = date ?? DateTimeOffset.Now;
|
||||
DateOnly usedDate = date ?? DateOnly.FromDateTime(DateTime.Now);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
@@ -228,6 +242,11 @@ public interface INumbers
|
||||
/// </summary>
|
||||
Task<string> Next(string alias, DateTimeOffset? date = null, bool store = true);
|
||||
|
||||
/// <summary>
|
||||
/// Get the next available number for the specified template
|
||||
/// </summary>
|
||||
Task<string> Next(string alias, DateOnly? date = null, bool store = true);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current counter for the specified template
|
||||
/// </summary>
|
||||
@@ -242,4 +261,9 @@ public interface INumbers
|
||||
/// Renders the final output from the template and the value
|
||||
/// </summary>
|
||||
string Compile(string template, long value, int minLength, DateTimeOffset? date = null);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the final output from the template and the value
|
||||
/// </summary>
|
||||
string Compile(string template, long value, int minLength, DateOnly? date = null);
|
||||
}
|
||||
@@ -12,5 +12,6 @@ internal class ZeroNumberModule : ZeroModule
|
||||
|
||||
services.AddScoped<INumbers, Numbers>();
|
||||
services.AddScoped<IValidator<Number>, NumberValidator>();
|
||||
services.AddScoped<IZeroNumberStoreDbProvider, EmptyZeroNumberStoreDbProvider>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace zero.Routing;
|
||||
|
||||
public class RequestUrlResolver : IRequestUrlResolver
|
||||
{
|
||||
protected IHttpContextAccessor HttpContextAccessor { get; private set; }
|
||||
|
||||
protected ILogger<RequestUrlResolver> Logger { get; private set; }
|
||||
|
||||
static string[] Protocols = new[] { "http://", "https://", "ftp://", "ftps://", "sftp://", "udp://" };
|
||||
|
||||
|
||||
public RequestUrlResolver(IHttpContextAccessor httpContextAccessor, ILogger<RequestUrlResolver> logger)
|
||||
{
|
||||
HttpContextAccessor = httpContextAccessor;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAbsolute(string path)
|
||||
{
|
||||
if (!CanResolve())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Protocols.Any(p => path.StartsWith(p, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ToAbsolute(string path)
|
||||
{
|
||||
if (!CanResolve())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return GetRoot();
|
||||
}
|
||||
|
||||
if (Protocols.Any(p => path.StartsWith(p, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
path = path.Trim().TrimEnd('/');
|
||||
|
||||
if (path.StartsWith("~/"))
|
||||
{
|
||||
path = path.Substring(1);
|
||||
}
|
||||
if (!path.StartsWith("/"))
|
||||
{
|
||||
return GetRoot(includePath: true) + '/' + path;
|
||||
}
|
||||
|
||||
return GetRoot() + path;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetRoot(bool includePath = false)
|
||||
{
|
||||
if (!CanResolve())
|
||||
{
|
||||
Logger.LogWarning("Tried to resolve an URL but no HttpRequest is available");
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpRequest request = HttpContextAccessor.HttpContext.Request;
|
||||
return $"{request.Scheme}://{request.Host.Host}{GetPortUrlPart(request)}{(includePath ? request.PathBase.ToUriComponent() : String.Empty)}";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Require a request in order to resolve the URL
|
||||
/// </summary>
|
||||
protected bool CanResolve() => HttpContextAccessor?.HttpContext?.Request != null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the port as part of the URL
|
||||
/// </summary>
|
||||
protected string GetPortUrlPart(HttpRequest request)
|
||||
{
|
||||
if (!request.Host.Port.HasValue || request.Host.Port == 80 || (request.Host.Port == 443 && request.IsHttps))
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
return ":" + request.Host.Port;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IRequestUrlResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether a path is absolute or relative
|
||||
/// </summary>
|
||||
bool IsAbsolute(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative to an absolute path for the currently used domain
|
||||
/// </summary>
|
||||
string ToAbsolute(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Get protocol + domain and optional port
|
||||
/// </summary>
|
||||
string GetRoot(bool includePath = false);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Routing;
|
||||
|
||||
internal class ZeroRoutingModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<IRequestUrlResolver, RequestUrlResolver>();
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ namespace zero.Utils;
|
||||
|
||||
public class IdGenerator
|
||||
{
|
||||
const string CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const string CHARS_COMPLEX = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-@#.:!?*";
|
||||
const string CHARS_az09 = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const string CHARS_azAZ09x = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-@#.:!?*";
|
||||
const string CHARS_x09 = "0123456789";
|
||||
|
||||
private static Random random = new();
|
||||
|
||||
@@ -19,7 +20,7 @@ public class IdGenerator
|
||||
length = 12;
|
||||
}
|
||||
|
||||
string chars = charset == Charset.az09 ? CHARS : CHARS_COMPLEX;
|
||||
string chars = charset == Charset.az09 ? CHARS_az09 : charset == Charset.x09 ? CHARS_x09 : CHARS_azAZ09x;
|
||||
|
||||
return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
|
||||
|
||||
@@ -107,6 +108,10 @@ public class IdGenerator
|
||||
/// <summary>
|
||||
/// a-z, A-Z, 0-9, _-@#.:!?*
|
||||
/// </summary>
|
||||
azAZ09x = 1
|
||||
azAZ09x = 1,
|
||||
/// <summary>
|
||||
/// 0-9
|
||||
/// </summary>
|
||||
x09 = 2
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using zero.Mvc;
|
||||
using zero.Numbers;
|
||||
using zero.Routing;
|
||||
|
||||
namespace zero;
|
||||
|
||||
@@ -57,7 +58,7 @@ public class ZeroBuilder
|
||||
//Modules.Add<ZeroPageModule>();
|
||||
Modules.Add<ZeroRenderingModule>();
|
||||
Modules.Add<ZeroNumberModule>();
|
||||
//Modules.Add<ZeroRoutingModule>();
|
||||
Modules.Add<ZeroRoutingModule>();
|
||||
|
||||
Modules.ConfigureServices(services, configuration);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user