start backoffice services (transition from api)
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Raven.Client;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using Raven.Client.Documents.Session;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Attributes;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
using zero.Core.Utils;
|
||||
|
||||
namespace zero.Core.Backoffice
|
||||
{
|
||||
public abstract class BackofficeService<T> : IBackofficeService<T> where T : IZeroEntity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Database { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Zero context
|
||||
/// </summary>
|
||||
protected readonly IZeroContext Context;
|
||||
|
||||
/// <summary>
|
||||
/// Document store
|
||||
/// </summary>
|
||||
protected readonly IZeroStore Store;
|
||||
|
||||
/// <summary>
|
||||
/// The validator
|
||||
/// </summary>
|
||||
protected readonly IValidator<T> Validator;
|
||||
|
||||
|
||||
public BackofficeService(IZeroContext context, IValidator<T> validator = null)
|
||||
{
|
||||
Context = context;
|
||||
Store = context.Store;
|
||||
Validator = validator;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an an async document session
|
||||
/// </summary>
|
||||
protected virtual IAsyncDocumentSession Session()
|
||||
{
|
||||
return Database.IsNullOrWhiteSpace() ? Store.OpenAsyncSession() : Store.OpenAsyncSession(Database);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<T> GetById(string id)
|
||||
{
|
||||
if (id.IsNullOrWhiteSpace())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
using IAsyncDocumentSession session = Session();
|
||||
return await session.LoadAsync<T>(id);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<Dictionary<string, T>> GetByIds(params string[] ids)
|
||||
{
|
||||
using IAsyncDocumentSession session = Session();
|
||||
Dictionary<string, T> models = await session.LoadAsync<T>(ids);
|
||||
Dictionary<string, T> result = new Dictionary<string, T>();
|
||||
|
||||
foreach (string id in ids)
|
||||
{
|
||||
models.TryGetValue(id, out T model);
|
||||
result.Add(id, model);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<ListResult<T>> GetByQuery(ListQuery<T> query)
|
||||
{
|
||||
using IAsyncDocumentSession session = Session();
|
||||
return await session.Query<T>().ToQueriedListAsync(query);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IAsyncEnumerable<T> Stream() => Stream(null);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async IAsyncEnumerable<T> Stream(Func<IRavenQueryable<T>, IRavenQueryable<T>> expression)
|
||||
{
|
||||
using IAsyncDocumentSession session = Session();
|
||||
IRavenQueryable<T> query = session.Query<T>();
|
||||
|
||||
if (expression != null)
|
||||
{
|
||||
query = expression(query);
|
||||
}
|
||||
|
||||
var stream = await session.Advanced.StreamAsync(query);
|
||||
|
||||
while (await stream.MoveNextAsync())
|
||||
{
|
||||
yield return stream.Current.Document;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntityResult<T>> Save(T model)
|
||||
{
|
||||
bool isCreate = false;
|
||||
|
||||
// run validator
|
||||
if (Validator != null)
|
||||
{
|
||||
ValidationResult validation = await Validator.ValidateAsync(model);
|
||||
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
return EntityResult<T>.Fail(validation);
|
||||
}
|
||||
}
|
||||
|
||||
// find all Raven Ids
|
||||
List<ObjectTraverser.Result<GenerateIdAttribute>> ravenIds = ObjectTraverser.FindAttribute<GenerateIdAttribute>(model);
|
||||
|
||||
// set unset Raven Ids
|
||||
foreach (ObjectTraverser.Result<GenerateIdAttribute> item in ravenIds)
|
||||
{
|
||||
string id = item.Property.GetValue(item.Parent, null) as string;
|
||||
if (String.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
item.Property.SetValue(item.Parent, item.Item.Length.HasValue ? IdGenerator.Create(item.Item.Length.Value) : IdGenerator.Create());
|
||||
}
|
||||
}
|
||||
|
||||
// get current user
|
||||
string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId);
|
||||
|
||||
// set default properties
|
||||
if (model.Id.IsNullOrEmpty())
|
||||
{
|
||||
isCreate = true;
|
||||
|
||||
model.CreatedDate = DateTimeOffset.Now;
|
||||
model.CreatedById = userId;
|
||||
|
||||
if (model is ILanguageAwareEntity)
|
||||
{
|
||||
(model as ILanguageAwareEntity).LanguageId = "languages.1-A"; // TODO correct language id
|
||||
}
|
||||
}
|
||||
|
||||
// update name alias and last modified
|
||||
model.Alias = Safenames.Alias(model.Name);
|
||||
model.LastModifiedById = userId;
|
||||
model.LastModifiedDate = DateTimeOffset.Now;
|
||||
model.CreatedById ??= userId;
|
||||
model.Hash ??= IdGenerator.Create();
|
||||
|
||||
using IAsyncDocumentSession session = Session();
|
||||
session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false);
|
||||
|
||||
await session.StoreAsync(model);
|
||||
|
||||
//await Backoffice.Messages.Publish(new EntitySavedMessage<T>()
|
||||
//{
|
||||
// Id = model.Id,
|
||||
// IsCreate = isCreate,
|
||||
// IsDelete = false,
|
||||
// Model = model,
|
||||
// Session = session
|
||||
//});
|
||||
|
||||
await session.SaveChangesAsync();
|
||||
|
||||
return EntityResult<T>.Success(model);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<EntityResult<T>> Delete(T model) => await DeleteById(model?.Id);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<EntityResult<T>> DeleteById(string id)
|
||||
{
|
||||
using IAsyncDocumentSession session = Session();
|
||||
session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false);
|
||||
|
||||
T entity = await session.LoadAsync<T>(id);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
return EntityResult<T>.Fail("@errors.ondelete.idnotfound");
|
||||
}
|
||||
|
||||
session.Delete(entity);
|
||||
|
||||
await session.SaveChangesAsync();
|
||||
|
||||
return EntityResult<T>.Success();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<int> Delete(params T[] models) => await DeleteByIds(models.Select(x => x.Id).ToArray());
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<int> DeleteByIds(params string[] ids)
|
||||
{
|
||||
int successCount = 0;
|
||||
|
||||
foreach (string id in ids)
|
||||
{
|
||||
EntityResult<T> result = await DeleteById(id);
|
||||
successCount += result.IsSuccess ? 1 : 0;
|
||||
}
|
||||
|
||||
return successCount;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<EntityResult<T>> Purge(string querySuffix = null, Parameters parameters = null)
|
||||
{
|
||||
await Store.PurgeAsync<T>(Database, querySuffix, parameters);
|
||||
return EntityResult<T>.Success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IBackofficeService<T> where T : IZeroEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The database to operate on.
|
||||
/// Is null by default, which uses the database from the resolved application.
|
||||
/// </summary>
|
||||
string Database { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get an entity by Id
|
||||
/// </summary>
|
||||
Task<T> GetById(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Get entities by ids
|
||||
/// </summary>
|
||||
Task<Dictionary<string, T>> GetByIds(params string[] ids);
|
||||
|
||||
/// <summary>
|
||||
/// Get entities by query
|
||||
/// </summary>
|
||||
Task<ListResult<T>> GetByQuery(ListQuery<T> query);
|
||||
|
||||
/// <summary>
|
||||
/// Stream the collection
|
||||
/// </summary>
|
||||
IAsyncEnumerable<T> Stream();
|
||||
|
||||
/// <summary>
|
||||
/// Stream the collection
|
||||
/// </summary>
|
||||
IAsyncEnumerable<T> Stream(Func<IRavenQueryable<T>, IRavenQueryable<T>> expression);
|
||||
|
||||
/// <summary>
|
||||
/// Updates or creates an entity with an optional validator
|
||||
/// </summary>
|
||||
Task<EntityResult<T>> Save(T model);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity
|
||||
/// </summary>
|
||||
Task<EntityResult<T>> Delete(T model);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes entities
|
||||
/// </summary>
|
||||
Task<int> Delete(params T[] models);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity by Id
|
||||
/// </summary>
|
||||
Task<EntityResult<T>> DeleteById(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes entities by Id
|
||||
/// </summary>
|
||||
Task<int> DeleteByIds(params string[] ids);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a whole collection (with an optional query suffix, i.e. a where statement)
|
||||
/// </summary>
|
||||
Task<EntityResult<T>> Purge(string querySuffix = null, Parameters parameters = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FluentValidation;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using System.Collections.Generic;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
|
||||
namespace zero.Core.Backoffice
|
||||
{
|
||||
public class CountriesBackofficeService : BackofficeService<ICountry>, ICountriesBackofficeService
|
||||
{
|
||||
public CountriesBackofficeService(IZeroContext context, IValidator<ICountry> validator) : base(context, validator) { }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IAsyncEnumerable<ICountry> Stream()
|
||||
{
|
||||
return base.Stream(q => q.OrderByDescending(x => x.IsPreferred).ThenBy(x => x.Name));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
//public async Task<ListResult<ICountry>> GetByQuery(string languageId, ListQuery<ICountry> query)
|
||||
//{
|
||||
// query.SearchSelector = country => country.Name;
|
||||
|
||||
// using IAsyncDocumentSession session = Store.OpenAsyncSession();
|
||||
// return await session.Query<ICountry>()
|
||||
// .OrderByDescending(x => x.IsPreferred)
|
||||
// .ThenBy(x => x.Name)
|
||||
// .ToQueriedListAsync(query);
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
public interface ICountriesBackofficeService : IBackofficeService<ICountry>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,14 @@ namespace zero.Core
|
||||
/// <inheritdoc />
|
||||
public IResolvedRoute ResolvedRoute { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IZeroStore Store { get; private set; }
|
||||
|
||||
|
||||
protected IApplicationResolver AppResolver { get; private set; }
|
||||
|
||||
protected ILogger<ZeroContext> Logger { get; private set; }
|
||||
|
||||
protected IZeroStore Store { get; private set; }
|
||||
|
||||
|
||||
private bool _resolved = false;
|
||||
|
||||
@@ -124,6 +125,11 @@ namespace zero.Core
|
||||
/// </summary>
|
||||
IZeroOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Document store
|
||||
/// </summary>
|
||||
IZeroStore Store { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Matching (frontend) path route
|
||||
/// </summary>
|
||||
|
||||
@@ -112,6 +112,25 @@ namespace zero.Web.Controllers
|
||||
|
||||
|
||||
|
||||
public async Task<IList<SelectModel>> SelectList<T>(IAsyncEnumerable<T> enumerable) where T : IZeroEntity
|
||||
{
|
||||
List<SelectModel> items = new List<SelectModel>();
|
||||
|
||||
await foreach (T item in enumerable)
|
||||
{
|
||||
items.Add(new SelectModel()
|
||||
{
|
||||
Id = item.Id,
|
||||
Name = item.Name,
|
||||
IsActive = item.IsActive
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<IList<PreviewModel>> Previews<T>(Dictionary<string, T> items, Func<T, Task<PreviewModel>> transform)
|
||||
{
|
||||
IList<PreviewModel> previews = new List<PreviewModel>();
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Backoffice;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
using zero.Core.Identity;
|
||||
using zero.Web.Models;
|
||||
|
||||
@@ -15,34 +11,25 @@ namespace zero.Web.Controllers
|
||||
[ZeroAuthorize(Permissions.Settings.Countries, PermissionsValue.Read)]
|
||||
public class CountriesController : BackofficeController
|
||||
{
|
||||
ICountriesApi Api;
|
||||
ICountriesBackofficeService Service;
|
||||
|
||||
public CountriesController(ICountriesApi api)
|
||||
public CountriesController(ICountriesBackofficeService service)
|
||||
{
|
||||
Api = api;
|
||||
Service = service;
|
||||
}
|
||||
|
||||
|
||||
public async Task<EditModel<ICountry>> GetById([FromQuery] string id) => Edit(await Api.GetById(id));
|
||||
|
||||
public async Task<EditModel<ICountry>> GetById([FromQuery] string id) => Edit(await Service.GetById(id));
|
||||
|
||||
public EditModel<ICountry> GetEmpty([FromServices] ICountry blueprint) => Edit(blueprint);
|
||||
|
||||
public async Task<ListResult<ICountry>> GetAll([FromQuery] ListQuery<ICountry> query) => await Service.GetByQuery(query);
|
||||
|
||||
public async Task<ListResult<ICountry>> GetAll([FromQuery] ListQuery<ICountry> query) => await Api.GetByQuery("languages.1-A", query); // TODO correct language
|
||||
|
||||
|
||||
public async Task<IEnumerable<SelectModel>> GetForPicker() => (await Api.GetAll("languages.1-A")).Select(x => new SelectModel()
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
IsActive = x.IsActive
|
||||
});
|
||||
|
||||
public async Task<IEnumerable<SelectModel>> GetForPicker() => await SelectList(Service.Stream());
|
||||
|
||||
public async Task<IList<PreviewModel>> GetPreviews([FromQuery] List<string> ids)
|
||||
{
|
||||
return Previews(await Api.GetByIds(ids.ToArray()), item => new PreviewModel()
|
||||
return Previews(await Service.GetByIds(ids.ToArray()), item => new PreviewModel()
|
||||
{
|
||||
Id = item.Id,
|
||||
Icon = "flag flag-" + item.Code.ToLowerInvariant(),
|
||||
@@ -52,10 +39,10 @@ namespace zero.Web.Controllers
|
||||
|
||||
|
||||
[ZeroAuthorize(Permissions.Settings.Countries, PermissionsValue.Update)]
|
||||
public async Task<EntityResult<ICountry>> Save([FromBody] ICountry model) => await Api.Save(model);
|
||||
public async Task<EntityResult<ICountry>> Save([FromBody] ICountry model) => await Service.Save(model);
|
||||
|
||||
|
||||
[ZeroAuthorize(Permissions.Settings.Countries, PermissionsValue.Update)]
|
||||
public async Task<EntityResult<ICountry>> Delete([FromQuery] string id) => await Api.Delete(id);
|
||||
public async Task<EntityResult<ICountry>> Delete([FromQuery] string id) => await Service.DeleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Backoffice;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Messages;
|
||||
using zero.Core.Options;
|
||||
@@ -71,6 +72,7 @@ namespace zero.Web.Defaults
|
||||
|
||||
services.AddTransient<IApplicationsApi, ApplicationsApi>();
|
||||
services.AddTransient<ICountriesApi, CountriesApi>();
|
||||
services.AddTransient<ICountriesBackofficeService, CountriesBackofficeService>();
|
||||
services.AddTransient<ILanguagesApi, LanguagesApi>();
|
||||
services.AddTransient<ITranslationsApi, TranslationsApi>();
|
||||
services.AddTransient<IUserApi, UserApi>();
|
||||
|
||||
Reference in New Issue
Block a user