Merge branch 'newgen' of https://github.com/ceee/zero into newgen

This commit is contained in:
2023-06-06 11:22:19 +02:00
24 changed files with 472 additions and 67 deletions
@@ -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) =>
+2 -1
View File
@@ -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;
}
}
+3 -3
View File
@@ -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 -26
View File
@@ -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>();
+8
View File
@@ -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>
+3 -6
View File
@@ -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; }
}
+72 -12
View File
@@ -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);
}
+21 -1
View File
@@ -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>
+29
View File
@@ -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;
}
+7 -3
View File
@@ -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
+4 -4
View File
@@ -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);
}
+4
View File
@@ -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
View File
@@ -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);
}
+1
View File
@@ -12,5 +12,6 @@ internal class ZeroNumberModule : ZeroModule
services.AddScoped<INumbers, Numbers>();
services.AddScoped<IValidator<Number>, NumberValidator>();
services.AddScoped<IZeroNumberStoreDbProvider, EmptyZeroNumberStoreDbProvider>();
}
}
+128
View File
@@ -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);
}
+12
View File
@@ -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>();
}
}
+9 -4
View File
@@ -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
View File
@@ -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);