using FlatRender.ContentSvc.Domain.Entities; using FlatRender.ContentSvc.Infrastructure.Data; using FlatRender.ContentSvc.Models.Requests; using FlatRender.ContentSvc.Models.Responses; using Microsoft.EntityFrameworkCore; namespace FlatRender.ContentSvc.Application.Services; public class TaxonomyService(ContentDbContext db) { public async Task> GetCategoryTreeAsync() { var all = await db.Categories .Where(x => x.ParentId == null) .Include(x => x.Children) .OrderBy(x => x.Sort) .ToListAsync(); return all.Select(MapCategory).ToList(); } public async Task CreateCategoryAsync(CreateCategoryRequest req) { var cat = new Category { ParentId = req.ParentId, Name = req.Name, Slug = req.Slug, Description = req.Description, ImageUrl = req.ImageUrl, Icon = req.Icon, MetaTitle = req.MetaTitle, MetaDescription = req.MetaDescription, MetaKeywords = req.MetaKeywords, BotFollow = req.BotFollow, Sort = req.Sort, IsActive = req.IsActive }; db.Categories.Add(cat); await db.SaveChangesAsync(); return MapCategory(cat); } public async Task UpdateCategoryAsync(Guid id, UpdateCategoryRequest req) { var cat = await db.Categories.FindAsync(id) ?? throw new KeyNotFoundException($"Category {id} not found"); cat.ParentId = req.ParentId; cat.Name = req.Name; cat.Slug = req.Slug; cat.Description = req.Description; cat.ImageUrl = req.ImageUrl; cat.Icon = req.Icon; cat.MetaTitle = req.MetaTitle; cat.MetaDescription = req.MetaDescription; cat.MetaKeywords = req.MetaKeywords; cat.BotFollow = req.BotFollow; cat.Sort = req.Sort; cat.IsActive = req.IsActive; cat.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return MapCategory(cat); } public async Task DeleteCategoryAsync(Guid id) { var cat = await db.Categories.FindAsync(id) ?? throw new KeyNotFoundException($"Category {id} not found"); cat.DeletedAt = DateTime.UtcNow; await db.SaveChangesAsync(); } public async Task> GetTagsAsync(int page, int pageSize, string? search) { var q = db.Tags.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%")); var total = await q.LongCountAsync(); var items = await q.OrderBy(x => x.Name).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); return new PagedResponse( items.Select(t => new TagResponse(t.Id, t.Name, t.LatinName, t.Slug, t.AppliesToMode, t.IsActive)), new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize)) ); } public async Task CreateTagAsync(CreateTagRequest req) { var tag = new Tag { Name = req.Name, LatinName = req.LatinName, Slug = req.Slug, AppliesToMode = req.AppliesToMode, IsActive = req.IsActive }; db.Tags.Add(tag); await db.SaveChangesAsync(); return new TagResponse(tag.Id, tag.Name, tag.LatinName, tag.Slug, tag.AppliesToMode, tag.IsActive); } public async Task UpdateTagAsync(Guid id, UpdateTagRequest req) { var tag = await db.Tags.FindAsync(id) ?? throw new KeyNotFoundException($"Tag {id} not found"); tag.Name = req.Name; tag.LatinName = req.LatinName; tag.Slug = req.Slug; tag.AppliesToMode = req.AppliesToMode; tag.IsActive = req.IsActive; tag.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return new TagResponse(tag.Id, tag.Name, tag.LatinName, tag.Slug, tag.AppliesToMode, tag.IsActive); } public async Task DeleteTagAsync(Guid id) { var tag = await db.Tags.FindAsync(id) ?? throw new KeyNotFoundException($"Tag {id} not found"); tag.DeletedAt = DateTime.UtcNow; await db.SaveChangesAsync(); } public async Task> GetFontsAsync(int page, int pageSize, string? search, string? direction) { var q = db.Fonts.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%")); if (!string.IsNullOrWhiteSpace(direction)) q = q.Where(x => x.Direction == direction); var total = await q.LongCountAsync(); var items = await q.OrderBy(x => x.Sort).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); return new PagedResponse( items.Select(MapFont), new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize)) ); } public async Task CreateFontAsync(CreateFontRequest req) { var font = new Font { Name = req.Name, OriginalName = req.OriginalName, SystemName = req.SystemName, Family = req.Family, Weight = req.Weight, Style = req.Style, Direction = req.Direction, FileUrl = req.FileUrl, SampleImageUrl = req.SampleImageUrl, IsPremium = req.IsPremium, IsActive = req.IsActive, InstalledOnNodes = req.InstalledOnNodes, Sort = req.Sort }; db.Fonts.Add(font); await db.SaveChangesAsync(); return MapFont(font); } public async Task UpdateFontAsync(Guid id, UpdateFontRequest req) { var font = await db.Fonts.FindAsync(id) ?? throw new KeyNotFoundException($"Font {id} not found"); font.Name = req.Name; font.OriginalName = req.OriginalName; font.SystemName = req.SystemName; font.Family = req.Family; font.Weight = req.Weight; font.Style = req.Style; font.Direction = req.Direction; font.FileUrl = req.FileUrl; font.SampleImageUrl = req.SampleImageUrl; font.IsPremium = req.IsPremium; font.IsActive = req.IsActive; font.InstalledOnNodes = req.InstalledOnNodes; font.Sort = req.Sort; font.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(); return MapFont(font); } public async Task DeleteFontAsync(Guid id) { var font = await db.Fonts.FindAsync(id) ?? throw new KeyNotFoundException($"Font {id} not found"); font.DeletedAt = DateTime.UtcNow; await db.SaveChangesAsync(); } public async Task> GetMusicTracksAsync(int page, int pageSize, string? search) { var q = db.MusicTracks.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) q = q.Where(x => EF.Functions.ILike(x.Name, $"%{search}%")); var total = await q.LongCountAsync(); var items = await q.OrderBy(x => x.Sort).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync(); return new PagedResponse( items.Select(MapMusic), new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize)) ); } public async Task CreateMusicTrackAsync(CreateMusicTrackRequest req) { var track = new MusicTrack { Name = req.Name, Caption = req.Caption, Keywords = req.Keywords, Url = req.Url, WaveformData = req.WaveformData, DurationSec = req.DurationSec, Bpm = req.Bpm, Genre = req.Genre, Mood = req.Mood, IsPremium = req.IsPremium, IsActive = req.IsActive, Sort = req.Sort }; db.MusicTracks.Add(track); await db.SaveChangesAsync(); return MapMusic(track); } public async Task DeleteMusicTrackAsync(Guid id) { var track = await db.MusicTracks.FindAsync(id) ?? throw new KeyNotFoundException($"MusicTrack {id} not found"); track.DeletedAt = DateTime.UtcNow; await db.SaveChangesAsync(); } private static CategoryResponse MapCategory(Category c) => new( c.Id, c.ParentId, c.Name, c.Slug, c.Description, c.ImageUrl, c.Icon, c.IsActive, c.Sort, c.Children.Select(MapCategory).ToList() ); private static FontResponse MapFont(Font f) => new( f.Id, f.Name, f.OriginalName, f.SystemName, f.Family, f.Weight, f.Style, f.Direction, f.FileUrl, f.SampleImageUrl, f.IsPremium, f.IsActive, f.InstalledOnNodes ); private static MusicTrackResponse MapMusic(MusicTrack m) => new( m.Id, m.Name, m.Caption, m.Url, m.WaveformData, m.DurationSec, m.Bpm, m.Genre, m.Mood, m.IsPremium ); }