WEB-0: CMS content model + /api/site-snapshot + Page/Block CRUD
- Entities: Site (singleton, navOrder JSON), Page (slug/title/seoDescription/status enum),
Block (single table, Type enum + Data JSON; RichText=Markdown), Media. Migration
AddCmsEntities, seeds 6 Jimdo-mirror pages (placeholder Heading each; abgabetiere also an
auto AbgabetiereList block) + Site singleton nav.
- GET /api/site-snapshot: one JSON doc (site nav + pages + ordered blocks {id,order,type,data});
AbgabetiereList(auto) resolved from live ForSale gerbils -> {name, farbschlag, group(=Becken),
photos[urls], aiSaleText:null} (no price per god). SiteSnapshotService.
- CRUD: GET/POST/PUT/DELETE pages; add/update/delete blocks; PUT .../blocks/order reorder.
Block type allowlisted by enum; data validated as JSON object.
This commit is contained in:
80
GerbilManagerWebAPI/Models/CmsModels.cs
Normal file
80
GerbilManagerWebAPI/Models/CmsModels.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GerbilManagerWebAPI.Models
|
||||
{
|
||||
/// <summary>Publication state of a CMS page (serialised as string name).</summary>
|
||||
public enum PageStatus
|
||||
{
|
||||
Draft = 0,
|
||||
Published = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CMS block kinds. RichText holds MARKDOWN (sanitised on render). AbgabetiereList is
|
||||
/// dynamic — resolved from live ForSale animals at snapshot time. Serialised as string.
|
||||
/// </summary>
|
||||
public enum BlockType
|
||||
{
|
||||
Heading = 0,
|
||||
RichText = 1,
|
||||
Image = 2,
|
||||
Gallery = 3,
|
||||
ContactInfo = 4,
|
||||
AbgabetiereList = 5,
|
||||
}
|
||||
|
||||
/// <summary>Singleton site settings (WEB epic CMS).</summary>
|
||||
public class Site
|
||||
{
|
||||
/// <summary>Fixed id — there is exactly one Site row.</summary>
|
||||
public static readonly Guid SingletonId = new("5172e000-0000-0000-0000-000000000001");
|
||||
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
public string DefaultLocale { get; set; } = "de";
|
||||
/// <summary>Ordered page ids for the public nav (JSON list).</summary>
|
||||
public List<Guid> NavOrder { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>A CMS page (one of the public website's pages).</summary>
|
||||
public class Page
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
/// <summary>Stable URL slug (e.g. "abgabetiere") — the human key.</summary>
|
||||
public required string Slug { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public string? SeoDescription { get; set; }
|
||||
public PageStatus Status { get; set; } = PageStatus.Draft;
|
||||
|
||||
public ICollection<Block> Blocks { get; } = new List<Block>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A content block on a page (single table, discriminated by <see cref="Type"/>).
|
||||
/// Type-specific payload lives in <see cref="Data"/> as JSON (RichText = Markdown).
|
||||
/// </summary>
|
||||
public class Block
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
public Guid PageId { get; set; }
|
||||
/// <summary>Explicit ordering within the page (AI tools reorder by this).</summary>
|
||||
public int Order { get; set; }
|
||||
public BlockType Type { get; set; }
|
||||
/// <summary>Type-specific fields as a JSON object string.</summary>
|
||||
public string Data { get; set; } = "{}";
|
||||
}
|
||||
|
||||
/// <summary>A media asset referenced by Image/Gallery blocks.</summary>
|
||||
public class Media
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
public required string FileName { get; set; }
|
||||
public required string Url { get; set; }
|
||||
public string? Alt { get; set; }
|
||||
public int? Width { get; set; }
|
||||
public int? Height { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user