2021-12-29 15:29:33 +01:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Raven.Client.Documents.Indexes;
|
|
|
|
|
|
|
|
|
|
namespace zero.Api.Abstractions;
|
|
|
|
|
|
|
|
|
|
public class ZeroApiEntityStoreOperations<TModel, TStore>
|
|
|
|
|
where TModel : ZeroEntity, new()
|
|
|
|
|
where TStore : IEntityStore<TModel>
|
|
|
|
|
{
|
|
|
|
|
protected TStore Store { get; set; }
|
|
|
|
|
|
|
|
|
|
IZeroMapper _mapper;
|
|
|
|
|
protected IZeroMapper Mapper => _mapper ?? (_mapper = Controller.HttpContext?.RequestServices?.GetService<IZeroMapper>());
|
|
|
|
|
|
|
|
|
|
protected ZeroApiController Controller { get; private set; }
|
|
|
|
|
|
|
|
|
|
public ZeroApiEntityStoreOperations(ZeroApiController controller, TStore store)
|
|
|
|
|
{
|
|
|
|
|
Controller = controller;
|
|
|
|
|
Store = store;
|
2021-12-30 11:11:34 +01:00
|
|
|
Store.Config.IncludeInactive = true;
|
2021-12-29 15:29:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected string GetActionMethod { get; set; } = "Get";
|
|
|
|
|
|
|
|
|
|
protected virtual string GetAction(TModel model)
|
|
|
|
|
{
|
|
|
|
|
return "/"; // TODO Url.Action does not work anymore,
|
|
|
|
|
// probably due to ZeroApiControllerModelConvention, which rewrites AttributeRouteModel
|
|
|
|
|
//return Url.Action(GetActionMethod, ControllerContext.ActionDescriptor.ControllerName, new { id = model.Id, changeVector = default(string) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<T>> EmptyModel<T>(string flavorAlias = null, Action<T> modify = null)
|
|
|
|
|
{
|
|
|
|
|
TModel model = await Store.Empty(flavorAlias);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
T result = Mapper.Map<TModel, T>(model);
|
|
|
|
|
modify?.Invoke(result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<TModel>> EmptyModel(string flavorAlias = null, Action<TModel> modify = null)
|
|
|
|
|
{
|
|
|
|
|
TModel model = await Store.Empty(flavorAlias);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modify?.Invoke(model);
|
|
|
|
|
return model;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<T>> GetModel<T>(string id, string changeVector = null)
|
|
|
|
|
{
|
|
|
|
|
TModel model = await Store.Load(id, changeVector);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Controller.HttpContext.Items[ApiConstants.ChangeToken] = Store.GetChangeToken(model);
|
|
|
|
|
|
|
|
|
|
return Mapper.Map<TModel, T>(model);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<TModel>> GetModel(string id, string changeVector = null)
|
|
|
|
|
{
|
|
|
|
|
TModel model = await Store.Load(id, changeVector);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Controller.HttpContext.Items[ApiConstants.ChangeToken] = Store.GetChangeToken(model);
|
|
|
|
|
|
|
|
|
|
return model;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Paged>> GetModels<T>(ListQuery<TModel> query)
|
|
|
|
|
{
|
|
|
|
|
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
|
|
|
|
query.SearchSelector ??= x => x.Name;
|
|
|
|
|
Paged<TModel> result = await Store.Load(query.Page, query.PageSize, q => q.Filter(query));
|
|
|
|
|
|
|
|
|
|
return Mapper.Map<TModel, T>(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Paged>> GetModels(ListQuery<TModel> query)
|
|
|
|
|
{
|
|
|
|
|
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
|
|
|
|
query.SearchSelector ??= x => x.Name;
|
|
|
|
|
Paged<TModel> result = await Store.Load(query.Page, query.PageSize, q => q.Filter(query));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Paged>> GetModelsByIndex<T, TIndex>(ListQuery<TModel> query) where TIndex : AbstractCommonApiForIndexes, new()
|
|
|
|
|
{
|
|
|
|
|
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
|
|
|
|
query.SearchSelector ??= x => x.Name;
|
|
|
|
|
Paged<TModel> result = await Store.Load<TIndex>(query.Page, query.PageSize, q => q.Filter(query));
|
|
|
|
|
|
|
|
|
|
return Mapper.Map<TModel, T>(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Paged>> GetModelsByIndex<TIndex>(ListQuery<TModel> query) where TIndex : AbstractCommonApiForIndexes, new()
|
|
|
|
|
{
|
|
|
|
|
query.OrderQuery ??= q => q.OrderByDescending(x => x.CreatedDate);
|
|
|
|
|
query.SearchSelector ??= x => x.Name;
|
|
|
|
|
Paged<TModel> result = await Store.Load<TIndex>(query.Page, query.PageSize, q => q.Filter(query));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> CreateModel<T, TEdit>(T saveModel) where T : ISupportsFlavors
|
|
|
|
|
{
|
|
|
|
|
TModel emptyModel = saveModel.Flavor.IsNullOrEmpty() ? await Store.Empty() : await Store.Empty(saveModel.Flavor);
|
|
|
|
|
TModel model = Mapper.Map(saveModel, emptyModel);
|
|
|
|
|
|
|
|
|
|
Result<TModel> result = await Store.Create(model);
|
|
|
|
|
|
|
|
|
|
bool minimalResponse = Controller.Hints.ResponsePreference == ApiResponsePreference.Minimal;
|
|
|
|
|
|
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
{
|
|
|
|
|
Result<TEdit> mappedResult = Mapper.Map<TModel, TEdit>(result);
|
|
|
|
|
return Controller.Created(GetAction(model), minimalResponse ? null : mappedResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.WithoutModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> CreateModel(TModel saveModel)
|
|
|
|
|
{
|
|
|
|
|
Result<TModel> result = await Store.Create(saveModel);
|
|
|
|
|
|
|
|
|
|
bool minimalResponse = Controller.Hints.ResponsePreference == ApiResponsePreference.Minimal;
|
|
|
|
|
|
|
|
|
|
if (result.IsSuccess)
|
|
|
|
|
{
|
|
|
|
|
return Controller.Created(GetAction(saveModel), minimalResponse ? null : saveModel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.WithoutModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> UpdateModel<T, TEdit>(string id, T updateModel, string changeToken = null) where T : ZeroIdEntity
|
|
|
|
|
{
|
|
|
|
|
if (id != updateModel.Id)
|
|
|
|
|
{
|
|
|
|
|
return Controller.BadRequest(Result.Fail(nameof(id), "@errors.onupdate.noidmatch"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TModel model = await Store.Load(id);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.BadRequest(Result.Fail(nameof(id), "@errors.idnotfound"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string storedChangeToken = Store.GetChangeToken(model);
|
|
|
|
|
|
|
|
|
|
if (!changeToken.IsNullOrEmpty() && storedChangeToken != changeToken)
|
|
|
|
|
{
|
|
|
|
|
return Controller.BadRequest(Result.Fail("@errors.onupdate.changetokenmismatch"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mapper.Map(updateModel, model);
|
|
|
|
|
|
|
|
|
|
Result<TModel> result = await Store.Update(model);
|
|
|
|
|
|
|
|
|
|
if (Controller.Hints.ResponsePreference == ApiResponsePreference.Minimal)
|
|
|
|
|
{
|
|
|
|
|
return result.WithoutModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO add Preference-Applied header, see https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#76-standard-response-headers
|
|
|
|
|
return Mapper.Map<TModel, TEdit>(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> UpdateModel(string id, TModel updateModel, string changeToken = null)
|
|
|
|
|
{
|
|
|
|
|
if (id != updateModel.Id)
|
|
|
|
|
{
|
|
|
|
|
return Controller.BadRequest(Result.Fail(nameof(id), "@errors.onupdate.noidmatch"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO throws error on save: Attempted to associate a different object with id ...
|
|
|
|
|
// we need to map props to new object
|
|
|
|
|
//TModel model = await Store.Load(id);
|
|
|
|
|
|
|
|
|
|
//if (model == null)
|
|
|
|
|
//{
|
|
|
|
|
// return BadRequest(Result.Fail(nameof(id), "@errors.idnotfound"));
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//string storedChangeToken = Store.GetChangeToken(model);
|
|
|
|
|
|
|
|
|
|
//if (!changeToken.IsNullOrEmpty() && storedChangeToken != changeToken)
|
|
|
|
|
//{
|
|
|
|
|
// return BadRequest(Result.Fail("@errors.onupdate.changetokenmismatch"));
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
Result<TModel> result = await Store.Update(updateModel);
|
|
|
|
|
|
|
|
|
|
if (Controller.Hints.ResponsePreference == ApiResponsePreference.Minimal)
|
|
|
|
|
{
|
|
|
|
|
return result.WithoutModel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> SortModels(string[] ids)
|
|
|
|
|
{
|
|
|
|
|
return await Store.Sort(ids);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ActionResult<Result>> DeleteModel(string id)
|
|
|
|
|
{
|
|
|
|
|
TModel model = await Store.Load(id);
|
|
|
|
|
|
|
|
|
|
if (model == null)
|
|
|
|
|
{
|
|
|
|
|
return Controller.NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Result<TModel> result = await Store.Delete(model);
|
|
|
|
|
|
|
|
|
|
return result.WithoutModel();
|
|
|
|
|
}
|
|
|
|
|
}
|