Files
mixtape/zero.Core/Collections/Pages/PagesCollection.cs
T

399 lines
12 KiB
C#
Raw Normal View History

using FluentValidation;
2020-11-06 14:16:11 +01:00
using Microsoft.Extensions.Logging;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
2020-08-14 13:26:48 +02:00
using System;
2020-04-06 15:53:32 +02:00
using System.Collections.Generic;
2020-05-19 15:53:01 +02:00
using System.Linq;
2020-04-06 15:53:32 +02:00
using System.Threading.Tasks;
2021-10-05 14:08:39 +02:00
using zero.Core.Api;
2020-08-14 13:26:48 +02:00
using zero.Core.Database.Indexes;
2020-04-06 15:53:32 +02:00
using zero.Core.Entities;
2020-05-19 15:53:01 +02:00
using zero.Core.Extensions;
2020-11-06 14:16:11 +01:00
using zero.Core.Handlers;
2020-04-06 15:53:32 +02:00
2021-10-05 14:08:39 +02:00
namespace zero.Core.Collections
2020-04-06 15:53:32 +02:00
{
2021-10-05 14:08:39 +02:00
public class PagesCollection : CollectionBase<Page>, IPagesCollection
2020-04-06 15:53:32 +02:00
{
2020-08-26 11:10:52 +02:00
const string RECYCLE_BIN_GROUP = "pages";
protected IRecycleBinApi RecycleBinApi { get; private set; }
2021-10-05 14:08:39 +02:00
protected ILogger<IPagesCollection> Logger { get; private set; }
2020-11-06 14:16:11 +01:00
protected IHandlerHolder Handler { get; private set; }
2021-10-13 12:47:18 +02:00
public PagesCollection(ICollectionContext context, IRecycleBinApi recycleBinApi, IHandlerHolder handler,
ILogger<IPagesCollection> logger, IValidator<Page> validator = null) : base(context, validator)
2020-04-06 15:53:32 +02:00
{
2020-08-26 11:10:52 +02:00
RecycleBinApi = recycleBinApi;
2020-11-06 14:16:11 +01:00
Handler = handler;
2021-10-05 14:08:39 +02:00
Logger = logger;
2020-11-06 14:16:11 +01:00
}
/// <inheritdoc />
2021-05-04 17:23:52 +02:00
public Task<Page> GetEmpty(string pageType, string parentId = null)
2020-11-06 14:16:11 +01:00
{
PageType type = GetPageType(pageType);
if (type == null)
{
2021-05-04 17:23:52 +02:00
return Task.FromResult<Page>(null);
2020-11-06 14:16:11 +01:00
}
try
{
2021-05-04 17:23:52 +02:00
Page model = Activator.CreateInstance(type.ContentType) as Page;
2020-11-06 14:16:11 +01:00
model.PageTypeAlias = type.Alias;
model.ParentId = parentId; // TODO validate if type is allowed and if parentid is allowed
return Task.FromResult(model);
}
catch
{
Logger.LogWarning("Could not create page with type {type}", type);
}
2021-05-04 17:23:52 +02:00
return Task.FromResult<Page>(null);
2020-04-06 15:53:32 +02:00
}
/// <inheritdoc />
2021-10-05 14:08:39 +02:00
public IList<PageType> GetPageTypes() => Context.Options.Pages.GetAllItems().ToList();
2020-05-19 15:53:01 +02:00
2020-10-29 21:44:22 +01:00
/// <inheritdoc />
2021-10-05 14:08:39 +02:00
public PageType GetPageType(string alias) => Context.Options.Pages.GetAllItems().FirstOrDefault(x => x.Alias == alias);
2020-05-19 15:53:01 +02:00
/// <inheritdoc />
public async Task<IList<PageType>> GetAllowedPageTypes(string parentId = null)
2021-10-05 14:08:39 +02:00
{
IEnumerable<PageType> types = Context.Options.Pages.GetAllItems();
2021-05-04 17:23:52 +02:00
List<Page> parents = new();
2020-05-19 15:53:01 +02:00
2020-12-04 13:18:12 +01:00
if (!parentId.IsNullOrEmpty())
2020-05-19 15:53:01 +02:00
{
2021-10-05 14:08:39 +02:00
Pages_ByHierarchy.Result result = await Session.Query<Pages_ByHierarchy.Result, Pages_ByHierarchy>()
2020-12-04 13:18:12 +01:00
.ProjectInto<Pages_ByHierarchy.Result>()
2021-05-04 17:23:52 +02:00
.Include<Pages_ByHierarchy.Result, Page>(x => x.Id)
.Include<Pages_ByHierarchy.Result, Page>(x => x.Path.Select(p => p.Id))
2020-12-04 13:18:12 +01:00
.FirstOrDefaultAsync(x => x.Id == parentId);
if (result != null)
{
List<string> ids = result.Path.Select(x => x.Id).ToList();
ids.Add(result.Id);
2021-10-05 14:08:39 +02:00
parents = (await Session.LoadAsync<Page>(ids)).Select(x => x.Value).Reverse().ToList();
2020-12-04 13:18:12 +01:00
}
2020-05-19 15:53:01 +02:00
}
2020-12-04 13:18:12 +01:00
IPageTypeHandler handler = Handler.Get<IPageTypeHandler>();
2020-05-19 15:53:01 +02:00
2020-12-04 13:18:12 +01:00
// if there is no registered handler we just allow all page types
if (handler == null)
2020-05-19 15:53:01 +02:00
{
2020-12-04 13:18:12 +01:00
return types.ToList();
2020-05-19 15:53:01 +02:00
}
2021-10-11 13:58:14 +02:00
return (await handler.GetAllowedPageTypes(Context.Application, types, parents))?.ToList() ?? new();
2020-05-25 11:27:23 +02:00
}
2020-08-13 14:58:01 +02:00
/// <inheritdoc />
2021-05-04 17:23:52 +02:00
public async Task<EntityResult<IList<Page>>> SaveSorting(string[] sortedIds)
2020-08-13 14:58:01 +02:00
{
2021-10-05 14:08:39 +02:00
Dictionary<string, Page> items = await GetByIds(sortedIds);
2020-08-13 14:58:01 +02:00
uint index = 0;
// contains multiple parents, therefore fail
if (items.Select(x => x.Value?.ParentId).Distinct().Count() > 1)
{
2021-05-04 17:23:52 +02:00
return EntityResult<IList<Page>>.Fail("@errors.page.sortingmultipleparents");
}
2020-08-13 14:58:01 +02:00
2021-10-05 14:08:39 +02:00
foreach (var item in items)
2020-08-13 14:58:01 +02:00
{
2021-10-05 14:08:39 +02:00
item.Value.Sort = index;
index += 10;
await Save(item.Value);
//session.Advanced.Patch<T, uint>(item.Value.Id, x => x.Sort, index++);
2020-08-13 14:58:01 +02:00
}
2021-05-04 17:23:52 +02:00
return EntityResult<IList<Page>>.Success(items.Select(x => x.Value).ToList());
2020-08-13 14:58:01 +02:00
}
2020-08-13 15:55:59 +02:00
/// <inheritdoc />
2021-05-04 17:23:52 +02:00
public async Task<EntityResult<Page>> Move(string id, string parentId)
2020-08-13 15:55:59 +02:00
{
2021-10-05 14:08:39 +02:00
Page model = await GetById(id);
Page parent = await GetById(parentId);
if (model == null || (!parentId.IsNullOrEmpty() && parent == null))
{
2021-05-04 17:23:52 +02:00
return EntityResult<Page>.Fail("@errors.idnotfound");
}
IList<PageType> pageTypes = await GetAllowedPageTypes(parentId);
if (!pageTypes.Any(x => x.Alias == model.PageTypeAlias))
{
2021-05-04 17:23:52 +02:00
return EntityResult<Page>.Fail("@errors.page.parentnotallowed");
}
2020-10-12 13:14:23 +02:00
model.ParentId = parent?.Id;
2020-08-13 15:55:59 +02:00
return await Save(model);
}
2020-08-14 13:26:48 +02:00
/// <inheritdoc />
2021-05-04 17:23:52 +02:00
public async Task<EntityResult<Page>> Copy(string id, string destinationId, bool includeDescendants = false)
2020-08-14 13:26:48 +02:00
{
2021-10-05 14:08:39 +02:00
Page model = await GetById(id);
Page parent = await GetById(destinationId);
if (model == null || (!destinationId.IsNullOrEmpty() && parent == null))
{
2021-05-04 17:23:52 +02:00
return EntityResult<Page>.Fail("@errors.idnotfound");
}
2020-08-14 13:26:48 +02:00
string baseId = model.Id;
// update new page properties
model.Id = null;
2020-10-12 13:14:23 +02:00
model.ParentId = parent?.Id;
2020-08-14 13:26:48 +02:00
model.IsActive = false;
model.CreatedDate = DateTimeOffset.Now;
// check if new parent is allowed
IList<PageType> pageTypes = await GetAllowedPageTypes(destinationId);
if (!pageTypes.Any(x => x.Alias == model.PageTypeAlias))
{
2021-05-04 17:23:52 +02:00
return EntityResult<Page>.Fail("@errors.page.parentnotallowed");
}
2021-10-05 14:08:39 +02:00
// recursive function to store descendants
async Task AddChildren(string oldParentId, string newParentId)
2020-08-14 13:26:48 +02:00
{
2021-10-05 14:08:39 +02:00
Pages_WithChildren.Result childrenResult = await Session.Query<Pages_WithChildren.Result, Pages_WithChildren>()
.ProjectInto<Pages_WithChildren.Result>()
.Include<Pages_WithChildren.Result, Page>(x => x.Id)
.Where(x => x.Id == oldParentId)
.FirstOrDefaultAsync();
2020-08-14 13:26:48 +02:00
2021-10-05 14:08:39 +02:00
if (childrenResult == null || childrenResult.ChildrenIds.Length < 1)
2020-08-14 13:26:48 +02:00
{
2021-10-05 14:08:39 +02:00
return;
2020-08-14 13:26:48 +02:00
}
2021-10-05 14:08:39 +02:00
Dictionary<string, Page> childrenPages = await GetByIds(childrenResult.ChildrenIds);
2020-08-14 13:26:48 +02:00
2021-10-05 14:08:39 +02:00
foreach (var child in childrenPages)
2020-08-14 13:26:48 +02:00
{
2021-10-05 14:08:39 +02:00
Page childPage = child.Value.Clone();
childPage.Id = null;
childPage.IsActive = false;
childPage.ParentId = newParentId;
childPage.CreatedDate = DateTimeOffset.Now;
2020-08-14 13:26:48 +02:00
2021-10-05 14:08:39 +02:00
await Save(childPage);
await AddChildren(child.Key, childPage.Id);
}
}
if (includeDescendants)
{
await AddChildren(baseId, model.Id);
2020-08-14 13:26:48 +02:00
}
2021-05-04 17:23:52 +02:00
return EntityResult<Page>.Success(model);
2020-08-14 13:26:48 +02:00
}
2020-05-25 11:27:23 +02:00
/// <inheritdoc />
2021-10-05 14:08:39 +02:00
public async Task<EntityResult<string[]>> Delete(string id, bool recursive = true, bool moveToRecycleBin = true)
2020-05-25 11:27:23 +02:00
{
2021-10-05 14:08:39 +02:00
List<Page> pages = recursive ? await GetByIdWithDescendants(id) : new() { await GetById(id) };
2020-08-14 14:42:31 +02:00
if (pages.Count < 1)
{
return EntityResult<string[]>.Fail("@errors.ondelete.idnotfound");
}
2020-08-26 11:10:52 +02:00
if (moveToRecycleBin)
2020-08-14 14:42:31 +02:00
{
2020-08-26 11:10:52 +02:00
await RecycleBinApi.Add(pages, RECYCLE_BIN_GROUP);
2020-08-14 14:42:31 +02:00
}
2021-10-05 14:08:39 +02:00
await Delete(pages.ToArray());
2020-08-26 11:10:52 +02:00
2021-10-05 14:08:39 +02:00
return EntityResult<string[]>.Success(pages.Select(x => x.Id).ToArray());
2020-08-14 14:42:31 +02:00
}
2020-09-04 11:50:22 +02:00
/// <inheritdoc />
2020-08-26 23:32:48 +02:00
public async Task<EntityResult<string[]>> Restore(string id, bool includeDescendants = false)
{
2020-08-27 11:41:40 +02:00
EntityResult<string[]> result = new EntityResult<string[]>();
2021-05-04 17:23:52 +02:00
RecycledEntity recycledEntity = await RecycleBinApi.GetById(id);
List<RecycledEntity> entities = new List<RecycledEntity>() { recycledEntity };
2020-08-14 14:42:31 +02:00
2020-08-27 11:41:40 +02:00
if (recycledEntity == null)
2020-08-26 23:32:48 +02:00
{
return EntityResult<string[]>.Fail(); // TODO correct error message
}
2020-08-14 14:42:31 +02:00
2020-08-27 11:41:40 +02:00
// get descendants from the operation
if (includeDescendants && !recycledEntity.OperationId.IsNullOrEmpty())
2020-08-26 23:32:48 +02:00
{
2020-08-27 11:41:40 +02:00
entities = (await RecycleBinApi.GetByOperation(recycledEntity.OperationId)).ToList();
2020-08-26 23:32:48 +02:00
}
2020-08-27 11:41:40 +02:00
// fill ids
string[] ids = entities.Select(x => x.OriginalId).ToArray();
// check if parents are available
2021-05-04 17:23:52 +02:00
string[] parentIds = entities.Select(x => x.Content as Page).Where(x => x != null).Select(x => x.ParentId).ToArray();
2021-10-05 14:08:39 +02:00
parentIds = (await GetByIds(parentIds)).Where(x => x.Value != null).Select(x => x.Value.Id).ToArray();
2020-08-27 11:41:40 +02:00
// validate and restore all items
2021-05-04 17:23:52 +02:00
foreach (RecycledEntity entity in entities)
2020-08-27 11:41:40 +02:00
{
// check if it contains page data
2021-05-04 17:23:52 +02:00
if (entity.Group != RECYCLE_BIN_GROUP || !(entity.Content is Page))
2020-08-27 11:41:40 +02:00
{
2021-05-04 17:23:52 +02:00
//result.AddError("Cannot parse recycled entity as an Page in group \"" + RECYCLE_BIN_GROUP + "\""); // TODO correct error message
2020-08-27 11:41:40 +02:00
continue;
}
// get page
2021-05-04 17:23:52 +02:00
Page page = entity.Content as Page;
2020-08-27 11:41:40 +02:00
page.IsActive = false;
// validate app and parent
2020-11-14 12:34:26 +01:00
if (!page.ParentId.IsNullOrEmpty() && !ids.Contains(page.ParentId) && !parentIds.Contains(page.ParentId))
2020-08-27 11:41:40 +02:00
{
// TODO correct error message
continue;
}
// restore to pages
2021-10-05 14:08:39 +02:00
EntityResult<Page> saveResult = await Save(page);
2020-08-27 11:41:40 +02:00
}
// delete affected entities from recycle bin
if (!recycledEntity.OperationId.IsNullOrEmpty())
{
await RecycleBinApi.DeleteByOperation(recycledEntity.OperationId);
}
// set result
result.Model = ids;
result.IsSuccess = true;
return result;
2020-08-26 23:32:48 +02:00
}
2020-08-14 14:42:31 +02:00
/// <summary>
/// Get a page with all its descendants
/// </summary>
2021-05-04 17:23:52 +02:00
async Task<List<Page>> GetByIdWithDescendants(string id)
2020-08-14 14:42:31 +02:00
{
2021-05-04 17:23:52 +02:00
List<Page> items = new List<Page>();
2020-08-14 14:42:31 +02:00
2021-10-05 14:08:39 +02:00
Page model = await GetById(id);
2020-08-14 14:42:31 +02:00
if (model == null)
{
return items;
}
items.Add(model);
2021-10-05 14:08:39 +02:00
// recursive function to store descendants
async Task AddChildren(string parentId)
2020-08-14 14:42:31 +02:00
{
2021-10-05 14:08:39 +02:00
Pages_WithChildren.Result childrenResult = await Session.Query<Pages_WithChildren.Result, Pages_WithChildren>()
.ProjectInto<Pages_WithChildren.Result>()
.Include<Pages_WithChildren.Result, Page>(x => x.Id)
.Where(x => x.Id == parentId)
.FirstOrDefaultAsync();
if (childrenResult == null || childrenResult.ChildrenIds.Length < 1)
2020-08-14 14:42:31 +02:00
{
2021-10-05 14:08:39 +02:00
return;
2020-08-14 14:42:31 +02:00
}
2021-10-05 14:08:39 +02:00
Dictionary<string, Page> childrenPages = await GetByIds(childrenResult.ChildrenIds);
foreach (var child in childrenPages)
{
items.Add(child.Value);
await AddChildren(child.Value.Id);
}
2020-08-14 14:42:31 +02:00
}
2021-10-05 14:08:39 +02:00
await AddChildren(model.Id);
2020-08-14 14:42:31 +02:00
return items;
2020-05-25 11:27:23 +02:00
}
2020-04-06 15:53:32 +02:00
}
2021-10-05 14:08:39 +02:00
public interface IPagesCollection : ICollectionBase<Page>
2020-04-06 15:53:32 +02:00
{
2020-11-06 14:16:11 +01:00
/// <summary>
/// Get a new empty page with the specified type
/// </summary>
2021-05-04 17:23:52 +02:00
public Task<Page> GetEmpty(string pageType, string parentId = null);
2020-11-06 14:16:11 +01:00
2020-04-06 15:53:32 +02:00
/// <summary>
2020-05-19 15:53:01 +02:00
/// Get all available page types
2020-04-06 15:53:32 +02:00
/// </summary>
2020-05-19 15:53:01 +02:00
IList<PageType> GetPageTypes();
/// <summary>
/// Get all page types which are allowed below a selected parent page
/// </summary>
Task<IList<PageType>> GetAllowedPageTypes(string parentId = null);
2020-05-25 11:27:23 +02:00
2020-07-08 13:59:46 +02:00
/// <summary>
/// Get a specific page type by alias
/// </summary>
PageType GetPageType(string alias);
2020-08-13 14:58:01 +02:00
/// <summary>
/// Update sorting of pages on a specific level
/// </summary>
2021-05-04 17:23:52 +02:00
Task<EntityResult<IList<Page>>> SaveSorting(string[] sortedIds);
2020-08-13 14:58:01 +02:00
2020-08-13 15:55:59 +02:00
/// <summary>
/// Move a page to a new parent
/// </summary>
2021-05-04 17:23:52 +02:00
Task<EntityResult<Page>> Move(string id, string parentId);
2020-08-13 15:55:59 +02:00
2020-08-14 13:26:48 +02:00
/// <summary>
/// Copies a page (with optional descendants) to a new location
/// </summary>
2021-05-04 17:23:52 +02:00
Task<EntityResult<Page>> Copy(string id, string destinationId, bool includeDescendants = false);
2020-08-14 13:26:48 +02:00
2020-05-25 11:27:23 +02:00
/// <summary>
2020-08-14 14:42:31 +02:00
/// Deletes a page by Id (with all it's descendants)
2020-05-25 11:27:23 +02:00
/// </summary>
2021-10-05 14:08:39 +02:00
Task<EntityResult<string[]>> Delete(string id, bool recursive = true, bool moveToRecycleBin = true);
2020-08-14 14:42:31 +02:00
/// <summary>
/// Restores a page from the recycle bin
/// </summary>
2020-08-26 23:32:48 +02:00
Task<EntityResult<string[]>> Restore(string id, bool includeDescendants = false);
2020-04-06 15:53:32 +02:00
}
}