using FlatRender.ContentSvc.Domain.Entities; using FlatRender.ContentSvc.Infrastructure.Data; using FlatRender.ContentSvc.Models; using Microsoft.EntityFrameworkCore; namespace FlatRender.ContentSvc.Application.Services; /// /// CRUD for admin-authored preset stories (premade example videos) per template. /// Public callers see published stories only; admins see drafts too. /// public class PresetStoryService(ContentDbContext db) { public async Task> GetByProjectAsync(Guid projectId, bool publishedOnly) => await db.PresetStories .Where(s => s.ProjectId == projectId && (!publishedOnly || s.IsPublished)) .OrderBy(s => s.Sort).ThenByDescending(s => s.CreatedAt) .Select(s => new PresetStorySummary( s.Id, s.ProjectId, s.Name, s.Description, s.Demo, s.MusicId, s.Sort, s.IsPublished, s.Scenes.Count, s.CreatedAt, s.UpdatedAt)) .ToListAsync(); public async Task GetAsync(Guid id, bool publishedOnly) { var s = await db.PresetStories .Include(x => x.Scenes).ThenInclude(ps => ps.Scene) .FirstOrDefaultAsync(x => x.Id == id); if (s == null || (publishedOnly && !s.IsPublished)) return null; return ToResponse(s); } public async Task CreateAsync(SavePresetStoryRequest req) { var story = new PresetStory { ProjectId = req.ProjectId, Name = req.Name, Description = req.Description, Demo = req.Demo, MusicId = req.MusicId, ScenesSpa = req.ScenesSpa, Sort = req.Sort, IsPublished = req.IsPublished, Scenes = MapScenes(req.Scenes), }; db.PresetStories.Add(story); await db.SaveChangesAsync(); return await ReloadAsync(story.Id); } public async Task UpdateAsync(Guid id, SavePresetStoryRequest req) { var story = await db.PresetStories.Include(s => s.Scenes).FirstOrDefaultAsync(s => s.Id == id) ?? throw new KeyNotFoundException($"Preset story {id} not found"); story.Name = req.Name; story.Description = req.Description; story.Demo = req.Demo; story.MusicId = req.MusicId; if (req.ScenesSpa != null) story.ScenesSpa = req.ScenesSpa; story.Sort = req.Sort; story.IsPublished = req.IsPublished; story.UpdatedAt = DateTime.UtcNow; // Replace the scene set wholesale (small, ordered list). if (req.Scenes != null) { db.PresetScenes.RemoveRange(story.Scenes); story.Scenes = MapScenes(req.Scenes); } await db.SaveChangesAsync(); return await ReloadAsync(story.Id); } public async Task DeleteAsync(Guid id) { var story = await db.PresetStories.FirstOrDefaultAsync(s => s.Id == id) ?? throw new KeyNotFoundException($"Preset story {id} not found"); story.DeletedAt = DateTime.UtcNow; // soft delete (global query filter hides it) await db.SaveChangesAsync(); } // ── helpers ─────────────────────────────────────────────────────────────── private static List MapScenes(List? scenes) => (scenes ?? []).Select(s => new PresetScene { SceneId = s.SceneId, Sort = s.Sort, DefaultDurationSec = s.DefaultDurationSec, }).ToList(); private async Task ReloadAsync(Guid id) => ToResponse(await db.PresetStories.Include(x => x.Scenes).ThenInclude(ps => ps.Scene) .FirstAsync(x => x.Id == id)); private static PresetStoryResponse ToResponse(PresetStory s) => new( s.Id, s.ProjectId, s.Name, s.Description, s.Demo, s.MusicId, s.ScenesSpa, s.Sort, s.IsPublished, s.CreatedAt, s.UpdatedAt, s.Scenes.OrderBy(ps => ps.Sort).Select(ps => new PresetSceneResponse( ps.Id, ps.SceneId, ps.Scene?.Key, ps.Scene?.Title, ps.Sort, ps.DefaultDurationSec)).ToList()); }