diff --git a/zero.Core/Api/PagesApi.cs b/zero.Core/Collections/Pages/PagesCollection.cs similarity index 60% rename from zero.Core/Api/PagesApi.cs rename to zero.Core/Collections/Pages/PagesCollection.cs index ceeb48d4..66cf7fc1 100644 --- a/zero.Core/Api/PagesApi.cs +++ b/zero.Core/Collections/Pages/PagesCollection.cs @@ -2,38 +2,35 @@ using Microsoft.Extensions.Logging; using Raven.Client.Documents; using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using zero.Core.Api; using zero.Core.Database.Indexes; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Handlers; -using zero.Core.Options; -namespace zero.Core.Api +namespace zero.Core.Collections { - public class PagesApi : BackofficeApi, IPagesApi + public class PagesCollection : CollectionBase, IPagesCollection { const string RECYCLE_BIN_GROUP = "pages"; - protected IZeroOptions Options { get; private set; } - protected IRecycleBinApi RecycleBinApi { get; private set; } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; private set; } protected IHandlerHolder Handler { get; private set; } - public PagesApi(IZeroOptions options, IBackofficeStore store, IRecycleBinApi recycleBinApi, ILogger logger, IHandlerHolder handler) : base(store) + public PagesCollection(IZeroContext context, ICollectionInterceptorHandler interceptor, IRecycleBinApi recycleBinApi, IHandlerHolder handler, + ILogger logger, IValidator validator = null) : base(context, interceptor, validator) { - Options = options; RecycleBinApi = recycleBinApi; - Logger = logger; Handler = handler; + Logger = logger; } @@ -66,37 +63,22 @@ namespace zero.Core.Api /// - public async Task GetById(string id) - { - return await GetById(id); - } + public IList GetPageTypes() => Context.Options.Pages.GetAllItems().ToList(); /// - public async Task> GetByIds(params string[] ids) - { - return await GetByIds(ids); - } - - - /// - public IList GetPageTypes() - { - return Options.Pages.GetAllItems().ToList(); - } + public PageType GetPageType(string alias) => Context.Options.Pages.GetAllItems().FirstOrDefault(x => x.Alias == alias); /// public async Task> GetAllowedPageTypes(string parentId = null) - { - IEnumerable types = Options.Pages.GetAllItems(); + { + IEnumerable types = Context.Options.Pages.GetAllItems(); List parents = new(); - using IAsyncDocumentSession session = Store.OpenAsyncSession(); - if (!parentId.IsNullOrEmpty()) { - Pages_ByHierarchy.Result result = await session.Query() + Pages_ByHierarchy.Result result = await Session.Query() .ProjectInto() .Include(x => x.Id) .Include(x => x.Path.Select(p => p.Id)) @@ -106,7 +88,7 @@ namespace zero.Core.Api { List ids = result.Path.Select(x => x.Id).ToList(); ids.Add(result.Id); - parents = (await session.LoadAsync(ids)).Select(x => x.Value).Reverse().ToList(); + parents = (await Session.LoadAsync(ids)).Select(x => x.Value).Reverse().ToList(); } } @@ -118,28 +100,14 @@ namespace zero.Core.Api return types.ToList(); } - return handler.GetAllowedPageTypes(Backoffice.Context.Application, types, parents)?.ToList() ?? new(); - } - - - /// - public PageType GetPageType(string alias) - { - return Options.Pages.GetAllItems().FirstOrDefault(x => x.Alias == alias); - } - - - /// - public async Task> Save(Page model) - { - return await SaveModel(model, null); + return handler.GetAllowedPageTypes(Context.Application, types, parents)?.ToList() ?? new(); } /// public async Task>> SaveSorting(string[] sortedIds) { - Dictionary items = await GetByIds(sortedIds); + Dictionary items = await GetByIds(sortedIds); uint index = 0; // contains multiple parents, therefore fail @@ -148,19 +116,12 @@ namespace zero.Core.Api return EntityResult>.Fail("@errors.page.sortingmultipleparents"); } - using (IAsyncDocumentSession session = Store.OpenAsyncSession()) + foreach (var item in items) { - session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false); - - foreach (var item in items) - { - item.Value.Sort = index; - index += 10; - await session.StoreAsync(item.Value); - //session.Advanced.Patch(item.Value.Id, x => x.Sort, index++); - } - - await session.SaveChangesAsync(); + item.Value.Sort = index; + index += 10; + await Save(item.Value); + //session.Advanced.Patch(item.Value.Id, x => x.Sort, index++); } return EntityResult>.Success(items.Select(x => x.Value).ToList()); @@ -170,8 +131,8 @@ namespace zero.Core.Api /// public async Task> Move(string id, string parentId) { - Page model = await GetById(id); - Page parent = await GetById(parentId); + Page model = await GetById(id); + Page parent = await GetById(parentId); if (model == null || (!parentId.IsNullOrEmpty() && parent == null)) { @@ -194,8 +155,8 @@ namespace zero.Core.Api /// public async Task> Copy(string id, string destinationId, bool includeDescendants = false) { - Page model = await GetById(id); - Page parent = await GetById(destinationId); + Page model = await GetById(id); + Page parent = await GetById(destinationId); if (model == null || (!destinationId.IsNullOrEmpty() && parent == null)) { @@ -218,47 +179,38 @@ namespace zero.Core.Api return EntityResult.Fail("@errors.page.parentnotallowed"); } - using (IAsyncDocumentSession session = Store.OpenAsyncSession()) + // recursive function to store descendants + async Task AddChildren(string oldParentId, string newParentId) { - session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false); + Pages_WithChildren.Result childrenResult = await Session.Query() + .ProjectInto() + .Include(x => x.Id) + .Where(x => x.Id == oldParentId) + .FirstOrDefaultAsync(); - // recursive function to store descendants - async Task AddChildren(string oldParentId, string newParentId) + if (childrenResult == null || childrenResult.ChildrenIds.Length < 1) { - Pages_WithChildren.Result childrenResult = await session.Query() - .ProjectInto() - .Include(x => x.Id) - .Where(x => x.Id == oldParentId) - .FirstOrDefaultAsync(); - - if (childrenResult == null || childrenResult.ChildrenIds.Length < 1) - { - return; - } - - Dictionary childrenPages = await session.LoadAsync(childrenResult.ChildrenIds); - - foreach (var child in childrenPages) - { - Page childPage = child.Value.Clone(); - childPage.Id = null; - childPage.IsActive = false; - childPage.ParentId = newParentId; - childPage.CreatedDate = DateTimeOffset.Now; - - await session.StoreAsync(childPage); - await AddChildren(child.Key, childPage.Id); - } + return; } - await session.StoreAsync(model); + Dictionary childrenPages = await GetByIds(childrenResult.ChildrenIds); - if (includeDescendants) + foreach (var child in childrenPages) { - await AddChildren(baseId, model.Id); - } + Page childPage = child.Value.Clone(); + childPage.Id = null; + childPage.IsActive = false; + childPage.ParentId = newParentId; + childPage.CreatedDate = DateTimeOffset.Now; - await session.SaveChangesAsync(); + await Save(childPage); + await AddChildren(child.Key, childPage.Id); + } + } + + if (includeDescendants) + { + await AddChildren(baseId, model.Id); } return EntityResult.Success(model); @@ -266,10 +218,9 @@ namespace zero.Core.Api /// - public async Task> Delete(string id, bool moveToRecycleBin = true) + public async Task> Delete(string id, bool recursive = true, bool moveToRecycleBin = true) { - IList pages = await GetByIdWithDescendants(id); - string[] ids = pages.Select(x => x.Id).ToArray(); + List pages = recursive ? await GetByIdWithDescendants(id) : new() { await GetById(id) }; if (pages.Count < 1) { @@ -281,9 +232,9 @@ namespace zero.Core.Api await RecycleBinApi.Add(pages, RECYCLE_BIN_GROUP); } - await DeleteByIds(ids); + await Delete(pages.ToArray()); - return EntityResult.Success(ids); + return EntityResult.Success(pages.Select(x => x.Id).ToArray()); } @@ -310,7 +261,7 @@ namespace zero.Core.Api // check if parents are available string[] parentIds = entities.Select(x => x.Content as Page).Where(x => x != null).Select(x => x.ParentId).ToArray(); - parentIds = (await GetByIds(parentIds)).Where(x => x.Value != null).Select(x => x.Value.Id).ToArray(); + parentIds = (await GetByIds(parentIds)).Where(x => x.Value != null).Select(x => x.Value.Id).ToArray(); // validate and restore all items foreach (RecycledEntity entity in entities) @@ -334,7 +285,7 @@ namespace zero.Core.Api } // restore to pages - EntityResult saveResult = await SaveModel(page); + EntityResult saveResult = await Save(page); } // delete affected entities from recycle bin @@ -358,7 +309,7 @@ namespace zero.Core.Api { List items = new List(); - Page model = await GetById(id); + Page model = await GetById(id); if (model == null) { @@ -367,56 +318,43 @@ namespace zero.Core.Api items.Add(model); - using (IAsyncDocumentSession session = Store.OpenAsyncSession()) + // recursive function to store descendants + async Task AddChildren(string parentId) { - // recursive function to store descendants - async Task AddChildren(string parentId) + Pages_WithChildren.Result childrenResult = await Session.Query() + .ProjectInto() + .Include(x => x.Id) + .Where(x => x.Id == parentId) + .FirstOrDefaultAsync(); + + if (childrenResult == null || childrenResult.ChildrenIds.Length < 1) { - Pages_WithChildren.Result childrenResult = await session.Query() - .ProjectInto() - .Include(x => x.Id) - .Where(x => x.Id == parentId) - .FirstOrDefaultAsync(); - - if (childrenResult == null || childrenResult.ChildrenIds.Length < 1) - { - return; - } - - Dictionary childrenPages = await session.LoadAsync(childrenResult.ChildrenIds); - - foreach (var child in childrenPages) - { - items.Add(child.Value); - await AddChildren(child.Value.Id); - } + return; } - await AddChildren(model.Id); + Dictionary childrenPages = await GetByIds(childrenResult.ChildrenIds); + + foreach (var child in childrenPages) + { + items.Add(child.Value); + await AddChildren(child.Value.Id); + } } + await AddChildren(model.Id); + return items; } } - public interface IPagesApi + public interface IPagesCollection : ICollectionBase { /// /// Get a new empty page with the specified type /// public Task GetEmpty(string pageType, string parentId = null); - /// - /// Get page by Id - /// - Task GetById(string id); - - /// - /// Get pages by ids - /// - Task> GetByIds(params string[] ids); - /// /// Get all available page types /// @@ -432,11 +370,6 @@ namespace zero.Core.Api /// PageType GetPageType(string alias); - /// - /// Creates or updates a page - /// - Task> Save(Page model); - /// /// Update sorting of pages on a specific level /// @@ -455,7 +388,7 @@ namespace zero.Core.Api /// /// Deletes a page by Id (with all it's descendants) /// - Task> Delete(string id, bool moveToRecycleBin = true); + Task> Delete(string id, bool recursive = true, bool moveToRecycleBin = true); /// /// Restores a page from the recycle bin diff --git a/zero.Web/Controllers/PagesController.cs b/zero.Web/Controllers/PagesController.cs index f66e1af0..e8f7355b 100644 --- a/zero.Web/Controllers/PagesController.cs +++ b/zero.Web/Controllers/PagesController.cs @@ -1,114 +1,89 @@ using Microsoft.AspNetCore.Mvc; using Raven.Client.Documents; -using Raven.Client.Documents.Session; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using zero.Core.Api; -using zero.Core.Database; +using zero.Core.Collections; using zero.Core.Entities; +using zero.Core.Extensions; using zero.Core.Routing; using zero.Web.Models; namespace zero.Web.Controllers { - public class PagesController : BackofficeController + public class PagesController : BackofficeCollectionController { - IPagesApi Api; - IZeroStore Store; + IRoutes Routes; - public PagesController(IPagesApi api, IZeroStore store) + public PagesController(IPagesCollection collection, IRoutes routes) : base(collection) { - Api = api; - Store = store; + Routes = routes; } - public async Task> GetAllowedPageTypes([FromQuery] string parent = null) => await Api.GetAllowedPageTypes(parent); - - - public PageType GetPageType([FromQuery] string alias) => Api.GetPageType(alias); - - public async Task> GetById([FromQuery] string id) + public override async Task> GetById([FromQuery] string id, [FromQuery] string changeVector = null) { - Page entity = await Api.GetById(id); + Page entity = changeVector.IsNullOrEmpty() ? await Collection.GetById(id) : await Collection.GetRevision(changeVector); - if (entity == null) - { - return null; - } - - return Edit>(new PageEditModel() + return entity == null ? null : Edit>(new PageEditModel() { Entity = entity, - //Revisions = await RevisionsApi.GetPaged(id), - PageType = Api.GetPageType(entity.PageTypeAlias) + PageType = Collection.GetPageType(entity.PageTypeAlias) }); } - public async Task> GetEmpty([FromQuery] string type, [FromQuery] string parent = null) - { - return Edit(await Api.GetEmpty(type, parent)); - } - - - public async Task> GetPreviews([FromQuery] List ids) + public override async Task> GetPreviews([FromQuery] List ids) { IReadOnlyCollection pageTypes = Options.Pages.GetAllItems(); - - using IAsyncDocumentSession session = Store.OpenAsyncSession(); - - Dictionary pages = await session.LoadAsync(ids); - Dictionary routes = await session.LoadAsync(pages.Where(x => x.Value != null).Select(x => "routes." + x.Value.Hash)); + Dictionary pages = await Collection.GetByIds(ids.ToArray()); + Dictionary routes = await Routes.GetRoutes(pages.Where(x => x.Value != null).Select(x => x.Value)); return Previews(pages, item => { - PageType pageType = pageTypes.FirstOrDefault(x => x.Alias == item.PageTypeAlias); - Route route = null; + routes.TryGetValue(item, out Route route); - if (!routes.TryGetValue("routes." + item.Hash, out route) || route == null) - { - route = new Route() - { - Url = "No URL found" // TODO translate - }; - } - - return new PreviewModel() + PreviewModel model = new() { Id = item.Id, - Icon = pageType?.Icon ?? "fth-folder", + Icon = pageTypes.FirstOrDefault(x => x.Alias == item.PageTypeAlias)?.Icon ?? "fth-folder", Name = item.Name, - Text = route.Url + Text = route?.Url.Or("No URL found") }; + + PreviewTransform?.Invoke(item, model); + return model; }); + } - //public async Task> GetRevisions([FromQuery] string id, [FromQuery] int page = 1) => await RevisionsApi.GetPaged(id, page); - // TODO this endpoint is available when pages controller moved to BackofficeCollectionController + public PageType GetPageType([FromQuery] string alias) => Collection.GetPageType(alias); - public async Task> Save([FromBody] Page model) => await Api.Save(model); + public async Task> GetAllowedPageTypes([FromQuery] string parent = null) => await Collection.GetAllowedPageTypes(parent); + + + public async Task> GetEmpty([FromQuery] string type, [FromQuery] string parent = null) => Edit(await Collection.GetEmpty(type, parent)); [HttpPost] - public async Task>> SaveSorting([FromBody] string[] ids) => await Api.SaveSorting(ids); + public async Task>> SaveSorting([FromBody] string[] ids) => await Collection.SaveSorting(ids); [HttpPost] - public async Task> Move([FromBody] ActionCopyModel model) => await Api.Move(model.Id, model.DestinationId); + public async Task> Move([FromBody] ActionCopyModel model) => await Collection.Move(model.Id, model.DestinationId); [HttpPost] - public async Task> Copy([FromBody] ActionCopyModel model) => await Api.Copy(model.Id, model.DestinationId, model.IncludeDescendants); + public async Task> Copy([FromBody] ActionCopyModel model) => await Collection.Copy(model.Id, model.DestinationId, model.IncludeDescendants); [HttpPost] - public async Task> Restore([FromBody] ActionCopyModel model) => await Api.Restore(model.Id, model.IncludeDescendants); + public async Task> Restore([FromBody] ActionCopyModel model) => await Collection.Restore(model.Id, model.IncludeDescendants); - public async Task> Delete([FromQuery] string id) => await Api.Delete(id, true); + [HttpDelete] + public async Task> DeleteRecursive([FromQuery] string id) => await Collection.Delete(id, recursive: true, moveToRecycleBin: true); } } diff --git a/zero.Web/Defaults/ZeroBackofficePlugin.cs b/zero.Web/Defaults/ZeroBackofficePlugin.cs index db851b17..936b4c11 100644 --- a/zero.Web/Defaults/ZeroBackofficePlugin.cs +++ b/zero.Web/Defaults/ZeroBackofficePlugin.cs @@ -87,8 +87,8 @@ namespace zero.Web.Defaults services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient();