2024-08-19 01:22:48 -05:00
|
|
|
|
using JetBrains.Annotations;
|
|
|
|
|
|
using LANCommander.Server.Data;
|
|
|
|
|
|
using LANCommander.Server.Data.Models;
|
2024-10-07 18:04:26 -05:00
|
|
|
|
using LANCommander.Server.Services.Extensions;
|
2024-08-27 21:35:45 -05:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2024-10-07 18:04:26 -05:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2024-08-19 01:22:48 -05:00
|
|
|
|
using System.Text;
|
2024-11-30 02:09:10 -06:00
|
|
|
|
using System.Text.RegularExpressions;
|
2025-02-01 02:21:34 -06:00
|
|
|
|
using AutoMapper;
|
2025-02-09 18:53:40 -06:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
2024-08-27 21:35:45 -05:00
|
|
|
|
using YamlDotNet.Core.Tokens;
|
|
|
|
|
|
using ZiggyCreatures.Caching.Fusion;
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
|
|
|
|
|
namespace LANCommander.Server.Services
|
|
|
|
|
|
{
|
2025-02-01 02:21:34 -06:00
|
|
|
|
public sealed class PageService(
|
|
|
|
|
|
ILogger<PageService> logger,
|
2025-11-16 12:31:25 -06:00
|
|
|
|
SettingsProvider<Settings.Settings> settingsProvider,
|
2025-02-01 02:21:34 -06:00
|
|
|
|
IFusionCache cache,
|
|
|
|
|
|
IMapper mapper,
|
2025-02-09 18:53:40 -06:00
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
2025-11-16 12:31:25 -06:00
|
|
|
|
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Page>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
|
2024-08-19 01:22:48 -05:00
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public override async Task<Page> AddAsync(Page entity)
|
2024-08-19 01:22:48 -05:00
|
|
|
|
{
|
2024-08-27 21:35:45 -05:00
|
|
|
|
if (entity.Parent != null && entity.Parent.Parent != null && entity.Parent.Parent.Id == entity.Id)
|
|
|
|
|
|
throw new Exception("Tried creating page with circular reference");
|
|
|
|
|
|
|
|
|
|
|
|
entity.Slug = entity.Slug.ToUrlSlug();
|
|
|
|
|
|
|
|
|
|
|
|
if (String.IsNullOrWhiteSpace(entity.Slug))
|
|
|
|
|
|
entity.Slug = entity.Title.ToUrlSlug();
|
|
|
|
|
|
|
|
|
|
|
|
entity.Route = RenderRoute(entity);
|
|
|
|
|
|
|
2024-11-30 02:09:10 -06:00
|
|
|
|
int i = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch all siblings with the same ParentId
|
|
|
|
|
|
var siblings = await GetAsync(p => p.ParentId == entity.ParentId);
|
|
|
|
|
|
|
|
|
|
|
|
// Filter siblings that match the slug pattern
|
|
|
|
|
|
var matchingSiblings = siblings
|
|
|
|
|
|
.Where(s => Regex.IsMatch(s.Slug, @$"^{Regex.Escape(entity.Slug)}(?:-(\d+))?$"))
|
|
|
|
|
|
.Select(s =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// Extract numeric suffix, or use 0 for the base slug
|
|
|
|
|
|
var match = Regex.Match(s.Slug, @$"^{Regex.Escape(entity.Slug)}(?:-(\d+))?$");
|
|
|
|
|
|
return match.Groups[1].Success ? int.Parse(match.Groups[1].Value) : 0;
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// Find the next available number
|
|
|
|
|
|
while (matchingSiblings.Contains(i))
|
|
|
|
|
|
{
|
|
|
|
|
|
i++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set the new slug and route
|
|
|
|
|
|
entity.Slug = $"{entity.Slug}-{i}";
|
|
|
|
|
|
entity.Route = RenderRoute(entity);
|
|
|
|
|
|
|
2025-02-23 08:49:43 -06:00
|
|
|
|
return await base.AddAsync(entity, async context =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Children);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Games);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Parent);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Redistributables);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Servers);
|
|
|
|
|
|
});
|
2024-08-27 21:35:45 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public override async Task<Page> UpdateAsync(Page entity)
|
2024-08-27 21:35:45 -05:00
|
|
|
|
{
|
|
|
|
|
|
if (entity.Parent != null && entity.Parent.Parent != null && entity.Parent.Parent.Id == entity.Id)
|
|
|
|
|
|
throw new Exception("Tried updating page with circular reference");
|
|
|
|
|
|
|
|
|
|
|
|
entity.Slug = entity.Slug.ToUrlSlug();
|
|
|
|
|
|
|
|
|
|
|
|
if (String.IsNullOrWhiteSpace(entity.Slug))
|
|
|
|
|
|
entity.Slug = entity.Title.ToUrlSlug();
|
|
|
|
|
|
|
|
|
|
|
|
entity.Route = RenderRoute(entity);
|
|
|
|
|
|
|
2025-02-09 01:37:31 -06:00
|
|
|
|
await cache.ExpireAsync($"Page/{entity.Route}");
|
2024-08-27 21:35:45 -05:00
|
|
|
|
|
2025-02-02 14:58:07 -06:00
|
|
|
|
return await base.UpdateAsync(entity, async context =>
|
|
|
|
|
|
{
|
2025-02-23 08:49:43 -06:00
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Children);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Games);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Parent);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Redistributables);
|
|
|
|
|
|
await context.UpdateRelationshipAsync(p => p.Servers);
|
2025-02-02 14:58:07 -06:00
|
|
|
|
});
|
2024-08-19 01:22:48 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task ChangeParentAsync(Guid childId, Guid parentId)
|
2024-08-19 01:22:48 -05:00
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
var child = await GetAsync(childId);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
|
|
|
|
|
if (child.ParentId != parentId)
|
|
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
var parent = await GetAsync(parentId);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
|
|
|
|
|
child.Parent = parent;
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await UpdateAsync(child);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task UpdateOrderAsync(Guid parentId, IEnumerable<Guid> childOrder)
|
2024-08-27 20:05:09 -05:00
|
|
|
|
{
|
|
|
|
|
|
var childOrderList = new List<Guid>(childOrder);
|
|
|
|
|
|
var children = new List<Page>();
|
|
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
|
|
children = new List<Page>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var childId in childOrder)
|
|
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
var child = await GetAsync(childId);
|
2024-08-27 20:05:09 -05:00
|
|
|
|
|
|
|
|
|
|
child.SortOrder = i;
|
|
|
|
|
|
|
|
|
|
|
|
if (parentId == Guid.Empty)
|
|
|
|
|
|
child.Parent = null;
|
|
|
|
|
|
|
|
|
|
|
|
children.Add(child);
|
|
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (parentId == Guid.Empty)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var child in children)
|
|
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await UpdateAsync(child);
|
2024-08-27 20:05:09 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
var parent = await GetAsync(parentId);
|
2024-08-27 20:05:09 -05:00
|
|
|
|
|
|
|
|
|
|
parent.Children = children;
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await UpdateAsync(parent);
|
2024-08-27 20:05:09 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
public async Task FixRoutesAsync()
|
2024-08-19 01:22:48 -05:00
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
var parentPages = await GetAsync(p => p.Parent == null);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
2024-08-27 21:35:45 -05:00
|
|
|
|
foreach (var page in parentPages)
|
2024-08-19 01:22:48 -05:00
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await FixRouteAsync(page);
|
2024-08-27 21:35:45 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
private async Task FixRouteAsync(Page page)
|
2024-08-27 21:35:45 -05:00
|
|
|
|
{
|
|
|
|
|
|
page.Route = RenderRoute(page);
|
|
|
|
|
|
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await UpdateAsync(page);
|
2024-08-27 21:35:45 -05:00
|
|
|
|
|
|
|
|
|
|
foreach (var child in page.Children)
|
|
|
|
|
|
{
|
2024-11-12 18:32:46 -06:00
|
|
|
|
await FixRouteAsync(child);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
}
|
2024-08-27 21:35:45 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string RenderRoute(Page page)
|
|
|
|
|
|
{
|
|
|
|
|
|
var parts = new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
if (page.Parent != null)
|
|
|
|
|
|
parts.Add(page.Parent.Route);
|
|
|
|
|
|
else
|
|
|
|
|
|
parts.Add("Pages");
|
|
|
|
|
|
|
|
|
|
|
|
parts.Add(page.Slug);
|
2024-08-19 01:22:48 -05:00
|
|
|
|
|
|
|
|
|
|
return String.Join('/', parts);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|