start new user store

This commit is contained in:
2020-03-25 00:47:14 +01:00
parent c9f6b6e571
commit a17ff2190e
9 changed files with 263 additions and 6 deletions
+6
View File
@@ -2,6 +2,9 @@
{
public class BackofficeConfiguration : IBackofficeConfiguration
{
public string DefaultLanguage { get; set; }
public string SystemUserId { get; set; }
public FoldersConfig Folders { get; set; }
@@ -13,6 +16,9 @@
public interface IBackofficeConfiguration
{
string DefaultLanguage { get; set; }
string SystemUserId { get; set; }
FoldersConfig Folders { get; set; }
@@ -29,7 +29,7 @@ namespace zero.Core.Entities
public List<string> RoleIds { get; set; } = new List<string>();
/// <inheritdoc/>
public List<IBackofficeUserClaim> Claims { get; set; } = new List<IBackofficeUserClaim>();
public List<BackofficeUserClaim> Claims { get; set; } = new List<BackofficeUserClaim>();
@@ -105,7 +105,7 @@ namespace zero.Core.Entities
/// <summary>
/// The user's claims, for use in claims-based authentication.
/// </summary>
List<IBackofficeUserClaim> Claims { get; set; }
List<BackofficeUserClaim> Claims { get; set; }
@@ -14,7 +14,7 @@ namespace zero.Core.Entities
public Claim ToClaim() => new Claim(Type, Value);
/// <inheritdoc/>
public IBackofficeUserClaim FromClaim(Claim other)
public BackofficeUserClaim FromClaim(Claim other)
{
Type = other?.Type;
Value = other?.Value;
@@ -39,7 +39,7 @@ namespace zero.Core.Entities
/// <summary>
/// Constructs a new claim with the type and value
/// </summary>
IBackofficeUserClaim FromClaim(Claim other);
BackofficeUserClaim FromClaim(Claim other);
/// <summary>
/// Initializes by copying ClaimType and ClaimValue from the other claim
@@ -11,7 +11,7 @@ namespace zero.Core.Entities
public string Icon { get; set; }
/// <inheritdoc/>
public List<IBackofficeUserClaim> Claims { get; set; } = new List<IBackofficeUserClaim>();
public List<BackofficeUserClaim> Claims { get; set; } = new List<BackofficeUserClaim>();
}
@@ -30,6 +30,6 @@ namespace zero.Core.Entities
/// <summary>
/// The user's claims, for use in claims-based authentication.
/// </summary>
List<IBackofficeUserClaim> Claims { get; set; }
List<BackofficeUserClaim> Claims { get; set; }
}
}
+84
View File
@@ -0,0 +1,84 @@
using FluentValidation.Results;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace zero.Core.Entities
{
[DataContract(Name = "result", Namespace = "")]
public class EntityChangeResult<T>
{
[DataMember(Name = "model")]
public T Model { get; set; }
[DataMember(Name = "success")]
public bool IsSuccess { get; set; }
[DataMember(Name = "errors")]
public IList<EntityChangeResultError> Errors { get; set; } = new List<EntityChangeResultError>();
public EntityChangeResult() { }
public EntityChangeResult(ValidationResult validation)
{
IsSuccess = validation.IsValid;
Errors = validation.Errors.Select(x => new EntityChangeResultError()
{
Property = x.PropertyName,
Message = x.ErrorMessage
}).ToList();
}
public void AddError(string property, string message)
{
IsSuccess = false;
Errors.Add(new EntityChangeResultError()
{
Property = property,
Message = message
});
}
public EntityChangeResult<T> Combine(EntityChangeResult<T> with)
{
if (IsSuccess && !with.IsSuccess)
{
IsSuccess = false;
}
foreach (EntityChangeResultError error in with.Errors)
{
Errors.Add(error);
}
return this;
}
public static EntityChangeResult<T> Success() => new EntityChangeResult<T>() { IsSuccess = true };
public static EntityChangeResult<T> Success(T model) => new EntityChangeResult<T>() { IsSuccess = true, Model = model };
public static EntityChangeResult<T> Fail() => new EntityChangeResult<T>() { };
public static EntityChangeResult<T> Fail(string property, string message)
{
EntityChangeResult<T> result = new EntityChangeResult<T>();
result.AddError(property, message);
return result;
}
public static EntityChangeResult<T> Fail(ValidationResult validation) => new EntityChangeResult<T>(validation);
public static EntityChangeResult<T> Fail(EntityChangeResultError error) => Fail(error.Property, error.Message);
}
[DataContract(Name = "resultError", Namespace = "")]
public class EntityChangeResultError
{
[DataMember(Name = "property")]
public string Property { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
}
}
@@ -0,0 +1,81 @@
using FluentValidation;
using System;
using System.Linq;
namespace zero.Core.Extensions
{
public static class ValidatorExtensions
{
private const char DOT = '.';
private const char KLAMMERAFFE = '@';
private static string HEX_REGEX = "#[0-9a-fA-F]{3,8}";
/// <summary>
/// Validate a color input as HEX (#aabbccdd or #aabbcc or #abc)
/// </summary>
public static IRuleBuilderOptions<T, string> Hex<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Matches(HEX_REGEX).WithMessage("@errors/format_hex");
}
/// <summary>
/// Validate an email
/// </summary>
public static IRuleBuilderOptions<T, string> Email<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must((root, value, context) =>
{
if (value.IsNullOrWhiteSpace())
{
return true;
}
int index = value.IndexOf(KLAMMERAFFE);
if (index < 0 || index == value.Length - 1 || index != value.LastIndexOf(KLAMMERAFFE))
{
return false;
}
return true;
}).WithMessage("@errors/invalid_mail");
}
/// <summary>
/// Validate one or multiple emails
/// </summary>
public static IRuleBuilderOptions<T, string> Emails<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must((root, value, context) =>
{
if (value.IsNullOrWhiteSpace())
{
return true;
}
string[] mails = value.Split(',', ';').Select(x => x.Trim()).Where(x => !x.IsNullOrWhiteSpace()).ToArray();
if (!mails.Any())
{
return false;
}
foreach (string mail in mails)
{
int index = value.IndexOf(KLAMMERAFFE);
if (index < 0 || index == value.Length - 1 || index != value.LastIndexOf(KLAMMERAFFE))
{
return false;
}
}
return true;
}).WithMessage("@errors/invalid_mails");
}
}
}
+61
View File
@@ -0,0 +1,61 @@
using FluentValidation.Results;
using Raven.Client.Documents;
using System;
using System.Threading;
using System.Threading.Tasks;
using zero.Core.Entities;
using zero.Core.Validation;
using zero.Core.Extensions;
namespace zero.Core.Identity
{
public class BackofficeUserStore<TUser, TRole>
where TUser : IBackofficeUser, new()
where TRole : IBackofficeUserRole, new()
{
/// <summary>
/// Gets the database context for this store.
/// </summary>
protected IDocumentStore Raven { get; set; }
/// <summary>
/// Configuration
/// </summary>
protected IBackofficeConfiguration Config { get; private set; }
/// <summary>
/// Currently logged-in user
/// </summary>
protected IBackofficeUser Current { get; private set; }
public BackofficeUserStore(IDocumentStore raven, IBackofficeConfiguration config, IBackofficeUser currentUser)
{
Raven = raven;
Config = config;
}
/// <summary>
/// Adds a new backoffice user
/// </summary>
public async Task<EntityChangeResult<TUser>> Add(TUser model, CancellationToken token = default)
{
// set default language
if (model.LanguageId.IsNullOrEmpty())
{
model.LanguageId = Config.DefaultLanguage;
}
// validate model
ValidationResult validation = await new BackofficeUserValidator(isCreate: true).ValidateAsync(model);
if (!validation.IsValid)
{
return EntityChangeResult<TUser>.Fail(validation);
}
return EntityChangeResult<TUser>.Success();
}
}
}
@@ -0,0 +1,23 @@
using FluentValidation;
using zero.Core.Entities;
using zero.Core.Extensions;
namespace zero.Core.Validation
{
public class BackofficeUserValidator : AbstractValidator<IBackofficeUser>
{
public BackofficeUserValidator(bool isCreate = false)
{
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Email).Email();
RuleFor(x => x.PasswordHash).NotEmpty();
RuleFor(x => x.LanguageId).NotEmpty(); // TODO only allow available languages
RuleFor(x => x.RoleIds).NotEmpty();
if (isCreate)
{
RuleFor(x => x.IsSuper).Equals(false);
}
}
}
}
+2
View File
@@ -6,8 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.2" />
<PackageReference Include="RavenDB.Client" Version="4.2.101" />
</ItemGroup>
</Project>