start new controllers
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
public class BackofficeFilterAttribute : IActionFilter
|
||||
{
|
||||
const string SCOPE_KEY = "scope";
|
||||
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
Type type = context.Controller.GetType();
|
||||
|
||||
if (typeof(ZeroBackofficeController).IsAssignableFrom(type))
|
||||
{
|
||||
if (context.HttpContext.Request.Query.TryGetValue(SCOPE_KEY, out var scope))
|
||||
{
|
||||
(context.Controller as ZeroBackofficeController).OnScopeChanged(scope.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
public class CanEditAttribute : TypeFilterAttribute
|
||||
{
|
||||
public CanEditAttribute() : base(typeof(CanEditAttributeImpl)) { }
|
||||
|
||||
|
||||
private class CanEditAttributeImpl : IAsyncResultFilter
|
||||
{
|
||||
//IToken token;
|
||||
|
||||
|
||||
public CanEditAttributeImpl()//IToken token)
|
||||
{
|
||||
//this.token = token;
|
||||
}
|
||||
|
||||
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
||||
{
|
||||
if (context.Result is JsonResult)
|
||||
{
|
||||
JsonResult result = context.Result as JsonResult;
|
||||
|
||||
//if (result.Value is ObsoleteEditModel)
|
||||
//{
|
||||
// ObsoleteEditModel model = result.Value as ObsoleteEditModel;
|
||||
|
||||
// model.CanEdit = true; // TODO query authorize attrs to get permissions
|
||||
//}
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the request is not cached by the browser
|
||||
/// </summary>
|
||||
public class DisableBrowserCacheAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
base.OnResultExecuting(context);
|
||||
|
||||
var httpResponse = context.HttpContext.Response;
|
||||
|
||||
if (httpResponse.StatusCode != 200) return;
|
||||
|
||||
httpResponse.GetTypedHeaders().CacheControl =
|
||||
new CacheControlHeaderValue()
|
||||
{
|
||||
NoCache = true,
|
||||
MaxAge = TimeSpan.Zero,
|
||||
MustRevalidate = true,
|
||||
NoStore = true
|
||||
};
|
||||
|
||||
httpResponse.Headers[HeaderNames.LastModified] = DateTime.Now.ToString("R"); // Format RFC1123
|
||||
httpResponse.Headers[HeaderNames.Pragma] = "no-cache";
|
||||
httpResponse.Headers[HeaderNames.Expires] = new DateTime(1990, 1, 1, 0, 0, 0).ToString("R");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
public class ModelStateValidationFilterAttribute : IActionFilter
|
||||
{
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (!context.ModelState.IsValid)
|
||||
{
|
||||
context.Result = new BadRequestObjectResult(context.ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//using Microsoft.AspNetCore.Mvc;
|
||||
//using Microsoft.AspNetCore.Mvc.Filters;
|
||||
//using System;
|
||||
//using System.Threading.Tasks;
|
||||
//using zero.Core.Api;
|
||||
//using zero.Web.Models;
|
||||
|
||||
//namespace zero.Web.Controllers
|
||||
//{
|
||||
// public class VerifyTokenAttribute : TypeFilterAttribute
|
||||
// {
|
||||
// public VerifyTokenAttribute(string key = "model") : base(typeof(VerifyTokenAttributeImpl))
|
||||
// {
|
||||
// Arguments = new object[] { key };
|
||||
// }
|
||||
|
||||
|
||||
// private class VerifyTokenAttributeImpl : IAsyncActionFilter
|
||||
// {
|
||||
// IToken token;
|
||||
// string key;
|
||||
|
||||
|
||||
// public VerifyTokenAttributeImpl(IToken token, string key)
|
||||
// {
|
||||
// this.token = token;
|
||||
// this.key = key;
|
||||
// }
|
||||
|
||||
|
||||
// public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
// {
|
||||
// if (context.ActionArguments.ContainsKey(key))
|
||||
// {
|
||||
// object argument = context.ActionArguments[key];
|
||||
|
||||
// if (argument is ObsoleteEditModel)
|
||||
// {
|
||||
// ObsoleteEditModel model = (argument as ObsoleteEditModel);
|
||||
// string tokenId = model.Meta?.Token;
|
||||
|
||||
// bool isVerified = await token.Verify(model.Id, tokenId);
|
||||
|
||||
// if (!isVerified)
|
||||
// {
|
||||
// context.Result = new StatusCodeResult(409);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// await next();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,60 @@
|
||||
//using Microsoft.AspNetCore.Mvc;
|
||||
//using Microsoft.AspNetCore.Mvc.Filters;
|
||||
//using System;
|
||||
//using System.Threading.Tasks;
|
||||
//using zero.Core.Api;
|
||||
//using zero.Core.Entities;
|
||||
//using zero.Web.Models;
|
||||
|
||||
//namespace zero.Web.Controllers
|
||||
//{
|
||||
// public class ZeroBackofficeAttribute : TypeFilterAttribute
|
||||
// {
|
||||
// public bool IsTyped { get; set; }
|
||||
|
||||
|
||||
// public ZeroBackofficeAttribute(bool isTyped = true) : base(typeof(ZeroBackofficeAttributeImpl))
|
||||
// {
|
||||
// IsTyped = isTyped;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// private class ZeroBackofficeAttributeImpl : IAsyncResultFilter
|
||||
// {
|
||||
// IToken token;
|
||||
|
||||
|
||||
// public ZeroBackofficeAttributeImpl(IToken token)
|
||||
// {
|
||||
// this.token = token;
|
||||
// }
|
||||
|
||||
// public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
||||
// {
|
||||
// JsonResult result = context.Result as JsonResult;
|
||||
|
||||
// if (result != null)
|
||||
// {
|
||||
// ZeroEntity model = result.Value as ZeroEntity;
|
||||
|
||||
// if (model != null)
|
||||
// {
|
||||
// Type type = result.Value.GetType();
|
||||
|
||||
// EditModel editModel = new EditModel()
|
||||
// {
|
||||
// Entity = result.Value,
|
||||
// Token = await token.Get(model),
|
||||
// IsAppAware = typeof(IAppAwareEntity).IsAssignableFrom(type),
|
||||
// CanBeShared = typeof(IAppAwareShareableEntity).IsAssignableFrom(type),
|
||||
// CanDelete = false, // TODO
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// await next();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
[ApiController]
|
||||
//[ServiceFilter(typeof(ModelStateValidationFilterAttribute))]
|
||||
//[ServiceFilter(typeof(BackofficeFilterAttribute))]
|
||||
public abstract class ZeroBackofficeApiController : ZeroBackofficeController
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
[ZeroAuthorize]
|
||||
[DisableBrowserCache]
|
||||
public abstract class ZeroBackofficeController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an edit model with associated metadata and permissions from a model
|
||||
/// </summary>
|
||||
protected EditModel<T> GetEditModel<T>(T model)
|
||||
{
|
||||
return GetEditModel<T, EditModel<T>>(new EditModel<T>() { Entity = model });
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an edit model with associated metadata and permissions from a model
|
||||
/// </summary>
|
||||
protected TWrapper GetEditModel<T, TWrapper>(TWrapper editModel) where TWrapper : EditModel<T>, new()
|
||||
{
|
||||
editModel.Meta = new()
|
||||
{
|
||||
Token = null,
|
||||
IsShared = false
|
||||
};
|
||||
|
||||
editModel.Permissions = new()
|
||||
{
|
||||
CanCreate = true,
|
||||
CanEdit = true,
|
||||
CanDelete = true
|
||||
};
|
||||
|
||||
return editModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using System.IO;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
public static class ZeroBackofficeControllerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform entities to a preview list
|
||||
/// </summary>
|
||||
public static List<PreviewModel> TransformToPreviewModels<T>(this ZeroBackofficeController controller, Dictionary<string, T> items, Action<T, PreviewModel> transform = null) where T : ZeroIdEntity
|
||||
{
|
||||
List<PreviewModel> previews = new();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Value == null)
|
||||
{
|
||||
previews.Add(new PreviewModel()
|
||||
{
|
||||
HasError = true,
|
||||
Icon = "fth-alert-circle color-red",
|
||||
Id = item.Key,
|
||||
Name = "@errors.preview.notfound",
|
||||
Text = "@errors.preview.notfound_text"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
PreviewModel model = new() { Id = item.Value.Id };
|
||||
|
||||
if (item.Value is ZeroEntity)
|
||||
{
|
||||
model.Name = (item.Value as ZeroEntity).Name;
|
||||
}
|
||||
|
||||
transform?.Invoke(item.Value, model);
|
||||
previews.Add(model);
|
||||
}
|
||||
|
||||
return previews;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transform entities to a select list
|
||||
/// </summary>
|
||||
public static List<SelectModel> TransformToSelectModels<T>(this ZeroBackofficeController controller, IEnumerable<T> enumerable, Action<T, SelectModel> transform = null) where T : ZeroIdEntity
|
||||
{
|
||||
List<SelectModel> items = new();
|
||||
|
||||
foreach (T item in enumerable)
|
||||
{
|
||||
SelectModel model = new()
|
||||
{
|
||||
Id = item.Id
|
||||
};
|
||||
|
||||
if (item is ZeroEntity)
|
||||
{
|
||||
model.Name = (item as ZeroEntity).Name;
|
||||
model.IsActive = (item as ZeroEntity).IsActive;
|
||||
}
|
||||
|
||||
transform?.Invoke(item, model);
|
||||
|
||||
items.Add(model);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides a file stream for download in the browser
|
||||
/// </summary>
|
||||
public static IActionResult DownloadFile(this ZeroBackofficeController controller, Stream stream, string filename)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
// TODO add success property + return error response
|
||||
}
|
||||
|
||||
if (filename.Contains("{date}"))
|
||||
{
|
||||
filename = filename.Replace("{date}", DateTimeOffset.Now.ToString("yyyy-MM-dd"));
|
||||
}
|
||||
|
||||
var provider = new FileExtensionContentTypeProvider();
|
||||
if (filename == null || !provider.TryGetContentType(Path.GetFileName(filename), out string contentType))
|
||||
{
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
controller.Response.Headers.Add("zero-filename", filename);
|
||||
|
||||
return controller.File(stream, contentType, filename, true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides a file stream to the browser
|
||||
/// </summary>
|
||||
public static IActionResult File(this ZeroBackofficeController controller, FileStorage.FileResult file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return controller.NotFound();
|
||||
}
|
||||
|
||||
FileStream stream = System.IO.File.OpenRead(file.Path);
|
||||
return controller.File(stream, file.ContentType, file.Filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace zero.Backoffice.Controllers;
|
||||
|
||||
public class ZeroBackofficeControllerModelConvention : IApplicationModelConvention
|
||||
{
|
||||
readonly AttributeRouteModel RouteModel;
|
||||
|
||||
readonly Type BaseClass = typeof(ZeroBackofficeController);
|
||||
|
||||
|
||||
public ZeroBackofficeControllerModelConvention(string backofficePath)
|
||||
{
|
||||
RouteModel = new AttributeRouteModel(new RouteAttribute(backofficePath.TrimEnd('/') + "/api/[controller]"));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Configure routing model for all backoffice controllers
|
||||
/// </summary>
|
||||
public void Apply(ApplicationModel application)
|
||||
{
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
bool hasAttributeRouteModels = controller.Selectors.Any(selector => selector.AttributeRouteModel != null);
|
||||
|
||||
if (controller.ControllerType.IsSubclassOf(BaseClass))
|
||||
{
|
||||
controller.Selectors[0].AttributeRouteModel = RouteModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using zero.Core.Api;
|
||||
using zero.Setup;
|
||||
|
||||
namespace zero.Backoffice.Endpoints;
|
||||
|
||||
[ZeroAuthorize(false)]
|
||||
public class ZeroSetupController : Controller
|
||||
{
|
||||
ISetupApi Api;
|
||||
IWebHostEnvironment Env;
|
||||
IZeroOptions Options;
|
||||
|
||||
|
||||
public ZeroSetupController(ISetupApi api, IWebHostEnvironment env, IZeroOptions options)
|
||||
{
|
||||
Api = api;
|
||||
Env = env;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
//if (!Options.ZeroVersion.IsNullOrEmpty())
|
||||
//{
|
||||
// return Redirect(Options.BackofficePath);
|
||||
//}
|
||||
|
||||
return View("/Views/Zero/Setup.cshtml");
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Install([FromBody] SetupModel model)
|
||||
{
|
||||
model.ContentRootPath = Env.ContentRootPath;
|
||||
|
||||
EntityResult<SetupModel> result = await Api.Install(model);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
object value = String.Join("\n\n", result.Errors.Select(error => error.Message + "\n(property: " + error.Property + ")"));
|
||||
return StatusCode(500, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Identity;
|
||||
|
||||
namespace zero.Web.Controllers
|
||||
{
|
||||
[ZeroAuthorize(false)]
|
||||
public class ZeroVueController : BackofficeController
|
||||
{
|
||||
private IZeroVue ZeroVue { get; set; }
|
||||
|
||||
public ZeroVueController(IZeroVue zeroVue)
|
||||
{
|
||||
ZeroVue = zeroVue;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Config()
|
||||
{
|
||||
JsonSerializerSettings settings = new();
|
||||
settings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy()));
|
||||
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||
settings.TypeNameHandling = TypeNameHandling.None;
|
||||
|
||||
return new JsonResult(await ZeroVue.Config(), settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Raven.Client.Documents.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core;
|
||||
using zero.Core.Entities;
|
||||
|
||||
|
||||
namespace zero.Backoffice;
|
||||
|
||||
public abstract class _ZeroBackofficeCollectionController<TEntity, TCollection> : ZeroBackofficeController
|
||||
where TEntity : ZeroIdEntity, new()
|
||||
where TCollection : ICollectionOperations<TEntity>
|
||||
{
|
||||
protected TCollection Collection { get; private set; }
|
||||
|
||||
[Obsolete]
|
||||
protected Func<IRavenQueryable<TEntity>, IRavenQueryable<TEntity>> DefaultQuery { get; set; }
|
||||
|
||||
protected Action<TEntity, PreviewModel> PreviewTransform { get; set; }
|
||||
|
||||
protected Action<TEntity, SelectModel> PickerTransform { get; set; }
|
||||
|
||||
|
||||
public ZeroBackofficeCollectionController(TCollection collection)
|
||||
{
|
||||
Collection = collection;
|
||||
}
|
||||
|
||||
public override void OnScopeChanged(string scope)
|
||||
{
|
||||
//Collection.ApplyScope(scope);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<EditModel<TEntity>> GetById([FromQuery] string id, [FromQuery] string changeVector = null) => Edit(await Collection.Load(id, changeVector));
|
||||
|
||||
|
||||
public virtual async Task<Dictionary<string, TEntity>> GetByIds([FromQuery] string[] ids) => await Collection.Load(ids);
|
||||
|
||||
|
||||
public virtual async Task<EditModel<TEntity>> GetEmpty() => Edit(await Collection.Empty());
|
||||
|
||||
|
||||
public virtual async Task<Paged<TEntity>> GetByQuery([FromQuery] ListBackofficeQuery<TEntity> query)
|
||||
{
|
||||
return await Collection.Load(query);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<Paged<Revision>> GetRevisions([FromQuery] string id, [FromQuery] ListBackofficeQuery<TEntity> query)
|
||||
{
|
||||
return null; // TODO
|
||||
//return await Collection.GetRevisions(id, query.Page, query.PageSize);
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<IEnumerable<SelectModel>> GetForPicker() => await SelectList(Collection.Stream(), PickerTransform);
|
||||
|
||||
|
||||
public virtual async Task<IList<PreviewModel>> GetPreviews([FromQuery] List<string> ids) => Previews(await Collection.Load(ids), PreviewTransform);
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public virtual async Task<EntityResult<TEntity>> Save([FromBody] TEntity model) => await Collection.Save(model);
|
||||
|
||||
|
||||
[HttpDelete]
|
||||
public virtual async Task<EntityResult<TEntity>> Delete([FromQuery] string id) => await Collection.Delete(id);
|
||||
}
|
||||
Reference in New Issue
Block a user