diff --git a/zero.Backoffice/Filters/BackofficeFilterAttribute.cs b/zero.Backoffice/Controllers/Filters/BackofficeFilterAttribute.cs
similarity index 69%
rename from zero.Backoffice/Filters/BackofficeFilterAttribute.cs
rename to zero.Backoffice/Controllers/Filters/BackofficeFilterAttribute.cs
index ca7efd9a..a4ae8893 100644
--- a/zero.Backoffice/Filters/BackofficeFilterAttribute.cs
+++ b/zero.Backoffice/Controllers/Filters/BackofficeFilterAttribute.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.Filters;
-namespace zero.Backoffice.Filters;
+namespace zero.Backoffice.Controllers;
public class BackofficeFilterAttribute : IActionFilter
{
@@ -11,11 +11,11 @@ public class BackofficeFilterAttribute : IActionFilter
{
Type type = context.Controller.GetType();
- if (typeof(BackofficeController).IsAssignableFrom(type))
+ if (typeof(ZeroBackofficeController).IsAssignableFrom(type))
{
if (context.HttpContext.Request.Query.TryGetValue(SCOPE_KEY, out var scope))
{
- (context.Controller as BackofficeController).OnScopeChanged(scope.ToString());
+ (context.Controller as ZeroBackofficeController).OnScopeChanged(scope.ToString());
}
}
}
diff --git a/zero.Backoffice/Filters/CanEditAttribute.cs b/zero.Backoffice/Controllers/Filters/CanEditAttribute.cs
similarity index 95%
rename from zero.Backoffice/Filters/CanEditAttribute.cs
rename to zero.Backoffice/Controllers/Filters/CanEditAttribute.cs
index cb5d6ab8..df111032 100644
--- a/zero.Backoffice/Filters/CanEditAttribute.cs
+++ b/zero.Backoffice/Controllers/Filters/CanEditAttribute.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
-namespace zero.Backoffice.Filters;
+namespace zero.Backoffice.Controllers;
public class CanEditAttribute : TypeFilterAttribute
{
diff --git a/zero.Backoffice/Controllers/Filters/DisableBrowserCacheFilterAttribute.cs b/zero.Backoffice/Controllers/Filters/DisableBrowserCacheFilterAttribute.cs
new file mode 100644
index 00000000..367d4828
--- /dev/null
+++ b/zero.Backoffice/Controllers/Filters/DisableBrowserCacheFilterAttribute.cs
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Net.Http.Headers;
+
+namespace zero.Backoffice.Controllers;
+
+///
+/// Ensures that the request is not cached by the browser
+///
+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");
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Filters/ModelStateValidationFilterAttribute.cs b/zero.Backoffice/Controllers/Filters/ModelStateValidationFilterAttribute.cs
similarity index 91%
rename from zero.Backoffice/Filters/ModelStateValidationFilterAttribute.cs
rename to zero.Backoffice/Controllers/Filters/ModelStateValidationFilterAttribute.cs
index c50589bc..d8788590 100644
--- a/zero.Backoffice/Filters/ModelStateValidationFilterAttribute.cs
+++ b/zero.Backoffice/Controllers/Filters/ModelStateValidationFilterAttribute.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
-namespace zero.Backoffice.Filters;
+namespace zero.Backoffice.Controllers;
public class ModelStateValidationFilterAttribute : IActionFilter
{
diff --git a/zero.Backoffice/Filters/VerifyTokenAttribute.cs b/zero.Backoffice/Controllers/Filters/VerifyTokenAttribute.cs
similarity index 97%
rename from zero.Backoffice/Filters/VerifyTokenAttribute.cs
rename to zero.Backoffice/Controllers/Filters/VerifyTokenAttribute.cs
index f48d6055..ff18ac1c 100644
--- a/zero.Backoffice/Filters/VerifyTokenAttribute.cs
+++ b/zero.Backoffice/Controllers/Filters/VerifyTokenAttribute.cs
@@ -5,7 +5,7 @@
//using zero.Core.Api;
//using zero.Web.Models;
-//namespace zero.Web.Filters
+//namespace zero.Web.Controllers
//{
// public class VerifyTokenAttribute : TypeFilterAttribute
// {
diff --git a/zero.Backoffice/Filters/ZeroBackofficeAttribute.cs b/zero.Backoffice/Controllers/Filters/ZeroBackofficeAttribute.cs
similarity index 97%
rename from zero.Backoffice/Filters/ZeroBackofficeAttribute.cs
rename to zero.Backoffice/Controllers/Filters/ZeroBackofficeAttribute.cs
index 2aed6f38..a7e4e9bd 100644
--- a/zero.Backoffice/Filters/ZeroBackofficeAttribute.cs
+++ b/zero.Backoffice/Controllers/Filters/ZeroBackofficeAttribute.cs
@@ -6,7 +6,7 @@
//using zero.Core.Entities;
//using zero.Web.Models;
-//namespace zero.Web.Filters
+//namespace zero.Web.Controllers
//{
// public class ZeroBackofficeAttribute : TypeFilterAttribute
// {
diff --git a/zero.Backoffice/Controllers/ZeroBackofficeApiController.cs b/zero.Backoffice/Controllers/ZeroBackofficeApiController.cs
new file mode 100644
index 00000000..f6e27b65
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroBackofficeApiController.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace zero.Backoffice.Controllers;
+
+[ApiController]
+//[ServiceFilter(typeof(ModelStateValidationFilterAttribute))]
+//[ServiceFilter(typeof(BackofficeFilterAttribute))]
+public abstract class ZeroBackofficeApiController : ZeroBackofficeController
+{
+
+}
diff --git a/zero.Backoffice/Controllers/ZeroBackofficeController.cs b/zero.Backoffice/Controllers/ZeroBackofficeController.cs
new file mode 100644
index 00000000..7b673474
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroBackofficeController.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace zero.Backoffice.Controllers;
+
+[ZeroAuthorize]
+[DisableBrowserCache]
+public abstract class ZeroBackofficeController : ControllerBase
+{
+ ///
+ /// Create an edit model with associated metadata and permissions from a model
+ ///
+ protected EditModel GetEditModel(T model)
+ {
+ return GetEditModel>(new EditModel() { Entity = model });
+ }
+
+
+ ///
+ /// Create an edit model with associated metadata and permissions from a model
+ ///
+ protected TWrapper GetEditModel(TWrapper editModel) where TWrapper : EditModel, new()
+ {
+ editModel.Meta = new()
+ {
+ Token = null,
+ IsShared = false
+ };
+
+ editModel.Permissions = new()
+ {
+ CanCreate = true,
+ CanEdit = true,
+ CanDelete = true
+ };
+
+ return editModel;
+ }
+}
diff --git a/zero.Backoffice/Controllers/ZeroBackofficeControllerExtensions.cs b/zero.Backoffice/Controllers/ZeroBackofficeControllerExtensions.cs
new file mode 100644
index 00000000..948bef26
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroBackofficeControllerExtensions.cs
@@ -0,0 +1,115 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.StaticFiles;
+using System.IO;
+
+namespace zero.Backoffice.Controllers;
+
+public static class ZeroBackofficeControllerExtensions
+{
+ ///
+ /// Transform entities to a preview list
+ ///
+ public static List TransformToPreviewModels(this ZeroBackofficeController controller, Dictionary items, Action transform = null) where T : ZeroIdEntity
+ {
+ List 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;
+ }
+
+
+ ///
+ /// Transform entities to a select list
+ ///
+ public static List TransformToSelectModels(this ZeroBackofficeController controller, IEnumerable enumerable, Action transform = null) where T : ZeroIdEntity
+ {
+ List 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;
+ }
+
+
+ ///
+ /// Provides a file stream for download in the browser
+ ///
+ 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);
+ }
+
+
+ ///
+ /// Provides a file stream to the browser
+ ///
+ 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);
+ }
+}
diff --git a/zero.Backoffice/Controllers/ZeroBackofficeControllerModelConvention.cs b/zero.Backoffice/Controllers/ZeroBackofficeControllerModelConvention.cs
new file mode 100644
index 00000000..285cd2c8
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroBackofficeControllerModelConvention.cs
@@ -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]"));
+ }
+
+
+ ///
+ /// Configure routing model for all backoffice controllers
+ ///
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Controllers/ZeroSetupController.cs b/zero.Backoffice/Controllers/ZeroSetupController.cs
new file mode 100644
index 00000000..58d53c0e
--- /dev/null
+++ b/zero.Backoffice/Controllers/ZeroSetupController.cs
@@ -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 Install([FromBody] SetupModel model)
+ {
+ model.ContentRootPath = Env.ContentRootPath;
+
+ EntityResult 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);
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Endpoints/ZeroVueController.cs b/zero.Backoffice/Controllers/ZeroVueController.cs
similarity index 100%
rename from zero.Backoffice/Endpoints/ZeroVueController.cs
rename to zero.Backoffice/Controllers/ZeroVueController.cs
diff --git a/zero.Backoffice/Endpoints/ZeroBackofficeCollectionController.cs b/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
similarity index 84%
rename from zero.Backoffice/Endpoints/ZeroBackofficeCollectionController.cs
rename to zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
index 722c0396..ff413319 100644
--- a/zero.Backoffice/Endpoints/ZeroBackofficeCollectionController.cs
+++ b/zero.Backoffice/Controllers/_ZeroBackofficeCollectionController.cs
@@ -9,7 +9,7 @@ using zero.Core.Entities;
namespace zero.Backoffice;
-public abstract class ZeroBackofficeCollectionController : ZeroBackofficeController
+public abstract class _ZeroBackofficeCollectionController : ZeroBackofficeController
where TEntity : ZeroIdEntity, new()
where TCollection : ICollectionOperations
{
@@ -43,13 +43,13 @@ public abstract class ZeroBackofficeCollectionController :
public virtual async Task> GetEmpty() => Edit(await Collection.Empty());
- public virtual async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
+ public virtual async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
{
return await Collection.Load(query);
}
- public virtual async Task> GetRevisions([FromQuery] string id, [FromQuery] ListBackofficeQuery query)
+ public virtual async Task> GetRevisions([FromQuery] string id, [FromQuery] ListBackofficeQuery query)
{
return null; // TODO
//return await Collection.GetRevisions(id, query.Page, query.PageSize);
diff --git a/zero.Backoffice/Endpoints/BackofficeCollectionController.cs b/zero.Backoffice/Endpoints/BackofficeCollectionController.cs
deleted file mode 100644
index 75576eae..00000000
--- a/zero.Backoffice/Endpoints/BackofficeCollectionController.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Raven.Client.Documents.Indexes;
-using Raven.Client.Documents.Linq;
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using zero.Core.Collections;
-using zero.Core.Entities;
-using zero.Core.Extensions;
-using zero.Web.Models;
-
-
-namespace zero.Web.Controllers
-{
- public abstract class BackofficeCollectionController : BackofficeController
- where TEntity : ZeroEntity
- where TCollection : ICollectionBase
- {
- protected TCollection Collection { get; private set; }
-
- [Obsolete]
- protected Func, IRavenQueryable> DefaultQuery { get; set; }
-
- protected Action PreviewTransform { get; set; }
-
- protected Action PickerTransform { get; set; }
-
-
- public BackofficeCollectionController(TCollection collection)
- {
- Collection = collection;
- }
-
- public override void OnScopeChanged(string scope)
- {
- Collection.ApplyScope(scope);
- }
-
-
- public virtual async Task> GetById([FromQuery] string id, [FromQuery] string changeVector = null) => Edit(changeVector.IsNullOrEmpty() ? await Collection.GetById(id) : await Collection.GetRevision(changeVector));
-
-
- public virtual async Task> GetByIds([FromQuery] string[] ids) => await Collection.GetByIds(ids);
-
-
- public virtual EditModel GetEmpty([FromServices] TEntity blueprint) => Edit(blueprint);
-
-
- public virtual async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
- {
- return await Collection.GetByQuery(query);
- }
-
-
- public virtual async Task> GetRevisions([FromQuery] string id, [FromQuery] ListBackofficeQuery query)
- {
- return await Collection.GetRevisions(id, query.Page, query.PageSize);
- }
-
-
- public virtual async Task> GetForPicker() => await SelectList(Collection.Stream(), PickerTransform);
-
-
- public virtual async Task> GetPreviews([FromQuery] List ids) => Previews(await Collection.GetByIds(ids.ToArray()), PreviewTransform);
-
-
- [HttpPost]
- public virtual async Task> Save([FromBody] TEntity model) => await Collection.Save(model);
-
-
- [HttpDelete]
- public virtual async Task> Delete([FromQuery] string id) => await Collection.DeleteById(id);
- }
-}
diff --git a/zero.Backoffice/Endpoints/BackofficeController.cs b/zero.Backoffice/Endpoints/BackofficeController.cs
deleted file mode 100644
index 77c780fd..00000000
--- a/zero.Backoffice/Endpoints/BackofficeController.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.StaticFiles;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-using zero.Core.Api;
-using zero.Core.Entities;
-using zero.Core.Identity;
-using zero.Core.Options;
-using zero.Web.Filters;
-using zero.Web.Models;
-
-namespace zero.Web.Controllers
-{
- [ZeroAuthorize]
- [ServiceFilter(typeof(ModelStateValidationFilterAttribute))]
- [ApiController]
- [Route("getsreplaced/[controller]/[action]")]
- [ServiceFilter(typeof(BackofficeFilterAttribute))]
- public abstract class BackofficeController : ControllerBase
- {
- IZeroOptions _options;
-
- public bool IsCoreDatabase { get; protected set; }
-
- protected IZeroOptions Options => _options ?? (_options = HttpContext?.RequestServices?.GetService());
-
-
- ///
- /// Is execuated when the scope changes.
- /// The scope is evaluated by the BackofficeFilterAttribute.
- ///
- public virtual void OnScopeChanged(string scope) { }
-
-
- ///
- /// Creates an edit model with appropriate options and permissions
- ///
- public EditModel Edit(T data, bool typed = true, Action> transform = null) where T : ZeroIdEntity
- {
- Type type = typeof(T);
-
- //ControllerContext.ActionDescriptor.FilterDescriptors[0].
-
- if (data == null)
- {
- return null;
- }
-
- EditModel model = new EditModel();
- model.Entity = data;
- model.Meta.Token = null; // Token.Get(data);
- //model.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx
- //model.Meta.CanBeShared = canBeShared;
- model.Meta.CanCreate = true;
- //model.Meta.CanCreateShared = canBeShared;
- model.Meta.CanEdit = true;
- model.Meta.CanDelete = true;
- model.Meta.IsShared = IsCoreDatabase;
-
- transform?.Invoke(model);
-
- return model;
- }
-
-
- ///
- /// Creates an edit model with appropriate options and permissions
- ///
- public TWrapper Edit(TWrapper data, bool typed = true, Action> transform = null)
- where T : ZeroIdEntity
- where TWrapper : EditModel, new()
- {
- Type type = typeof(T);
-
- //ControllerContext.ActionDescriptor.FilterDescriptors[0].
-
- data.Meta.Token = null; // Token.Get(data.Entity);
- //data.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx
- //data.Meta.CanBeShared = canBeShared;
- data.Meta.CanCreate = true;
- //data.Meta.CanCreateShared = canBeShared;
- data.Meta.CanEdit = true;
- data.Meta.CanDelete = true;
- data.Meta.IsShared = IsCoreDatabase;
-
- transform?.Invoke(data);
-
- return data;
- }
-
-
- public IList Previews(Dictionary items, Func transform)
- {
- IList previews = new List();
-
- foreach (var item in items)
- {
- bool exists = item.Value != null;
-
- if (!exists)
- {
- previews.Add(new PreviewModel()
- {
- HasError = true,
- Icon = "fth-alert-circle color-red",
- Id = item.Key,
- Name = "@errors.preview.notfound",
- Text = "@errors.preview.notfound_text"
- });
- }
- else
- {
- previews.Add(transform(item.Value));
- }
- }
-
- return previews;
- }
-
-
- public IList Previews(Dictionary items, Action transform = null) where T : ZeroIdEntity
- {
- IList previews = new List();
-
- 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"
- });
- }
- else
- {
- 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;
- }
-
-
-
- public async Task> SelectList(IAsyncEnumerable enumerable, Action transform = null) where T : ZeroIdEntity
- {
- List items = new List();
-
- await 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;
- }
-
-
-
- public async Task> Previews(Dictionary items, Func> transform)
- {
- IList previews = new List();
-
- foreach (var item in items)
- {
- bool exists = item.Value != null;
-
- if (!exists)
- {
- previews.Add(new PreviewModel()
- {
- HasError = true,
- Icon = "fth-alert-circle color-red",
- Id = item.Key,
- Name = "@errors.preview.notfound",
- Text = "@errors.preview.notfound_text"
- });
- }
- else
- {
- previews.Add(await transform(item.Value));
- }
- }
-
- return previews;
- }
-
-
- protected IActionResult Download(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";
- }
-
- Response.Headers.Add("zero-filename", filename);
-
- return base.File(stream, contentType, filename, true);
- }
-
-
- protected IActionResult File(Core.Entities.FileResult file)
- {
- if (file == null)
- {
- return NotFound();
- }
-
- FileStream stream = System.IO.File.OpenRead(file.Path);
- return File(stream, file.ContentType, file.Filename);
- }
- }
-}
diff --git a/zero.Backoffice/Endpoints/ZeroBackofficeController.cs b/zero.Backoffice/Endpoints/ZeroBackofficeController.cs
deleted file mode 100644
index b1eedd4c..00000000
--- a/zero.Backoffice/Endpoints/ZeroBackofficeController.cs
+++ /dev/null
@@ -1,252 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.StaticFiles;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-using zero.Core.Api;
-using zero.Core.Entities;
-using zero.Core.Identity;
-using zero.Core.Options;
-
-namespace zero.Backoffice;
-
-[ZeroAuthorize]
-//[ServiceFilter(typeof(ModelStateValidationFilterAttribute))]
-[ApiController]
-[Route("getsreplaced/[controller]/[action]")]
-//[ServiceFilter(typeof(BackofficeFilterAttribute))]
-public abstract class ZeroBackofficeController : ControllerBase
-{
- IZeroOptions _options;
-
- public bool IsCoreDatabase { get; protected set; }
-
- protected IZeroOptions Options => _options ?? (_options = HttpContext?.RequestServices?.GetService());
-
-
- ///
- /// Is execuated when the scope changes.
- /// The scope is evaluated by the BackofficeFilterAttribute.
- ///
- public virtual void OnScopeChanged(string scope) { }
-
-
- ///
- /// Creates an edit model with appropriate options and permissions
- ///
- public EditModel Edit(T data, bool typed = true, Action> transform = null) where T : ZeroIdEntity
- {
- Type type = typeof(T);
-
- //ControllerContext.ActionDescriptor.FilterDescriptors[0].
-
- if (data == null)
- {
- return null;
- }
-
- EditModel model = new EditModel();
- model.Entity = data;
- model.Meta.Token = null; // Token.Get(data);
- //model.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx
- //model.Meta.CanBeShared = canBeShared;
- model.Meta.CanCreate = true;
- //model.Meta.CanCreateShared = canBeShared;
- model.Meta.CanEdit = true;
- model.Meta.CanDelete = true;
- model.Meta.IsShared = IsCoreDatabase;
-
- transform?.Invoke(model);
-
- return model;
- }
-
-
- ///
- /// Creates an edit model with appropriate options and permissions
- ///
- public TWrapper Edit(TWrapper data, bool typed = true, Action> transform = null)
- where T : ZeroIdEntity
- where TWrapper : EditModel, new()
- {
- Type type = typeof(T);
-
- //ControllerContext.ActionDescriptor.FilterDescriptors[0].
-
- data.Meta.Token = null; // Token.Get(data.Entity);
- //data.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx
- //data.Meta.CanBeShared = canBeShared;
- data.Meta.CanCreate = true;
- //data.Meta.CanCreateShared = canBeShared;
- data.Meta.CanEdit = true;
- data.Meta.CanDelete = true;
- data.Meta.IsShared = IsCoreDatabase;
-
- transform?.Invoke(data);
-
- return data;
- }
-
-
- public IList Previews(Dictionary items, Func transform)
- {
- IList previews = new List();
-
- foreach (var item in items)
- {
- bool exists = item.Value != null;
-
- if (!exists)
- {
- previews.Add(new PreviewModel()
- {
- HasError = true,
- Icon = "fth-alert-circle color-red",
- Id = item.Key,
- Name = "@errors.preview.notfound",
- Text = "@errors.preview.notfound_text"
- });
- }
- else
- {
- previews.Add(transform(item.Value));
- }
- }
-
- return previews;
- }
-
-
- public IList Previews(Dictionary items, Action transform = null) where T : ZeroIdEntity
- {
- IList previews = new List();
-
- 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"
- });
- }
- else
- {
- 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;
- }
-
-
-
- public async Task> SelectList(IAsyncEnumerable enumerable, Action transform = null) where T : ZeroIdEntity
- {
- List items = new List();
-
- await 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;
- }
-
-
-
- public async Task> Previews(Dictionary items, Func> transform)
- {
- IList previews = new List();
-
- foreach (var item in items)
- {
- bool exists = item.Value != null;
-
- if (!exists)
- {
- previews.Add(new PreviewModel()
- {
- HasError = true,
- Icon = "fth-alert-circle color-red",
- Id = item.Key,
- Name = "@errors.preview.notfound",
- Text = "@errors.preview.notfound_text"
- });
- }
- else
- {
- previews.Add(await transform(item.Value));
- }
- }
-
- return previews;
- }
-
-
- protected IActionResult Download(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";
- }
-
- Response.Headers.Add("zero-filename", filename);
-
- return base.File(stream, contentType, filename, true);
- }
-
-
- protected IActionResult File(Core.Entities.FileResult file)
- {
- if (file == null)
- {
- return NotFound();
- }
-
- FileStream stream = System.IO.File.OpenRead(file.Path);
- return File(stream, file.ContentType, file.Filename);
- }
-}
\ No newline at end of file
diff --git a/zero.Backoffice/Endpoints/ZeroBackofficeControllerModelConvention.cs b/zero.Backoffice/Endpoints/ZeroBackofficeControllerModelConvention.cs
deleted file mode 100644
index 39f6bfb4..00000000
--- a/zero.Backoffice/Endpoints/ZeroBackofficeControllerModelConvention.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ApplicationModels;
-using System;
-
-namespace zero.Backoffice;
-
-public class ZeroBackofficeControllerModelConvention : IControllerModelConvention
-{
- readonly AttributeRouteModel RouteModel;
-
- readonly Type BaseClass = typeof(ZeroBackofficeController);
-
-
- public ZeroBackofficeControllerModelConvention(string backofficePath)
- {
- RouteModel = new AttributeRouteModel(new RouteAttribute(backofficePath + "/api/[controller]/[action]"));
- }
-
-
- public void Apply(ControllerModel controller)
- {
- if (!controller.ControllerType.IsSubclassOf(BaseClass))
- {
- return;
- }
-
- foreach (var selector in controller.Selectors)
- {
- selector.AttributeRouteModel = RouteModel;
- }
- }
-}
\ No newline at end of file
diff --git a/zero.Backoffice/Endpoints/ZeroSetupController.cs b/zero.Backoffice/Endpoints/ZeroSetupController.cs
deleted file mode 100644
index 126ebe25..00000000
--- a/zero.Backoffice/Endpoints/ZeroSetupController.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Mvc;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using zero.Core.Api;
-using zero.Core.Entities;
-using zero.Core.Entities.Setup;
-using zero.Core.Extensions;
-using zero.Core.Identity;
-using zero.Core.Options;
-
-namespace zero.Web.Setup
-{
- [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 Install([FromBody] SetupModel model)
- {
- model.ContentRootPath = Env.ContentRootPath;
-
- EntityResult 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);
- }
- }
-}
diff --git a/zero.Backoffice/Models/EditModel.cs b/zero.Backoffice/Models/EditModel.cs
index e19a46a3..0af0f978 100644
--- a/zero.Backoffice/Models/EditModel.cs
+++ b/zero.Backoffice/Models/EditModel.cs
@@ -13,10 +13,38 @@ public class EditModel
/// Meta data
///
public EditMetaModel Meta { get; set; } = new();
+
+ ///
+ /// Permissions for this entity
+ ///
+ public EditPermissionModel Permissions { get; set; } = new();
}
public class EditMetaModel
+{
+ ///
+ /// Wehther this entity is application aware
+ ///
+ public bool IsAppAware { get; set; }
+
+ ///
+ /// Whether this entity can be shared across applications (only for IsAppAware=true)
+ ///
+ public bool CanBeShared { get; set; }
+
+ public bool IsShared { get; set; }
+
+ ///
+ /// The change token maps to a database entity which holds ID and collection of the model to edit
+ /// If these values do not match the entity on save it is rejected
+ /// // TODO expiration expiry session.Advanced.GetMetadataFor(user)[Raven.Client.Constants.Documents.Metadata.Expires] = DateTime.UtcNow.AddMinutes(60);
+ ///
+ public string Token { get; set; }
+}
+
+
+public class EditPermissionModel
{
///
/// Whether an entity of this type can be created
@@ -37,23 +65,4 @@ public class EditMetaModel
/// Whether this entity can be deleted
///
public bool CanDelete { get; set; }
-
- ///
- /// Wehther this entity is application aware
- ///
- public bool IsAppAware { get; set; }
-
- ///
- /// Whether this entity can be shared across applications (only for IsAppAware=true)
- ///
- public bool CanBeShared { get; set; }
-
- public bool IsShared { get; set; }
-
- ///
- /// The change token maps to a database entity which holds ID and collection of the model to edit
- /// If these values do not match the entity on save it is rejected
- /// // TODO expiration expiry session.Advanced.GetMetadataFor(user)[Raven.Client.Constants.Documents.Metadata.Expires] = DateTime.UtcNow.AddMinutes(60);
- ///
- public string Token { get; set; }
-}
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Models/PreviewModel.cs b/zero.Backoffice/Models/PreviewModel.cs
new file mode 100644
index 00000000..5bdb4d8c
--- /dev/null
+++ b/zero.Backoffice/Models/PreviewModel.cs
@@ -0,0 +1,14 @@
+namespace zero.Backoffice.Models;
+
+public class PreviewModel
+{
+ public string Id { get; set; }
+
+ public string Icon { get; set; }
+
+ public string Text { get; set; }
+
+ public string Name { get; set; }
+
+ public bool HasError { get; set; }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Models/TreeItem.cs b/zero.Backoffice/Models/TreeItem.cs
index da0c624b..d82c7027 100644
--- a/zero.Backoffice/Models/TreeItem.cs
+++ b/zero.Backoffice/Models/TreeItem.cs
@@ -74,29 +74,4 @@ public class TreeItem
/// Output an additional count value.
///
public int? CountOutput { get; set; }
-}
-
-
-///
-/// The modifier displays a small icon (with hover text) next to the main item icon
-///
-public class TreeItemModifier
-{
- ///
- /// Name of the modifier
- ///
- public string Name { get; set; }
-
- ///
- /// Icon to display
- ///
- public string Icon { get; set; }
-
- public TreeItemModifier() { }
-
- public TreeItemModifier(string name, string icon)
- {
- Name = name;
- Icon = icon;
- }
-}
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Models/TreeItemModifier.cs b/zero.Backoffice/Models/TreeItemModifier.cs
new file mode 100644
index 00000000..90b0afec
--- /dev/null
+++ b/zero.Backoffice/Models/TreeItemModifier.cs
@@ -0,0 +1,25 @@
+namespace zero.Backoffice.Models;
+
+///
+/// The modifier displays a small icon (with hover text) next to the main item icon
+///
+public class TreeItemModifier
+{
+ ///
+ /// Name of the modifier
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Icon to display
+ ///
+ public string Icon { get; set; }
+
+ public TreeItemModifier() { }
+
+ public TreeItemModifier(string name, string icon)
+ {
+ Name = name;
+ Icon = icon;
+ }
+}
diff --git a/zero.Backoffice/Modules/Countries/CountriesController.cs b/zero.Backoffice/Modules/Countries/CountriesController.cs
new file mode 100644
index 00000000..9b4bd176
--- /dev/null
+++ b/zero.Backoffice/Modules/Countries/CountriesController.cs
@@ -0,0 +1,66 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace zero.Backoffice.Modules;
+
+///
+/// | GET /zero/api/countries/empty
+/// | GET /zero/api/countries/{id}
+/// | GET /zero/api/countries/{id}/revisions
+/// | GET /zero/api/countries[type,query,filter,...]
+/// | POST /zero/api/countries
+/// | PUT /zero/api/countries/{id}
+/// | DELETE /zero/api/countries/{id}
+///
+public class CountriesController : ZeroBackofficeApiController
+{
+ protected ICountriesCollection Collection { get; set; }
+
+ public CountriesController(ICountriesCollection collection)
+ {
+ Collection = collection;
+ }
+
+
+ [HttpGet("empty")]
+ public virtual async Task New()
+ {
+ return await Collection.Empty();
+ }
+
+ [HttpGet("{id}")]
+ public virtual async Task Get(string id, string changeVector = null)
+ {
+ return await Collection.Load(id, changeVector);
+ }
+
+ [HttpGet]
+ public virtual async Task> Get(ListBackofficeQuery query)
+ {
+ return await Collection.Load(query.Page, query.PageSize, q => q.OrderByDescending(x => x.CreatedDate));
+ }
+
+ //[HttpGet("{id}/revisions")]
+ //public virtual async Task>> GetRevisions(string id)
+ //{
+ // return await Collection.GetRevisions(id);
+ //}
+
+
+ [HttpPost]
+ public virtual async Task> Create(Country country)
+ {
+ return await Collection.Create(country);
+ }
+
+ [HttpPut("{id}")]
+ public virtual async Task> Update(string id, Country country)
+ {
+ return await Collection.Update(country);
+ }
+
+ [HttpDelete("{id}")]
+ public virtual async Task> Delete(string id)
+ {
+ return await Collection.Delete(id);
+ }
+}
\ No newline at end of file
diff --git a/zero.Backoffice/Modules/Countries/CountriesEndpoint.cs b/zero.Backoffice/Modules/Countries/CountriesEndpoint.cs
deleted file mode 100644
index 3cbaa843..00000000
--- a/zero.Backoffice/Modules/Countries/CountriesEndpoint.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Raven.Client.Documents;
-using Raven.Client.Documents.Linq;
-
-namespace zero.Backoffice.Modules;
-
-[ZeroAuthorize(Permissions.Settings.Countries, PermissionsValue.Read)]
-public class CountriesController : ZeroBackofficeCollectionController
-{
- public CountriesController(ICountriesCollection collection) : base(collection)
- {
- PreviewTransform = (item, model) => model.Icon = "flag-" + item.Code.ToLowerInvariant();
- }
-
- public override Task> GetByQuery([FromQuery] ListBackofficeQuery query)
- {
- query.OrderQuery = q => q.OrderByDescending(x => x.IsPreferred).ThenBy(x => x.Name);
- return Collection.Load(query);
- }
-}
\ No newline at end of file
diff --git a/zero.Backoffice/Modules/Languages/LanguagesController.cs b/zero.Backoffice/Modules/Languages/LanguagesController.cs
index d924e82a..b61b656e 100644
--- a/zero.Backoffice/Modules/Languages/LanguagesController.cs
+++ b/zero.Backoffice/Modules/Languages/LanguagesController.cs
@@ -31,7 +31,7 @@ namespace zero.Web.Controllers
}
- public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
+ public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
{
query.OrderQuery = q => q.OrderByDescending(x => x.CreatedDate);
return await Collection.Load(query);
diff --git a/zero.Backoffice/Modules/Mails/MailTemplatesController.cs b/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
index dd9cccdb..218ba21b 100644
--- a/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
+++ b/zero.Backoffice/Modules/Mails/MailTemplatesController.cs
@@ -15,7 +15,7 @@ namespace zero.Web.Controllers
PreviewTransform = (item, model) => model.Icon = "fth-mail";
}
- public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
+ public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
{
query.SearchFor(entity => entity.Name, entity => entity.Key, entity => entity.Subject);
return await Collection.Load(query);
diff --git a/zero.Backoffice/Modules/Search/BackofficeSearchService.cs b/zero.Backoffice/Modules/Search/BackofficeSearchService.cs
index 3f62f376..191757ca 100644
--- a/zero.Backoffice/Modules/Search/BackofficeSearchService.cs
+++ b/zero.Backoffice/Modules/Search/BackofficeSearchService.cs
@@ -18,7 +18,7 @@ public class BackofficeSearchService : IBackofficeSearchService
}
- public async Task> Query(string searchTerm)
+ public async Task> Query(string searchTerm)
{
string[] searchParts = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x =>
{
@@ -57,7 +57,7 @@ public class BackofficeSearchService : IBackofficeSearchService
items.Add(searchResult);
}
- return new ListResult(items, stats.TotalResults, 1, 20);
+ return new Paged(items, stats.TotalResults, 1, 20);
}
@@ -69,5 +69,5 @@ public class BackofficeSearchService : IBackofficeSearchService
public interface IBackofficeSearchService
{
- Task> Query(string searchTerm);
+ Task> Query(string searchTerm);
}
\ No newline at end of file
diff --git a/zero.Backoffice/Modules/Search/SearchEndpoint.cs b/zero.Backoffice/Modules/Search/SearchEndpoint.cs
index ad3800e3..ce3351c4 100644
--- a/zero.Backoffice/Modules/Search/SearchEndpoint.cs
+++ b/zero.Backoffice/Modules/Search/SearchEndpoint.cs
@@ -12,5 +12,5 @@ public class SearchController : BackofficeController
SearchService = searchService;
}
- public async Task> Query([FromQuery] string query) => await SearchService.Query(query);
+ public async Task> Query([FromQuery] string query) => await SearchService.Query(query);
}
\ No newline at end of file
diff --git a/zero.Backoffice/Modules/Translations/TranslationsController.cs b/zero.Backoffice/Modules/Translations/TranslationsController.cs
index d284ebcb..c9ac78a4 100644
--- a/zero.Backoffice/Modules/Translations/TranslationsController.cs
+++ b/zero.Backoffice/Modules/Translations/TranslationsController.cs
@@ -14,7 +14,7 @@ namespace zero.Web.Controllers
{
public TranslationsController(ITranslationsCollection collection) : base(collection) { }
- public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
+ public override async Task> GetByQuery([FromQuery] ListBackofficeQuery query)
{
query.SearchFor(entity => entity.Key, entity => entity.Value);
query.OrderQuery = q => q.OrderByDescending(x => x.CreatedDate);
diff --git a/zero.Backoffice/Usings.cs b/zero.Backoffice/Usings.cs
index d215cdf0..3cf5e6e2 100644
--- a/zero.Backoffice/Usings.cs
+++ b/zero.Backoffice/Usings.cs
@@ -1,29 +1,27 @@
global using System;
-global using System.Collections;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
+global using zero.Applications;
global using zero.Architecture;
+global using zero.Collections;
global using zero.Configuration;
+global using zero.Context;
global using zero.Extensions;
global using zero.Identity;
-global using zero.Pages;
-global using zero.Validation;
-global using zero.Utils;
-global using zero.Persistence;
global using zero.Localization;
-global using zero.Routing;
-global using zero.Context;
-global using zero.Communication;
-global using zero.Rendering;
global using zero.Media;
-global using zero.Applications;
+global using zero.Pages;
+global using zero.Persistence;
+global using zero.Routing;
+global using zero.Utils;
+global using zero.Models;
-global using zero.Backoffice;
+global using zero.Backoffice.Configuration;
+global using zero.Backoffice.Controllers;
global using zero.Backoffice.Models;
global using zero.Backoffice.Modules;
-global using zero.Backoffice.Configuration;
global using zero.Backoffice.UIComposition;
\ No newline at end of file
diff --git a/zero.Backoffice/ZeroBackofficeMvcOptions.cs b/zero.Backoffice/ZeroBackofficeMvcOptions.cs
index d310df82..c37f2aea 100644
--- a/zero.Backoffice/ZeroBackofficeMvcOptions.cs
+++ b/zero.Backoffice/ZeroBackofficeMvcOptions.cs
@@ -14,6 +14,7 @@ class ZeroBackofficeMvcOptions : IConfigureOptions
public void Configure(MvcOptions options)
{
- //options.Conventions.Add(new ZeroBackofficeControllerModelConvention(Options.BackofficePath));
+ string backofficePath = Options.For().Path;
+ options.Conventions.Add(new ZeroBackofficeControllerModelConvention(backofficePath));
}
}
\ No newline at end of file
diff --git a/zero.Backoffice/_legacy/Controllers/ApplicationsController.cs b/zero.Backoffice/_legacy/Controllers/ApplicationsController.cs
index 79faa72d..1bdf0ab1 100644
--- a/zero.Backoffice/_legacy/Controllers/ApplicationsController.cs
+++ b/zero.Backoffice/_legacy/Controllers/ApplicationsController.cs
@@ -29,7 +29,7 @@ namespace zero.Web.Controllers
public async Task> GetAll() => await Api.GetAll();
- public async Task> GetByQuery([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
+ public async Task> GetByQuery([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
public IReadOnlyCollection GetAllFeatures() => Options.Features.GetAllItems();
diff --git a/zero.Backoffice/_legacy/Controllers/IntegrationsController.cs b/zero.Backoffice/_legacy/Controllers/IntegrationsController.cs
index f81e8394..3b580cdc 100644
--- a/zero.Backoffice/_legacy/Controllers/IntegrationsController.cs
+++ b/zero.Backoffice/_legacy/Controllers/IntegrationsController.cs
@@ -25,7 +25,7 @@ namespace zero.Web.Controllers
public async Task> GetByAlias([FromQuery] string alias) => Edit(await Collection.GetByAlias(alias));
- public async Task> Load([FromQuery] ListQuery query) => await Collection.Load(query);
+ public async Task> Load([FromQuery] ListQuery query) => await Collection.Load(query);
public async Task> GetTypes() => await Collection.GetTypesWithStatus();
diff --git a/zero.Backoffice/_legacy/Controllers/MediaController.cs b/zero.Backoffice/_legacy/Controllers/MediaController.cs
index 34266cb8..f70f198e 100644
--- a/zero.Backoffice/_legacy/Controllers/MediaController.cs
+++ b/zero.Backoffice/_legacy/Controllers/MediaController.cs
@@ -27,7 +27,7 @@ namespace zero.Web.Controllers
}
- public async Task> GetListByQuery([FromQuery] MediaListItemQuery query)
+ public async Task> GetListByQuery([FromQuery] MediaListItemQuery query)
{
query.IncludeInactive = true;
return await Collection.Load(query);
@@ -47,7 +47,7 @@ namespace zero.Web.Controllers
public async Task GetAll([FromQuery] MediaListQuery query)
{
query.IncludeInactive = true;
- ListResult items = (await Collection.Load(query)).MapTo(x => new MediaListModel()
+ Paged items = (await Collection.Load(query)).MapTo(x => new MediaListModel()
{
Id = x.Id,
IsFolder = false,
diff --git a/zero.Backoffice/_legacy/Controllers/RecycleBinController.cs b/zero.Backoffice/_legacy/Controllers/RecycleBinController.cs
index 3c44e316..a853becb 100644
--- a/zero.Backoffice/_legacy/Controllers/RecycleBinController.cs
+++ b/zero.Backoffice/_legacy/Controllers/RecycleBinController.cs
@@ -15,7 +15,7 @@ namespace zero.Web.Controllers
}
- public async Task> GetByQuery([FromQuery] RecycleBinListQuery query)
+ public async Task> GetByQuery([FromQuery] RecycleBinListQuery query)
{
query.IncludeInactive = true;
return await Api.GetByQuery(query);
diff --git a/zero.Backoffice/_legacy/Controllers/UsersController.cs b/zero.Backoffice/_legacy/Controllers/UsersController.cs
index a603ee83..6cef3e3f 100644
--- a/zero.Backoffice/_legacy/Controllers/UsersController.cs
+++ b/zero.Backoffice/_legacy/Controllers/UsersController.cs
@@ -29,7 +29,7 @@ namespace zero.Web.Controllers
public async Task> GetById([FromQuery] string id) => Edit(await Api.GetUserById(id));
- public async Task> GetAll([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
+ public async Task> GetAll([FromQuery] ListBackofficeQuery query) => await Api.GetByQuery(query);
public IList GetAllPermissions() => PermissionsApi.GetAll();
diff --git a/zero.Backoffice/_legacy/Models/MediaListResultModel.cs b/zero.Backoffice/_legacy/Models/MediaListResultModel.cs
index 2aebe262..aefe58ba 100644
--- a/zero.Backoffice/_legacy/Models/MediaListResultModel.cs
+++ b/zero.Backoffice/_legacy/Models/MediaListResultModel.cs
@@ -3,7 +3,7 @@ using zero.Core.Entities;
namespace zero.Web.Models
{
- public class MediaListResultModel : ListResult
+ public class MediaListResultModel : Paged
{
public IEnumerable Folders { get; private set; }
@@ -11,12 +11,12 @@ namespace zero.Web.Models
public MediaFolder Folder { get; private set; }
- public MediaListResultModel(ListResult items, IEnumerable folders) : base(items.Items, items.TotalItems, items.Page, items.PageSize)
+ public MediaListResultModel(Paged items, IEnumerable folders) : base(items.Items, items.TotalItems, items.Page, items.PageSize)
{
Folders = folders;
}
- public MediaListResultModel(ListResult items, IEnumerable folders, MediaFolder currentFolder, IEnumerable hierarchy) : this(items, folders)
+ public MediaListResultModel(Paged items, IEnumerable folders, MediaFolder currentFolder, IEnumerable hierarchy) : this(items, folders)
{
Folder = currentFolder;
FolderHierarchy = hierarchy;
diff --git a/zero.Backoffice/_legacy/Models/PageEditModel.cs b/zero.Backoffice/_legacy/Models/PageEditModel.cs
index c99233ab..b8203d12 100644
--- a/zero.Backoffice/_legacy/Models/PageEditModel.cs
+++ b/zero.Backoffice/_legacy/Models/PageEditModel.cs
@@ -7,7 +7,7 @@ namespace zero.Web.Models
{
public PageType PageType { get; set; }
- public ListResult Revisions { get; set; }
+ public Paged Revisions { get; set; }
public List Urls { get; set; } = new();
}
diff --git a/zero.Core/Collections/CollectionOperations.Read.cs b/zero.Core/Collections/CollectionOperations.Read.cs
index 7e4ec485..db2e5550 100644
--- a/zero.Core/Collections/CollectionOperations.Read.cs
+++ b/zero.Core/Collections/CollectionOperations.Read.cs
@@ -1,5 +1,7 @@
-using Raven.Client.Documents.Indexes;
+using Raven.Client.Documents;
+using Raven.Client.Documents.Indexes;
using Raven.Client.Documents.Linq;
+using Raven.Client.Documents.Session;
namespace zero.Collections;
@@ -40,18 +42,26 @@ public abstract partial class CollectionOperations
///
- public virtual async Task> Load(ListQuery query) where T : ZeroIdEntity, new()
+ public virtual async Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : ZeroIdEntity, new()
{
- return await Session.Query().FilterAsync(query);
+ IRavenQueryable queryable = Session.Query().Statistics(out QueryStatistics statistics);
+ querySelector ??= x => x;
+
+ List result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync();
+ return new Paged(result, statistics.TotalResults, pageNumber, pageSize);
}
///
- public virtual async Task> Load(ListQuery query)
+ public virtual async Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default)
where T : ZeroIdEntity, new()
where TIndex : AbstractCommonApiForIndexes, new()
{
- return await Session.Query().FilterAsync(query);
+ IRavenQueryable queryable = Session.Query().Statistics(out QueryStatistics statistics);
+ querySelector ??= x => x;
+
+ List result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync();
+ return new Paged(result, statistics.TotalResults, pageNumber, pageSize);
}
@@ -74,16 +84,17 @@ public abstract partial class CollectionOperations
///
- public virtual async IAsyncEnumerable Stream(Func, IRavenQueryable> expression) where T : ZeroIdEntity, new()
+ public virtual async IAsyncEnumerable Stream(Func, IQueryable> expression) where T : ZeroIdEntity, new()
{
IRavenQueryable query = Session.Query();
+ IQueryable queryable = query;
if (expression != null)
{
- query = expression(query);
+ queryable = expression(query);
}
- var stream = await Session.Advanced.StreamAsync(query);
+ var stream = await Session.Advanced.StreamAsync(queryable);
while (await stream.MoveNextAsync())
{
diff --git a/zero.Core/Collections/CollectionOperations.Write.cs b/zero.Core/Collections/CollectionOperations.Write.cs
index c02177c3..a116b616 100644
--- a/zero.Core/Collections/CollectionOperations.Write.cs
+++ b/zero.Core/Collections/CollectionOperations.Write.cs
@@ -5,7 +5,13 @@ namespace zero.Collections;
public abstract partial class CollectionOperations
{
///
- public virtual async Task> Save(T model, Func> validate = null) where T : ZeroIdEntity, new()
+ public virtual Task> Create(T model, Func> validate = null) where T : ZeroIdEntity, new() => Save(model, validate);
+
+ ///
+ public virtual Task> Update(T model, Func> validate = null) where T : ZeroIdEntity, new() => Save(model, validate);
+
+ ///
+ protected virtual async Task> Save(T model, Func> validate = null) where T : ZeroIdEntity, new()
{
if (model == null)
{
diff --git a/zero.Core/Collections/CollectionOperations.cs b/zero.Core/Collections/CollectionOperations.cs
index c8f35e9f..90973696 100644
--- a/zero.Core/Collections/CollectionOperations.cs
+++ b/zero.Core/Collections/CollectionOperations.cs
@@ -150,12 +150,12 @@ public interface ICollectionOperations
///
/// Get entities by query
///
- Task> Load(ListQuery query) where T : ZeroIdEntity, new();
+ Task> Load(int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : ZeroIdEntity, new();
///
/// Get entities by query (by using the specified index)
///
- Task> Load(ListQuery query) where T : ZeroIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new();
+ Task> Load