diff --git a/BewerbungWebseite/Services/PageContentSerializer.cs b/BewerbungWebseite/Services/PageContentSerializer.cs new file mode 100644 index 0000000..95ba743 --- /dev/null +++ b/BewerbungWebseite/Services/PageContentSerializer.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using BewerbungsSeite.Models.SiteContent; + +namespace BewerbungsSeite.Services; + +/// +/// Wandelt den Seiteninhalt zwischen Objektmodell und UTF-8-JSON um, so wie er +/// im Feld PageContent.Content abgelegt wird. +/// +public static class PageContentSerializer +{ + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + WriteIndented = false + }; + + public static byte[] Serialize(PageContent content) => + JsonSerializer.SerializeToUtf8Bytes(content, Options); + + /// + /// Liest den Inhalt zurueck. Fehlende oder unbekannte Felder fuehren nicht zu + /// einem Fehler: unbekannte werden ignoriert, fehlende erhalten ihren Initialwert. + /// + public static PageContent Deserialize(byte[]? content) + { + if (content is null || content.Length == 0) + { + return new PageContent(); + } + + var result = JsonSerializer.Deserialize(content, Options) ?? new PageContent(); + return Normalize(result); + } + + /// + /// Stellt sicher, dass kein Bereich null ist, auch wenn ein aelterer + /// Datensatz ihn noch nicht kennt oder ihn ausdruecklich als null enthaelt. + /// + private static PageContent Normalize(PageContent content) + { + content.Hero ??= new HeroSection(); + content.Hero.TitleLines ??= new List(); + content.Hero.PrimaryButton ??= new LinkItem(); + content.Hero.SecondaryButton ??= new LinkItem(); + content.Hero.SocialLinks ??= new List(); + + content.About ??= new AboutSection(); + content.About.Facts ??= new List(); + + content.Skills ??= new SkillsSection(); + content.Skills.Items ??= new List(); + + content.Projects ??= new ProjectsSection(); + content.Projects.Items ??= new List(); + foreach (var project in content.Projects.Items) + { + project.Details ??= new List(); + project.Links ??= new List(); + } + + content.Goals ??= new GoalsSection(); + content.Goals.Items ??= new List(); + + content.Experience ??= new ExperienceSection(); + content.Experience.Items ??= new List(); + + content.Documents ??= new DocumentsSection(); + content.Documents.Items ??= new List(); + + content.Contact ??= new ContactSection(); + content.Contact.InfoBoxes ??= new List(); + + return content; + } +} diff --git a/BewerbungWebseite/Services/PageContentService.cs b/BewerbungWebseite/Services/PageContentService.cs new file mode 100644 index 0000000..5e71abe --- /dev/null +++ b/BewerbungWebseite/Services/PageContentService.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore; +using BewerbungsSeite.Data; +using BewerbungsSeite.Models; +using BewerbungsSeite.Models.SiteContent; + +namespace BewerbungsSeite.Services; + +public interface IPageContentService +{ + /// Laedt den Seiteninhalt. Existiert noch keine Zeile, kommt ein leerer Inhalt zurueck. + Task GetAsync(CancellationToken cancellationToken = default); + + /// Schreibt den kompletten Seiteninhalt in die eine Zeile zurueck. + Task SaveAsync(PageContent content, CancellationToken cancellationToken = default); +} + +public class PageContentService(RazorPagesMovieContext context) : IPageContentService +{ + public async Task GetAsync(CancellationToken cancellationToken = default) + { + var row = await context.PageContent + .AsNoTracking() + .SingleOrDefaultAsync(p => p.Id == PageContentEntity.SingletonId, cancellationToken); + + return PageContentSerializer.Deserialize(row?.Content); + } + + public async Task SaveAsync(PageContent content, CancellationToken cancellationToken = default) + { + var row = await context.PageContent + .SingleOrDefaultAsync(p => p.Id == PageContentEntity.SingletonId, cancellationToken); + + if (row is null) + { + row = new PageContentEntity { Id = PageContentEntity.SingletonId }; + context.PageContent.Add(row); + } + + row.Content = PageContentSerializer.Serialize(content); + row.UpdatedAt = DateTime.UtcNow; + + await context.SaveChangesAsync(cancellationToken); + } +}