diff --git a/GerbilManager.Tests/CmsTests.cs b/GerbilManager.Tests/CmsTests.cs
new file mode 100644
index 0000000..037b65a
--- /dev/null
+++ b/GerbilManager.Tests/CmsTests.cs
@@ -0,0 +1,83 @@
+using System.Net;
+using System.Net.Http.Json;
+using System.Text.Json;
+
+namespace GerbilManager.Tests;
+
+/// WEB-0: CMS snapshot shape, Abgabetiere auto-resolution from ForSale, block reorder.
+public class CmsTests : IClassFixture
+{
+ private readonly HttpClient _client;
+ public CmsTests(ApiFactory factory) => _client = factory.CreateClient();
+
+ [Fact]
+ public async Task Snapshot_has_site_nav_and_six_seeded_pages()
+ {
+ var doc = JsonDocument.Parse(await _client.GetStringAsync("/api/site-snapshot"));
+ var root = doc.RootElement;
+
+ Assert.Equal("de", root.GetProperty("site").GetProperty("defaultLocale").GetString());
+ var nav = root.GetProperty("site").GetProperty("navOrder").EnumerateArray().Select(x => x.GetString()).ToList();
+ Assert.Equal(new[] { "start", "ueber-die-zucht", "abgabetiere", "abgabebedingungen", "farben-genetik", "kontakt" }, nav);
+
+ var pages = root.GetProperty("pages").EnumerateArray().ToList();
+ Assert.Equal(6, pages.Count);
+ // abgabetiere page carries an AbgabetiereList block with a resolved (possibly empty) animals array
+ var abg = pages.Single(p => p.GetProperty("slug").GetString() == "abgabetiere");
+ var listBlock = abg.GetProperty("blocks").EnumerateArray()
+ .Single(b => b.GetProperty("type").GetString() == "AbgabetiereList");
+ Assert.Equal("auto", listBlock.GetProperty("data").GetProperty("mode").GetString());
+ Assert.Equal(JsonValueKind.Array, listBlock.GetProperty("data").GetProperty("animals").ValueKind);
+ }
+
+ [Fact]
+ public async Task Abgabetiere_resolves_ForSale_animals_with_becken_group()
+ {
+ // a Becken + a ForSale gerbil in it
+ var encResp = await _client.PostAsJsonAsync("/enclosures", new { name = "Verkaufsbecken" });
+ var encId = JsonDocument.Parse(await encResp.Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString();
+ await _client.PostAsJsonAsync("/gerbils", new { name = "Verkaufsmaus", gender = "female", status = "ForSale", enclosureId = encId });
+ // a non-ForSale gerbil must NOT appear
+ await _client.PostAsJsonAsync("/gerbils", new { name = "Bleibtmaus", gender = "male", status = "Active" });
+
+ var doc = JsonDocument.Parse(await _client.GetStringAsync("/api/site-snapshot"));
+ var animals = doc.RootElement.GetProperty("pages").EnumerateArray()
+ .Single(p => p.GetProperty("slug").GetString() == "abgabetiere")
+ .GetProperty("blocks").EnumerateArray()
+ .Single(b => b.GetProperty("type").GetString() == "AbgabetiereList")
+ .GetProperty("data").GetProperty("animals").EnumerateArray().ToList();
+
+ var sale = animals.SingleOrDefault(a => a.GetProperty("name").GetString() == "Verkaufsmaus");
+ Assert.Equal(JsonValueKind.Object, sale.ValueKind);
+ Assert.Equal("Verkaufsbecken", sale.GetProperty("group").GetString());
+ Assert.Equal(JsonValueKind.Array, sale.GetProperty("photos").ValueKind);
+ Assert.DoesNotContain(animals, a => a.GetProperty("name").GetString() == "Bleibtmaus");
+ }
+
+ [Fact]
+ public async Task Block_crud_and_reorder()
+ {
+ // create a page + two blocks
+ var pageResp = await _client.PostAsJsonAsync("/api/pages", new { slug = "test-page", title = "Testseite" });
+ Assert.Equal(HttpStatusCode.Created, pageResp.StatusCode);
+ var pageId = JsonDocument.Parse(await pageResp.Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString();
+
+ async Task AddBlock(string type, object data) =>
+ JsonDocument.Parse(await (await _client.PostAsJsonAsync($"/api/pages/{pageId}/blocks", new { type, data }))
+ .Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetGuid();
+
+ var b1 = await AddBlock("Heading", new { text = "Erstes", level = 2 });
+ var b2 = await AddBlock("RichText", new { markdown = "**hallo**" });
+
+ // reorder: b2 before b1
+ var reorder = await _client.PutAsJsonAsync($"/api/pages/{pageId}/blocks/order", new { blockIds = new[] { b2, b1 } });
+ Assert.Equal(HttpStatusCode.NoContent, reorder.StatusCode);
+
+ var blocks = JsonDocument.Parse(await _client.GetStringAsync("/api/pages/test-page"))
+ .RootElement.GetProperty("blocks").EnumerateArray().ToList();
+ Assert.Equal(b2.ToString(), blocks[0].GetProperty("id").GetString());
+ Assert.Equal(b1.ToString(), blocks[1].GetProperty("id").GetString());
+ // RichText markdown round-trips inside data
+ Assert.Equal("**hallo**", blocks[0].GetProperty("data").GetProperty("markdown").GetString());
+ }
+}
diff --git a/GerbilManagerWebAPI/ApplicationContext.cs b/GerbilManagerWebAPI/ApplicationContext.cs
index 3a58929..a18ca88 100644
--- a/GerbilManagerWebAPI/ApplicationContext.cs
+++ b/GerbilManagerWebAPI/ApplicationContext.cs
@@ -17,6 +17,10 @@ public class ApplicationContext : DbContext
public DbSet WeightRecords => Set();
public DbSet SaleContracts => Set();
public DbSet BreederSettings => Set();
+ public DbSet Sites => Set();
+ public DbSet Pages => Set();
+ public DbSet Blocks => Set();
+ public DbSet Media => Set();
// Keep Gerbil.NameSearch in sync on every save (separator-insensitive search key),
// so it can never drift from Name regardless of which code path mutates the entity.
@@ -122,9 +126,85 @@ public class ApplicationContext : DbContext
modelBuilder.Entity()
.HasData(new BreederSettings { Id = GerbilManagerWebAPI.Models.BreederSettings.SingletonId });
+ // WEB epic: CMS content model.
+ modelBuilder.Entity(e =>
+ {
+ var navConverter = new Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter, string>(
+ v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions?)null),
+ v => string.IsNullOrEmpty(v)
+ ? new List()
+ : System.Text.Json.JsonSerializer.Deserialize>(v, (System.Text.Json.JsonSerializerOptions?)null) ?? new List());
+ var navComparer = new Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer>(
+ (a, b) => (a ?? new List()).SequenceEqual(b ?? new List()),
+ v => v == null ? 0 : v.Aggregate(0, (h, x) => HashCode.Combine(h, x.GetHashCode())),
+ v => v.ToList());
+ e.Property(s => s.NavOrder).HasConversion(navConverter, navComparer);
+ });
+
+ modelBuilder.Entity(e =>
+ {
+ e.HasIndex(p => p.Slug).IsUnique();
+ e.Property(p => p.Status).HasConversion();
+ e.HasMany(p => p.Blocks).WithOne()
+ .HasForeignKey(b => b.PageId).OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity(e => e.Property(b => b.Type).HasConversion());
+
+ SeedCms(modelBuilder);
SeedColorVarieties(modelBuilder);
}
+ ///
+ /// Seed the public website's CMS: one Site + the 6 Jimdo-mirror pages, each with a
+ /// placeholder Heading; the abgabetiere page also carries an auto AbgabetiereList block
+ /// (resolved from live ForSale animals at snapshot time). Hand-editable afterwards.
+ ///
+ private static void SeedCms(ModelBuilder modelBuilder)
+ {
+ Guid Pid(int n) => new($"51720001-0000-0000-0000-{n:D12}");
+ Guid Bid(int n) => new($"51720002-0000-0000-0000-{n:D12}");
+
+ (int n, string Slug, string Title)[] pages =
+ {
+ (1, "start", "Startseite"),
+ (2, "ueber-die-zucht", "Über die Zucht"),
+ (3, "abgabetiere", "Abgabetiere"),
+ (4, "abgabebedingungen", "Abgabebedingungen"),
+ (5, "farben-genetik", "Farben & Genetik"),
+ (6, "kontakt", "Kontakt"),
+ };
+
+ var pageRows = new List();
+ var blockRows = new List();
+ foreach (var p in pages)
+ {
+ pageRows.Add(new Page { Id = Pid(p.n), Slug = p.Slug, Title = p.Title, Status = PageStatus.Published });
+ // placeholder Heading per page
+ blockRows.Add(new Block
+ {
+ Id = Bid(p.n), PageId = Pid(p.n), Order = 0, Type = BlockType.Heading,
+ Data = $"{{\"text\":\"{p.Title}\",\"level\":1}}",
+ });
+ }
+ // abgabetiere (n=3): auto AbgabetiereList as the second block
+ blockRows.Add(new Block
+ {
+ Id = Bid(10), PageId = Pid(3), Order = 1, Type = BlockType.AbgabetiereList,
+ Data = "{\"mode\":\"auto\",\"intro\":\"\"}",
+ });
+
+ modelBuilder.Entity().HasData(pageRows);
+ modelBuilder.Entity().HasData(blockRows);
+
+ modelBuilder.Entity().HasData(new Site
+ {
+ Id = Site.SingletonId,
+ DefaultLocale = "de",
+ NavOrder = pages.Select(p => Pid(p.n)).ToList(),
+ });
+ }
+
///
/// Seed the colour-variety catalog. Source of truth = Kevin's GEN-2 generated list
/// (gerbil-manager-web/src/genetics/colorVarietySeed.generated.json): 73 varieties
diff --git a/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs b/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs
new file mode 100644
index 0000000..19aaf9b
--- /dev/null
+++ b/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs
@@ -0,0 +1,124 @@
+using System.Text.Json.Nodes;
+using GerbilManagerWebAPI.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace GerbilManagerWebAPI.Cms
+{
+ ///
+ /// Builds the full CMS snapshot consumed by the static site renderer (WEB-1). The
+ /// abgabetiere page's auto AbgabetiereList block is resolved here from live ForSale
+ /// animals — nothing private is ever exposed (publish = snapshot of public content).
+ ///
+ public sealed class SiteSnapshotService
+ {
+ private readonly ApplicationContext _db;
+
+ public SiteSnapshotService(ApplicationContext db) => _db = db;
+
+ public async Task BuildAsync()
+ {
+ var site = await _db.Sites.AsNoTracking().FirstOrDefaultAsync()
+ ?? new Site { Id = Site.SingletonId };
+ var pages = await _db.Pages.AsNoTracking().Include(p => p.Blocks).ToListAsync();
+ var pageById = pages.ToDictionary(p => p.Id);
+
+ var navOrder = new JsonArray();
+ foreach (var id in site.NavOrder)
+ if (pageById.TryGetValue(id, out var p))
+ navOrder.Add(p.Slug);
+
+ JsonArray? forSale = null; // resolved lazily, reused across blocks
+
+ int NavIndex(Guid id) { var i = site.NavOrder.IndexOf(id); return i < 0 ? int.MaxValue : i; }
+
+ var pagesArr = new JsonArray();
+ foreach (var page in pages.OrderBy(p => NavIndex(p.Id)).ThenBy(p => p.Slug))
+ {
+ var blocks = new JsonArray();
+ foreach (var b in page.Blocks.OrderBy(b => b.Order))
+ {
+ var data = JsonNode.Parse(string.IsNullOrWhiteSpace(b.Data) ? "{}" : b.Data) as JsonObject
+ ?? new JsonObject();
+ if (b.Type == BlockType.AbgabetiereList && AsString(data["mode"]) == "auto")
+ {
+ forSale ??= await BuildForSaleAsync();
+ data["animals"] = forSale.DeepClone();
+ }
+ blocks.Add(new JsonObject
+ {
+ ["id"] = b.Id.ToString(),
+ ["order"] = b.Order,
+ ["type"] = b.Type.ToString(),
+ ["data"] = data,
+ });
+ }
+ pagesArr.Add(new JsonObject
+ {
+ ["slug"] = page.Slug,
+ ["title"] = page.Title,
+ ["seoDescription"] = page.SeoDescription,
+ ["status"] = page.Status.ToString(),
+ ["blocks"] = blocks,
+ });
+ }
+
+ return new JsonObject
+ {
+ ["site"] = new JsonObject
+ {
+ ["defaultLocale"] = site.DefaultLocale,
+ ["navOrder"] = navOrder,
+ },
+ ["pages"] = pagesArr,
+ };
+ }
+
+ private async Task BuildForSaleAsync()
+ {
+ var animals = await _db.Gerbils.AsNoTracking()
+ .Where(g => g.Status == GerbilStatus.ForSale)
+ .OrderBy(g => g.Name)
+ .Select(g => new
+ {
+ g.Id,
+ g.Name,
+ Farbschlag = g.ColorVariety != null ? g.ColorVariety.Name : null,
+ // group = Becken (enclosure) name — matches how the Abgabe composer
+ // groups ForSale animals by enclosure-mates (god ruling). null = single.
+ Group = g.Enclosure != null ? g.Enclosure.Name : null,
+ })
+ .ToListAsync();
+
+ var ids = animals.Select(a => a.Id).ToList();
+ var photos = await _db.GerbilPhotos.AsNoTracking()
+ .Where(p => ids.Contains(p.GerbilId))
+ .OrderBy(p => p.SortOrder)
+ .Select(p => new { p.GerbilId, p.FileName })
+ .ToListAsync();
+ var photosByGerbil = photos
+ .GroupBy(p => p.GerbilId)
+ .ToDictionary(g => g.Key, g => g.Select(x => $"/photos/files/{x.FileName}").ToList());
+
+ var arr = new JsonArray();
+ foreach (var a in animals)
+ {
+ var photoArr = new JsonArray();
+ if (photosByGerbil.TryGetValue(a.Id, out var urls))
+ foreach (var u in urls) photoArr.Add(u);
+
+ arr.Add(new JsonObject
+ {
+ ["name"] = a.Name,
+ ["farbschlag"] = a.Farbschlag,
+ ["group"] = a.Group,
+ ["photos"] = photoArr,
+ ["aiSaleText"] = null, // populated by WEB-5 once a Gemini key exists
+ });
+ }
+ return arr;
+ }
+
+ private static string? AsString(JsonNode? node) =>
+ node is JsonValue v && v.TryGetValue(out var s) ? s : null;
+ }
+}
diff --git a/GerbilManagerWebAPI/Dtos/CmsDtos.cs b/GerbilManagerWebAPI/Dtos/CmsDtos.cs
new file mode 100644
index 0000000..dd6ea58
--- /dev/null
+++ b/GerbilManagerWebAPI/Dtos/CmsDtos.cs
@@ -0,0 +1,26 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using GerbilManagerWebAPI.Models;
+
+namespace GerbilManagerWebAPI.Dtos
+{
+ // Response DTOs ----------------------------------------------------------
+
+ public record PageSummaryDto(Guid Id, string Slug, string Title, string? SeoDescription, PageStatus Status);
+
+ public record PageDto(
+ Guid Id, string Slug, string Title, string? SeoDescription, PageStatus Status,
+ IReadOnlyList Blocks);
+
+ /// Block.Data is emitted as a parsed JSON object (not a string).
+ public record BlockDto(Guid Id, int Order, BlockType Type, JsonNode? Data);
+
+ // Request DTOs -----------------------------------------------------------
+
+ public record PageInput(string Slug, string Title, string? SeoDescription, PageStatus? Status);
+
+ /// Data is the type-specific JSON object (e.g. {"markdown":"…"} for RichText).
+ public record BlockInput(BlockType Type, JsonElement Data, int? Order);
+
+ public record BlockOrderInput(List BlockIds);
+}
diff --git a/GerbilManagerWebAPI/Endpoints/CmsEndpoints.cs b/GerbilManagerWebAPI/Endpoints/CmsEndpoints.cs
new file mode 100644
index 0000000..1eaeb7c
--- /dev/null
+++ b/GerbilManagerWebAPI/Endpoints/CmsEndpoints.cs
@@ -0,0 +1,153 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using GerbilManagerWebAPI.Cms;
+using GerbilManagerWebAPI.Dtos;
+using GerbilManagerWebAPI.Models;
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.EntityFrameworkCore;
+
+namespace GerbilManagerWebAPI.Endpoints
+{
+ ///
+ /// WEB-0: CMS for the public website. GET /api/site-snapshot is the publish-time export
+ /// (with Abgabetiere resolved from live ForSale animals); the /api/pages + /api/blocks
+ /// CRUD edits content (hand-edit admin UI in WEB-0b, AI tools later). LAN-only like the
+ /// rest of the API.
+ ///
+ public static class CmsEndpoints
+ {
+ public static IEndpointRouteBuilder MapCmsEndpoints(this IEndpointRouteBuilder app)
+ {
+ var api = app.MapGroup("/api").WithTags("CMS");
+
+ // ---- snapshot (consumed by the static renderer) ----
+ api.MapGet("/site-snapshot", async (ApplicationContext db) =>
+ TypedResults.Ok(await new SiteSnapshotService(db).BuildAsync()));
+
+ // ---- pages ----
+ api.MapGet("/pages", async (ApplicationContext db) =>
+ TypedResults.Ok(await db.Pages.AsNoTracking().OrderBy(p => p.Slug)
+ .Select(p => new PageSummaryDto(p.Id, p.Slug, p.Title, p.SeoDescription, p.Status))
+ .ToListAsync()));
+
+ api.MapGet("/pages/{slug}", async Task, NotFound>> (string slug, ApplicationContext db) =>
+ {
+ var p = await db.Pages.AsNoTracking().Include(x => x.Blocks)
+ .FirstOrDefaultAsync(x => x.Slug == slug);
+ return p is null ? TypedResults.NotFound() : TypedResults.Ok(ToPageDto(p));
+ });
+
+ api.MapPost("/pages", async Task, ValidationProblem, Conflict>> (PageInput input, ApplicationContext db) =>
+ {
+ if (string.IsNullOrWhiteSpace(input.Slug) || string.IsNullOrWhiteSpace(input.Title))
+ return TypedResults.ValidationProblem(new Dictionary { ["slug"] = ["Slug and title are required."] });
+ if (await db.Pages.AnyAsync(p => p.Slug == input.Slug))
+ return TypedResults.Conflict($"A page with slug '{input.Slug}' already exists.");
+
+ var page = new Page
+ {
+ Id = Guid.NewGuid(),
+ Slug = input.Slug,
+ Title = input.Title,
+ SeoDescription = input.SeoDescription,
+ Status = input.Status ?? PageStatus.Draft,
+ };
+ db.Pages.Add(page);
+ await db.SaveChangesAsync();
+ return TypedResults.Created($"/api/pages/{page.Slug}", ToPageDto(page));
+ });
+
+ api.MapPut("/pages/{id:guid}", async Task> (Guid id, PageInput input, ApplicationContext db) =>
+ {
+ var page = await db.Pages.FirstOrDefaultAsync(p => p.Id == id);
+ if (page is null) return TypedResults.NotFound();
+ page.Slug = input.Slug;
+ page.Title = input.Title;
+ page.SeoDescription = input.SeoDescription;
+ if (input.Status is PageStatus s) page.Status = s;
+ await db.SaveChangesAsync();
+ return TypedResults.NoContent();
+ });
+
+ api.MapDelete("/pages/{id:guid}", async Task> (Guid id, ApplicationContext db) =>
+ {
+ var page = await db.Pages.FirstOrDefaultAsync(p => p.Id == id);
+ if (page is null) return TypedResults.NotFound();
+ db.Pages.Remove(page); // cascades blocks
+ await db.SaveChangesAsync();
+ return TypedResults.NoContent();
+ });
+
+ // ---- blocks ----
+ api.MapPost("/pages/{pageId:guid}/blocks", async Task, NotFound, BadRequest>> (
+ Guid pageId, BlockInput input, ApplicationContext db) =>
+ {
+ if (!await db.Pages.AnyAsync(p => p.Id == pageId)) return TypedResults.NotFound();
+ if (input.Data.ValueKind is not (JsonValueKind.Object or JsonValueKind.Undefined))
+ return TypedResults.BadRequest("Block data must be a JSON object.");
+
+ int order = input.Order ?? ((await db.Blocks.Where(b => b.PageId == pageId)
+ .Select(b => (int?)b.Order).MaxAsync() ?? -1) + 1);
+ var block = new Block
+ {
+ Id = Guid.NewGuid(),
+ PageId = pageId,
+ Order = order,
+ Type = input.Type,
+ Data = input.Data.ValueKind == JsonValueKind.Object ? input.Data.GetRawText() : "{}",
+ };
+ db.Blocks.Add(block);
+ await db.SaveChangesAsync();
+ return TypedResults.Created($"/api/blocks/{block.Id}", ToBlockDto(block));
+ });
+
+ api.MapPut("/blocks/{id:guid}", async Task>> (
+ Guid id, BlockInput input, ApplicationContext db) =>
+ {
+ var block = await db.Blocks.FirstOrDefaultAsync(b => b.Id == id);
+ if (block is null) return TypedResults.NotFound();
+ if (input.Data.ValueKind is not (JsonValueKind.Object or JsonValueKind.Undefined))
+ return TypedResults.BadRequest("Block data must be a JSON object.");
+ block.Type = input.Type;
+ if (input.Data.ValueKind == JsonValueKind.Object) block.Data = input.Data.GetRawText();
+ if (input.Order is int o) block.Order = o;
+ await db.SaveChangesAsync();
+ return TypedResults.NoContent();
+ });
+
+ api.MapDelete("/blocks/{id:guid}", async Task> (Guid id, ApplicationContext db) =>
+ {
+ var block = await db.Blocks.FirstOrDefaultAsync(b => b.Id == id);
+ if (block is null) return TypedResults.NotFound();
+ db.Blocks.Remove(block);
+ await db.SaveChangesAsync();
+ return TypedResults.NoContent();
+ });
+
+ // reorder: body = ordered block ids; Order set to the list index
+ api.MapPut("/pages/{pageId:guid}/blocks/order", async Task>> (
+ Guid pageId, BlockOrderInput input, ApplicationContext db) =>
+ {
+ var blocks = await db.Blocks.Where(b => b.PageId == pageId).ToListAsync();
+ if (blocks.Count == 0 && !await db.Pages.AnyAsync(p => p.Id == pageId))
+ return TypedResults.NotFound();
+ var byId = blocks.ToDictionary(b => b.Id);
+ if (input.BlockIds.Count != blocks.Count || input.BlockIds.Any(bid => !byId.ContainsKey(bid)))
+ return TypedResults.BadRequest("blockIds must list exactly the page's block ids.");
+ for (int i = 0; i < input.BlockIds.Count; i++)
+ byId[input.BlockIds[i]].Order = i;
+ await db.SaveChangesAsync();
+ return TypedResults.NoContent();
+ });
+
+ return app;
+ }
+
+ private static BlockDto ToBlockDto(Block b) =>
+ new(b.Id, b.Order, b.Type, JsonNode.Parse(string.IsNullOrWhiteSpace(b.Data) ? "{}" : b.Data));
+
+ private static PageDto ToPageDto(Page p) => new(
+ p.Id, p.Slug, p.Title, p.SeoDescription, p.Status,
+ p.Blocks.OrderBy(b => b.Order).Select(ToBlockDto).ToList());
+ }
+}
diff --git a/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.Designer.cs b/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.Designer.cs
new file mode 100644
index 0000000..843c62d
--- /dev/null
+++ b/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.Designer.cs
@@ -0,0 +1,1263 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace GerbilManagerWebAPI.Migrations
+{
+ [DbContext(typeof(ApplicationContext))]
+ [Migration("20260606071918_AddCmsEntities")]
+ partial class AddCmsEntities
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Block", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Data")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("integer");
+
+ b.Property("PageId")
+ .HasColumnType("uuid");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PageId");
+
+ b.ToTable("Blocks");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000001"),
+ Data = "{\"text\":\"Startseite\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000001"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000002"),
+ Data = "{\"text\":\"Über die Zucht\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000002"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000003"),
+ Data = "{\"text\":\"Abgabetiere\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000003"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000004"),
+ Data = "{\"text\":\"Abgabebedingungen\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000004"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000005"),
+ Data = "{\"text\":\"Farben & Genetik\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000005"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000006"),
+ Data = "{\"text\":\"Kontakt\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000006"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000010"),
+ Data = "{\"mode\":\"auto\",\"intro\":\"\"}",
+ Order = 1,
+ PageId = new Guid("51720001-0000-0000-0000-000000000003"),
+ Type = "AbgabetiereList"
+ });
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.BreederSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("City")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Homepage")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ZuchtName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("BreederSettings");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("11111111-1111-1111-1111-000000000001"),
+ Address = "",
+ City = "",
+ Email = "",
+ Homepage = "",
+ Name = "",
+ Phone = "",
+ ZuchtName = ""
+ });
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.ColorVariety", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CanonicalGenotype")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("ColorVarieties");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000001"),
+ CanonicalGenotype = "AA chch DD EE GG pp spsp rere",
+ Name = "Pink Eyed White (PEW)",
+ SortOrder = 0
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000002"),
+ CanonicalGenotype = "aa chch DD EE GG PP spsp rere",
+ Name = "Hermelin",
+ SortOrder = 1
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000003"),
+ CanonicalGenotype = "AA chch DD EE GG PP spsp rere",
+ Name = "Himalaya",
+ SortOrder = 2
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000004"),
+ CanonicalGenotype = "aa cchmcchm DD EE gg PP spsp rere",
+ Name = "Zobel",
+ SortOrder = 3
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000005"),
+ CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
+ Name = "Schwarzschimmel",
+ SortOrder = 4
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000006"),
+ CanonicalGenotype = "AA CC DD efef GG pp spsp rere",
+ Name = "Rotaugenschimmel",
+ SortOrder = 5
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000007"),
+ CanonicalGenotype = "AA CC DD EE GG PP spsp rere",
+ Name = "Agouti",
+ SortOrder = 6
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000008"),
+ CanonicalGenotype = "aa CC DD EE GG PP spsp rere",
+ Name = "Schwarz",
+ SortOrder = 7
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000009"),
+ CanonicalGenotype = "AA CC DD EE gg PP spsp rere",
+ Name = "Silberagouti",
+ SortOrder = 8
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000010"),
+ CanonicalGenotype = "aa CC DD EE gg PP spsp rere",
+ Name = "Anthrazit",
+ SortOrder = 9
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000011"),
+ CanonicalGenotype = "AA CC DD ee GG PP spsp rere",
+ Name = "Algierfuchs",
+ SortOrder = 10
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000012"),
+ CanonicalGenotype = "aa CC dd EE GG PP spsp rere",
+ Name = "Blau",
+ SortOrder = 11
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000013"),
+ CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
+ Name = "Gold",
+ SortOrder = 12
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000014"),
+ CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
+ Name = "Platin",
+ SortOrder = 13
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000015"),
+ CanonicalGenotype = "AA CC DD ee GG pp spsp rere",
+ Name = "Goldfuchs",
+ SortOrder = 14
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000016"),
+ CanonicalGenotype = "aa CC DD ee GG pp spsp rere",
+ Name = "Rotfuchs",
+ SortOrder = 15
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000017"),
+ CanonicalGenotype = "AA CC dd EE GG pp spsp rere",
+ Name = "dd Gold",
+ SortOrder = 16
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000018"),
+ CanonicalGenotype = "aa CC dd EE GG pp spsp rere",
+ Name = "dd Platin",
+ SortOrder = 17
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000019"),
+ CanonicalGenotype = "aa CC DD EE gg pp spsp rere",
+ Name = "Altweiss (REW)",
+ SortOrder = 18
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000020"),
+ CanonicalGenotype = "AA CC DD ee gg pp spsp rere",
+ Name = "Apricot (Blassfuchs)",
+ SortOrder = 19
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000021"),
+ CanonicalGenotype = "aa CC DD ee gg PP spsp rere",
+ Name = "Blaufuchs",
+ SortOrder = 20
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000022"),
+ CanonicalGenotype = "aa CC DD ee gg pp spsp rere",
+ Name = "C-Separator",
+ SortOrder = 21
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000023"),
+ CanonicalGenotype = "AA CC DD EE gg pp spsp rere",
+ Name = "Elfenbein",
+ SortOrder = 22
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000024"),
+ CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
+ Name = "Kohlfuchs",
+ SortOrder = 23
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000025"),
+ CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere",
+ Name = "Marder",
+ SortOrder = 24
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000026"),
+ CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere",
+ Name = "Siam (Marder-Hell)",
+ SortOrder = 25
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000027"),
+ CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
+ Name = "Polarfuchs",
+ SortOrder = 26
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000028"),
+ CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
+ Name = "Saphir",
+ SortOrder = 27
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000029"),
+ CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
+ Name = "Schimmel (Orangeschimmel)",
+ SortOrder = 28
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000030"),
+ CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
+ Name = "Topas",
+ SortOrder = 29
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000031"),
+ CanonicalGenotype = "aa CC DD EE GG pp spsp rere",
+ Name = "Platin-Hell",
+ SortOrder = 30
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000032"),
+ CanonicalGenotype = "AA CC dd EE GG PP spsp rere",
+ Name = "Agouti dd",
+ SortOrder = 31
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000033"),
+ CanonicalGenotype = "AA CC dd EE gg PP spsp rere",
+ Name = "Silberagouti dd",
+ SortOrder = 32
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000034"),
+ CanonicalGenotype = "aa CC dd ee GG PP spsp rere",
+ Name = "Kohlfuchs dd",
+ SortOrder = 33
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000035"),
+ CanonicalGenotype = "aa CC dd EE gg PP spsp rere",
+ Name = "Anthrazit dd",
+ SortOrder = 34
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000036"),
+ CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere",
+ Name = "Agouti CP-Hell",
+ SortOrder = 35
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000037"),
+ CanonicalGenotype = "aa cchmcchm DD ee gg PP spsp rere",
+ Name = "Blaufuchs CP",
+ SortOrder = 36
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000038"),
+ CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
+ Name = "Polarfuchsschimmel",
+ SortOrder = 37
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000039"),
+ CanonicalGenotype = "AA CC DD efef gg PP spsp rere",
+ Name = "Silberschimmel",
+ SortOrder = 38
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000040"),
+ CanonicalGenotype = "AA CC DD efef GG PP spsp rere",
+ Name = "Algierfuchsschimmel",
+ SortOrder = 39
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000041"),
+ CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere",
+ Name = "Polarfuchs-Hell CP",
+ SortOrder = 40
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000042"),
+ CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
+ Name = "Kohlfuchsschimmel",
+ SortOrder = 41
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000043"),
+ CanonicalGenotype = "aa CC DD efef gg PP spsp rere",
+ Name = "Blaufuchsschimmel",
+ SortOrder = 42
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000044"),
+ CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
+ Name = "Kohlfuchs, hell",
+ SortOrder = 43
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000045"),
+ CanonicalGenotype = "AA CC DD ee GG pp spsp rere",
+ Name = "Goldfuchs, hell",
+ SortOrder = 44
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000046"),
+ CanonicalGenotype = "AA CC DD efef GG pp spsp rere",
+ Name = "Goldfuchsschimmel",
+ SortOrder = 45
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000047"),
+ CanonicalGenotype = "AA CC DD EE GG pp spsp rere",
+ Name = "Gold-Hell",
+ SortOrder = 46
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000048"),
+ CanonicalGenotype = "aa cchmcchm dd EE GG PP spsp rere",
+ Name = "Siam (Marder-Hell) dd",
+ SortOrder = 47
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000049"),
+ CanonicalGenotype = "aa cchmcchm dd EE GG PP spsp rere",
+ Name = "Marder dd",
+ SortOrder = 48
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000050"),
+ CanonicalGenotype = "aa cchmcchm DD EE gg PP spsp rere",
+ Name = "Zobel-Hell",
+ SortOrder = 49
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000051"),
+ CanonicalGenotype = "AA cchmcchm dd EE gg PP spsp rere",
+ Name = "Silberagouti dd CP",
+ SortOrder = 50
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000052"),
+ CanonicalGenotype = "AA cchmcchm dd EE gg PP spsp rere",
+ Name = "Silberagouti-Hell dd CP",
+ SortOrder = 51
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000053"),
+ CanonicalGenotype = "AA cchmcchm dd EE GG PP spsp rere",
+ Name = "Agouti dd CP",
+ SortOrder = 52
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000054"),
+ CanonicalGenotype = "AA cchmcchm dd EE GG PP spsp rere",
+ Name = "Agouti-Hell dd CP",
+ SortOrder = 53
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000055"),
+ CanonicalGenotype = "aa CC DD ee gg PP spsp rere",
+ Name = "Blaufuchs, hell",
+ SortOrder = 54
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000056"),
+ CanonicalGenotype = "aa CC DD efef GG pp spsp rere",
+ Name = "Rotfuchsschimmel",
+ SortOrder = 55
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000057"),
+ CanonicalGenotype = "AA CC DD ee gg PP spsp rere",
+ Name = "Polarfuchs, hell",
+ SortOrder = 56
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000058"),
+ CanonicalGenotype = "aa CC DD efef GG PP spsp rere",
+ Name = "Kohlfuchsschimmel, hell",
+ SortOrder = 57
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000059"),
+ CanonicalGenotype = "aa CC DD ee GG pp spsp rere",
+ Name = "Rotfuchs, hell",
+ SortOrder = 58
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000060"),
+ CanonicalGenotype = "aa cchmcchm dd EE gg PP spsp rere",
+ Name = "Zobel dd",
+ SortOrder = 59
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000061"),
+ CanonicalGenotype = "aa CC DD ee GG PP spsp rere",
+ Name = "Kohlfuchs-Hell",
+ SortOrder = 60
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000062"),
+ CanonicalGenotype = "aa cchmcchm DD ee GG PP spsp rere",
+ Name = "Kohlfuchs CP",
+ SortOrder = 61
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000063"),
+ CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere",
+ Name = "Algierfuchs CP",
+ SortOrder = 62
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000064"),
+ CanonicalGenotype = "AA cchmcchm DD EE gg PP spsp rere",
+ Name = "Silberagouti CP",
+ SortOrder = 63
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000065"),
+ CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere",
+ Name = "Agouti CP",
+ SortOrder = 64
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000066"),
+ CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere",
+ Name = "Algierfuchs-Hell CP",
+ SortOrder = 65
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000067"),
+ CanonicalGenotype = "aa cchmcchm DD ee GG PP spsp rere",
+ Name = "Kohlfuchs,hell CP",
+ SortOrder = 66
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000068"),
+ CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere",
+ Name = "Polarfuchs CP",
+ SortOrder = 67
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000069"),
+ CanonicalGenotype = "AA CC DD ee GG PP spsp rere",
+ Name = "Algierfuchs, hell",
+ SortOrder = 68
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000070"),
+ CanonicalGenotype = "AA CC dd EE GG pp spsp rere",
+ Name = "Topas dd",
+ SortOrder = 69
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000071"),
+ CanonicalGenotype = "aa cchmcchm dd EE gg PP spsp rere",
+ Name = "Zobel-Hell dd",
+ SortOrder = 70
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000072"),
+ CanonicalGenotype = "aa cchmcchm DD efef GG PP spsp rere",
+ Name = "Kohlfuchsschimmel CP",
+ SortOrder = 71
+ },
+ new
+ {
+ Id = new Guid("00000000-0000-0000-0000-000000000073"),
+ CanonicalGenotype = "aa CC dd ee gg PP spsp rere",
+ Name = "Blaufuchs dd",
+ SortOrder = 72
+ });
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Contact", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Address")
+ .HasColumnType("text");
+
+ b.Property("Email")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Contacts");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Enclosure", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Enclosures");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CauseOfDeath")
+ .HasColumnType("text");
+
+ b.Property("ColorVarietyId")
+ .HasColumnType("uuid");
+
+ b.Property("DateOfBirth")
+ .HasColumnType("date");
+
+ b.Property("DateOfDeath")
+ .HasColumnType("date");
+
+ b.Property("EnclosureId")
+ .HasColumnType("uuid");
+
+ b.Property("ExternalRef")
+ .HasColumnType("text");
+
+ b.Property("Gender")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Genotype")
+ .HasColumnType("text");
+
+ b.Property("GoHomeDate")
+ .HasColumnType("date");
+
+ b.Property("ImportSource")
+ .HasColumnType("text");
+
+ b.Property("LitterId")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NameSearch")
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("OriginBreeder")
+ .HasColumnType("text");
+
+ b.Property("OriginContactId")
+ .HasColumnType("uuid");
+
+ b.Property("RawImportData")
+ .HasColumnType("text");
+
+ b.Property("ReceiverContactId")
+ .HasColumnType("uuid");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ColorVarietyId");
+
+ b.HasIndex("EnclosureId");
+
+ b.HasIndex("LitterId");
+
+ b.HasIndex("OriginContactId");
+
+ b.HasIndex("ReceiverContactId");
+
+ b.ToTable("Gerbils");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.GerbilPhoto", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Caption")
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("GerbilId")
+ .HasColumnType("uuid");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GerbilId");
+
+ b.ToTable("GerbilPhotos");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.HealthRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("GerbilId")
+ .HasColumnType("uuid");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Veterinarian")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GerbilId");
+
+ b.ToTable("HealthRecords");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Litter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("ExpectedGoHomeDate")
+ .HasColumnType("date");
+
+ b.Property("FatherId")
+ .HasColumnType("uuid");
+
+ b.Property("MotherId")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("PairingCode")
+ .HasColumnType("text");
+
+ b.Property("TotalBorn")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FatherId");
+
+ b.HasIndex("MotherId");
+
+ b.ToTable("Litters");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Media", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Alt")
+ .HasColumnType("text");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Height")
+ .HasColumnType("integer");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Width")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Media");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Page", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("SeoDescription")
+ .HasColumnType("text");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Slug")
+ .IsUnique();
+
+ b.ToTable("Pages");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000001"),
+ Slug = "start",
+ Status = "Published",
+ Title = "Startseite"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000002"),
+ Slug = "ueber-die-zucht",
+ Status = "Published",
+ Title = "Über die Zucht"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000003"),
+ Slug = "abgabetiere",
+ Status = "Published",
+ Title = "Abgabetiere"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000004"),
+ Slug = "abgabebedingungen",
+ Status = "Published",
+ Title = "Abgabebedingungen"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000005"),
+ Slug = "farben-genetik",
+ Status = "Published",
+ Title = "Farben & Genetik"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000006"),
+ Slug = "kontakt",
+ Status = "Published",
+ Title = "Kontakt"
+ });
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContract", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ContactId")
+ .HasColumnType("uuid");
+
+ b.Property("ContractDate")
+ .HasColumnType("date");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("HandoverDate")
+ .HasColumnType("date");
+
+ b.Property("Price")
+ .HasPrecision(10, 2)
+ .HasColumnType("numeric(10,2)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ContactId");
+
+ b.ToTable("SaleContracts");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContractAnimal", b =>
+ {
+ b.Property("SaleContractId")
+ .HasColumnType("uuid");
+
+ b.Property("GerbilId")
+ .HasColumnType("uuid");
+
+ b.HasKey("SaleContractId", "GerbilId");
+
+ b.HasIndex("GerbilId");
+
+ b.ToTable("SaleContractAnimal");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Site", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("DefaultLocale")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NavOrder")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Sites");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("5172e000-0000-0000-0000-000000000001"),
+ DefaultLocale = "de",
+ NavOrder = "[\"51720001-0000-0000-0000-000000000001\",\"51720001-0000-0000-0000-000000000002\",\"51720001-0000-0000-0000-000000000003\",\"51720001-0000-0000-0000-000000000004\",\"51720001-0000-0000-0000-000000000005\",\"51720001-0000-0000-0000-000000000006\"]"
+ });
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.WeightRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("GerbilId")
+ .HasColumnType("uuid");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("WeightGrams")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GerbilId");
+
+ b.ToTable("WeightRecords");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Block", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Page", null)
+ .WithMany("Blocks")
+ .HasForeignKey("PageId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.ColorVariety", "ColorVariety")
+ .WithMany()
+ .HasForeignKey("ColorVarietyId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("GerbilManagerWebAPI.Models.Enclosure", "Enclosure")
+ .WithMany("Gerbils")
+ .HasForeignKey("EnclosureId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("GerbilManagerWebAPI.Models.Litter", "Litter")
+ .WithMany()
+ .HasForeignKey("LitterId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("GerbilManagerWebAPI.Models.Contact", "OriginContact")
+ .WithMany()
+ .HasForeignKey("OriginContactId")
+ .OnDelete(DeleteBehavior.Restrict);
+
+ b.HasOne("GerbilManagerWebAPI.Models.Contact", "ReceiverContact")
+ .WithMany()
+ .HasForeignKey("ReceiverContactId")
+ .OnDelete(DeleteBehavior.Restrict);
+
+ b.Navigation("ColorVariety");
+
+ b.Navigation("Enclosure");
+
+ b.Navigation("Litter");
+
+ b.Navigation("OriginContact");
+
+ b.Navigation("ReceiverContact");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.GerbilPhoto", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", null)
+ .WithMany()
+ .HasForeignKey("GerbilId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.HealthRecord", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", null)
+ .WithMany()
+ .HasForeignKey("GerbilId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Litter", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", "Father")
+ .WithMany()
+ .HasForeignKey("FatherId")
+ .OnDelete(DeleteBehavior.Restrict);
+
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", "Mother")
+ .WithMany()
+ .HasForeignKey("MotherId")
+ .OnDelete(DeleteBehavior.Restrict);
+
+ b.Navigation("Father");
+
+ b.Navigation("Mother");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContract", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Contact", "Contact")
+ .WithMany()
+ .HasForeignKey("ContactId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Contact");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContractAnimal", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", "Gerbil")
+ .WithMany()
+ .HasForeignKey("GerbilId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GerbilManagerWebAPI.Models.SaleContract", null)
+ .WithMany("Animals")
+ .HasForeignKey("SaleContractId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Gerbil");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.WeightRecord", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Gerbil", null)
+ .WithMany()
+ .HasForeignKey("GerbilId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Enclosure", b =>
+ {
+ b.Navigation("Gerbils");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Page", b =>
+ {
+ b.Navigation("Blocks");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContract", b =>
+ {
+ b.Navigation("Animals");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.cs b/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.cs
new file mode 100644
index 0000000..7ea2047
--- /dev/null
+++ b/GerbilManagerWebAPI/Migrations/20260606071918_AddCmsEntities.cs
@@ -0,0 +1,141 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
+
+namespace GerbilManagerWebAPI.Migrations
+{
+ ///
+ public partial class AddCmsEntities : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Media",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ FileName = table.Column(type: "text", nullable: false),
+ Url = table.Column(type: "text", nullable: false),
+ Alt = table.Column(type: "text", nullable: true),
+ Width = table.Column(type: "integer", nullable: true),
+ Height = table.Column(type: "integer", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Media", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Pages",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Slug = table.Column(type: "text", nullable: false),
+ Title = table.Column(type: "text", nullable: false),
+ SeoDescription = table.Column(type: "text", nullable: true),
+ Status = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Pages", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Sites",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ DefaultLocale = table.Column(type: "text", nullable: false),
+ NavOrder = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Sites", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Blocks",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ PageId = table.Column(type: "uuid", nullable: false),
+ Order = table.Column(type: "integer", nullable: false),
+ Type = table.Column(type: "text", nullable: false),
+ Data = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Blocks", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Blocks_Pages_PageId",
+ column: x => x.PageId,
+ principalTable: "Pages",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.InsertData(
+ table: "Pages",
+ columns: new[] { "Id", "SeoDescription", "Slug", "Status", "Title" },
+ values: new object[,]
+ {
+ { new Guid("51720001-0000-0000-0000-000000000001"), null, "start", "Published", "Startseite" },
+ { new Guid("51720001-0000-0000-0000-000000000002"), null, "ueber-die-zucht", "Published", "Über die Zucht" },
+ { new Guid("51720001-0000-0000-0000-000000000003"), null, "abgabetiere", "Published", "Abgabetiere" },
+ { new Guid("51720001-0000-0000-0000-000000000004"), null, "abgabebedingungen", "Published", "Abgabebedingungen" },
+ { new Guid("51720001-0000-0000-0000-000000000005"), null, "farben-genetik", "Published", "Farben & Genetik" },
+ { new Guid("51720001-0000-0000-0000-000000000006"), null, "kontakt", "Published", "Kontakt" }
+ });
+
+ migrationBuilder.InsertData(
+ table: "Sites",
+ columns: new[] { "Id", "DefaultLocale", "NavOrder" },
+ values: new object[] { new Guid("5172e000-0000-0000-0000-000000000001"), "de", "[\"51720001-0000-0000-0000-000000000001\",\"51720001-0000-0000-0000-000000000002\",\"51720001-0000-0000-0000-000000000003\",\"51720001-0000-0000-0000-000000000004\",\"51720001-0000-0000-0000-000000000005\",\"51720001-0000-0000-0000-000000000006\"]" });
+
+ migrationBuilder.InsertData(
+ table: "Blocks",
+ columns: new[] { "Id", "Data", "Order", "PageId", "Type" },
+ values: new object[,]
+ {
+ { new Guid("51720002-0000-0000-0000-000000000001"), "{\"text\":\"Startseite\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000001"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000002"), "{\"text\":\"Über die Zucht\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000002"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000003"), "{\"text\":\"Abgabetiere\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000003"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000004"), "{\"text\":\"Abgabebedingungen\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000004"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000005"), "{\"text\":\"Farben & Genetik\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000005"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000006"), "{\"text\":\"Kontakt\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000006"), "Heading" },
+ { new Guid("51720002-0000-0000-0000-000000000010"), "{\"mode\":\"auto\",\"intro\":\"\"}", 1, new Guid("51720001-0000-0000-0000-000000000003"), "AbgabetiereList" }
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Blocks_PageId",
+ table: "Blocks",
+ column: "PageId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Pages_Slug",
+ table: "Pages",
+ column: "Slug",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Blocks");
+
+ migrationBuilder.DropTable(
+ name: "Media");
+
+ migrationBuilder.DropTable(
+ name: "Sites");
+
+ migrationBuilder.DropTable(
+ name: "Pages");
+ }
+ }
+}
diff --git a/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs b/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs
index bcba41a..cebc813 100644
--- a/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs
+++ b/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs
@@ -21,6 +21,91 @@ namespace GerbilManagerWebAPI.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Block", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Data")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("integer");
+
+ b.Property("PageId")
+ .HasColumnType("uuid");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PageId");
+
+ b.ToTable("Blocks");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000001"),
+ Data = "{\"text\":\"Startseite\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000001"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000002"),
+ Data = "{\"text\":\"Über die Zucht\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000002"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000003"),
+ Data = "{\"text\":\"Abgabetiere\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000003"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000004"),
+ Data = "{\"text\":\"Abgabebedingungen\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000004"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000005"),
+ Data = "{\"text\":\"Farben & Genetik\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000005"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000006"),
+ Data = "{\"text\":\"Kontakt\",\"level\":1}",
+ Order = 0,
+ PageId = new Guid("51720001-0000-0000-0000-000000000006"),
+ Type = "Heading"
+ },
+ new
+ {
+ Id = new Guid("51720002-0000-0000-0000-000000000010"),
+ Data = "{\"mode\":\"auto\",\"intro\":\"\"}",
+ Order = 1,
+ PageId = new Guid("51720001-0000-0000-0000-000000000003"),
+ Type = "AbgabetiereList"
+ });
+ });
+
modelBuilder.Entity("GerbilManagerWebAPI.Models.BreederSettings", b =>
{
b.Property("Id")
@@ -842,6 +927,107 @@ namespace GerbilManagerWebAPI.Migrations
b.ToTable("Litters");
});
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Media", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Alt")
+ .HasColumnType("text");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Height")
+ .HasColumnType("integer");
+
+ b.Property("Url")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Width")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Media");
+ });
+
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Page", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("SeoDescription")
+ .HasColumnType("text");
+
+ b.Property("Slug")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Slug")
+ .IsUnique();
+
+ b.ToTable("Pages");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000001"),
+ Slug = "start",
+ Status = "Published",
+ Title = "Startseite"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000002"),
+ Slug = "ueber-die-zucht",
+ Status = "Published",
+ Title = "Über die Zucht"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000003"),
+ Slug = "abgabetiere",
+ Status = "Published",
+ Title = "Abgabetiere"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000004"),
+ Slug = "abgabebedingungen",
+ Status = "Published",
+ Title = "Abgabebedingungen"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000005"),
+ Slug = "farben-genetik",
+ Status = "Published",
+ Title = "Farben & Genetik"
+ },
+ new
+ {
+ Id = new Guid("51720001-0000-0000-0000-000000000006"),
+ Slug = "kontakt",
+ Status = "Published",
+ Title = "Kontakt"
+ });
+ });
+
modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContract", b =>
{
b.Property("Id")
@@ -890,6 +1076,33 @@ namespace GerbilManagerWebAPI.Migrations
b.ToTable("SaleContractAnimal");
});
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Site", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("DefaultLocale")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NavOrder")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Sites");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("5172e000-0000-0000-0000-000000000001"),
+ DefaultLocale = "de",
+ NavOrder = "[\"51720001-0000-0000-0000-000000000001\",\"51720001-0000-0000-0000-000000000002\",\"51720001-0000-0000-0000-000000000003\",\"51720001-0000-0000-0000-000000000004\",\"51720001-0000-0000-0000-000000000005\",\"51720001-0000-0000-0000-000000000006\"]"
+ });
+ });
+
modelBuilder.Entity("GerbilManagerWebAPI.Models.WeightRecord", b =>
{
b.Property("Id")
@@ -915,6 +1128,15 @@ namespace GerbilManagerWebAPI.Migrations
b.ToTable("WeightRecords");
});
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Block", b =>
+ {
+ b.HasOne("GerbilManagerWebAPI.Models.Page", null)
+ .WithMany("Blocks")
+ .HasForeignKey("PageId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>
{
b.HasOne("GerbilManagerWebAPI.Models.ColorVariety", "ColorVariety")
@@ -1030,6 +1252,11 @@ namespace GerbilManagerWebAPI.Migrations
b.Navigation("Gerbils");
});
+ modelBuilder.Entity("GerbilManagerWebAPI.Models.Page", b =>
+ {
+ b.Navigation("Blocks");
+ });
+
modelBuilder.Entity("GerbilManagerWebAPI.Models.SaleContract", b =>
{
b.Navigation("Animals");
diff --git a/GerbilManagerWebAPI/Models/CmsModels.cs b/GerbilManagerWebAPI/Models/CmsModels.cs
new file mode 100644
index 0000000..d66dc28
--- /dev/null
+++ b/GerbilManagerWebAPI/Models/CmsModels.cs
@@ -0,0 +1,80 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace GerbilManagerWebAPI.Models
+{
+ /// Publication state of a CMS page (serialised as string name).
+ public enum PageStatus
+ {
+ Draft = 0,
+ Published = 1,
+ }
+
+ ///
+ /// CMS block kinds. RichText holds MARKDOWN (sanitised on render). AbgabetiereList is
+ /// dynamic — resolved from live ForSale animals at snapshot time. Serialised as string.
+ ///
+ public enum BlockType
+ {
+ Heading = 0,
+ RichText = 1,
+ Image = 2,
+ Gallery = 3,
+ ContactInfo = 4,
+ AbgabetiereList = 5,
+ }
+
+ /// Singleton site settings (WEB epic CMS).
+ public class Site
+ {
+ /// Fixed id — there is exactly one Site row.
+ public static readonly Guid SingletonId = new("5172e000-0000-0000-0000-000000000001");
+
+ [Key]
+ public Guid Id { get; set; }
+ public string DefaultLocale { get; set; } = "de";
+ /// Ordered page ids for the public nav (JSON list).
+ public List NavOrder { get; set; } = new();
+ }
+
+ /// A CMS page (one of the public website's pages).
+ public class Page
+ {
+ [Key]
+ public Guid Id { get; set; }
+ /// Stable URL slug (e.g. "abgabetiere") — the human key.
+ 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 Blocks { get; } = new List();
+ }
+
+ ///
+ /// A content block on a page (single table, discriminated by ).
+ /// Type-specific payload lives in as JSON (RichText = Markdown).
+ ///
+ public class Block
+ {
+ [Key]
+ public Guid Id { get; set; }
+ public Guid PageId { get; set; }
+ /// Explicit ordering within the page (AI tools reorder by this).
+ public int Order { get; set; }
+ public BlockType Type { get; set; }
+ /// Type-specific fields as a JSON object string.
+ public string Data { get; set; } = "{}";
+ }
+
+ /// A media asset referenced by Image/Gallery blocks.
+ 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; }
+ }
+}
diff --git a/GerbilManagerWebAPI/Program.cs b/GerbilManagerWebAPI/Program.cs
index 76dd181..5891c35 100644
--- a/GerbilManagerWebAPI/Program.cs
+++ b/GerbilManagerWebAPI/Program.cs
@@ -76,6 +76,7 @@ app.MapImportEndpoints();
app.MapContractEndpoints();
app.MapSettingsEndpoints();
app.MapExportEndpoints();
+app.MapCmsEndpoints();
app.Run();