From 97acaf6b10b78d1412a3d713426cca8a2d33ef32 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sat, 6 Jun 2026 15:15:54 +0200 Subject: [PATCH] =?UTF-8?q?WEB-6a:=20Oeffentliche=20Webseite=20=E2=80=94?= =?UTF-8?q?=20Gestaltung=20&=20Grundseiten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SiteRenderer: neues warmes, mobile-first CSS (Georgia-Headings, Scrollnav, site-footer-nav mit Impressum/Datenschutz-Links) - AbgabetiereList: Gruppen-Karten statt Einzelkarten (Becken-Gruppierung, Geschlecht-Label, Verfuegbar-Badge, Foto-Strip, pro-Tier-Detail-Zeilen mit Farbschlag + geboren-am + Persoenlichkeit/CharacterNote) - SiteSnapshotService: ForSale-Snapshot um gender, dateOfBirth, characterNote erweitert - Migration AddImpressumDatenschutzPages: Impressum + Datenschutz als Platzhalter-Seiten (footer-only, kein navOrder-Eintrag) - SeedCms: Impressum/Datenschutz auch in HasData (SQLite-Tests) - Tests: CmsTests angepasst (8 Seiten), 6 neue SiteRendererTests (Gruppen-Karte, Geschlecht, Badge, DateOfBirth, Solo-Tier, Impressum/Datenschutz, Footer-Links) Gate: dotnet 131/131 + vitest 78/78 + e2e 94/94 + build clean --- GerbilManager.Tests/CmsTests.cs | 8 +- GerbilManager.Tests/SiteRendererTests.cs | 89 +- GerbilManagerWebAPI/ApplicationContext.cs | 15 + GerbilManagerWebAPI/Cms/SiteRenderer.cs | 361 +++-- .../Cms/SiteSnapshotService.cs | 6 + ...2_AddImpressumDatenschutzPages.Designer.cs | 1348 +++++++++++++++++ ...0606131032_AddImpressumDatenschutzPages.cs | 71 + .../ApplicationContextModelSnapshot.cs | 46 + 8 files changed, 1848 insertions(+), 96 deletions(-) create mode 100644 GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.Designer.cs create mode 100644 GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.cs diff --git a/GerbilManager.Tests/CmsTests.cs b/GerbilManager.Tests/CmsTests.cs index 037b65a..7c15387 100644 --- a/GerbilManager.Tests/CmsTests.cs +++ b/GerbilManager.Tests/CmsTests.cs @@ -11,17 +11,21 @@ public class CmsTests : IClassFixture public CmsTests(ApiFactory factory) => _client = factory.CreateClient(); [Fact] - public async Task Snapshot_has_site_nav_and_six_seeded_pages() + public async Task Snapshot_has_site_nav_and_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(); + // main nav = 6 core pages (Impressum/Datenschutz are footer-only, not in navOrder) 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); + // 8 pages: 6 core + impressum + datenschutz (footer-only legal pages) + Assert.Equal(8, pages.Count); + Assert.Contains(pages, p => p.GetProperty("slug").GetString() == "impressum"); + Assert.Contains(pages, p => p.GetProperty("slug").GetString() == "datenschutz"); // 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() diff --git a/GerbilManager.Tests/SiteRendererTests.cs b/GerbilManager.Tests/SiteRendererTests.cs index 1a713d6..77fab11 100644 --- a/GerbilManager.Tests/SiteRendererTests.cs +++ b/GerbilManager.Tests/SiteRendererTests.cs @@ -69,10 +69,11 @@ public class SiteRendererTests ["data"] = new JsonObject { ["name"] = name, ["email"] = email, ["phone"] = phone }, }; - private static JsonObject AbgabetiereBlock(string intro, params (string name, string farbe, string? group, string? photo)[] animals) + private static JsonObject AbgabetiereBlock(string intro, + params (string name, string farbe, string? group, string? photo, string? gender, string? dob, string? note)[] animals) { var animalArr = new JsonArray(); - foreach (var (name, farbe, group, photo) in animals) + foreach (var (name, farbe, group, photo, gender, dob, note) in animals) { var photos = new JsonArray(); if (photo is not null) photos.Add(photo); @@ -81,6 +82,9 @@ public class SiteRendererTests ["name"] = name, ["farbschlag"] = farbe, ["group"] = group, + ["gender"] = gender, + ["dateOfBirth"] = dob, + ["characterNote"] = note, ["photos"] = photos, ["aiSaleText"] = (JsonNode?)null, }); @@ -92,6 +96,7 @@ public class SiteRendererTests }; } + // ── Tests ──────────────────────────────────────────────────────────────── [Fact] @@ -211,16 +216,52 @@ public class SiteRendererTests } [Fact] - public void AbgabetiereList_renders_animal_cards_with_name_and_farbschlag() + public void AbgabetiereList_renders_group_card_with_name_farbschlag_and_photo() { var snap = MakeSnapshot(("abgabetiere", "Abgabetiere", - Blocks(AbgabetiereBlock("Aktuelle Tiere:", ("Krümel", "CP-Agouti", "Großbecken", "/photos/files/abc.jpg"))))); + Blocks(AbgabetiereBlock("Aktuelle Tiere:", ("Krümel", "CP-Agouti", "Großbecken", "/photos/files/abc.jpg", null, null, null))))); var html = SiteRenderer.Render(snap)["abgabetiere/index.html"]; Assert.Contains("Krümel", html); Assert.Contains("CP-Agouti", html); Assert.Contains("Großbecken", html); Assert.Contains("/photos/files/abc.jpg", html); - Assert.Contains("cms-animal-card", html); + Assert.Contains("cms-group-card", html); + } + + [Fact] + public void AbgabetiereList_group_card_shows_gender_label_and_available_badge() + { + var snap = MakeSnapshot(("abgabetiere", "Abgabetiere", + Blocks(AbgabetiereBlock("", + ("Frieda", "Agouti", "Becken 1", null, "female", null, null), + ("Rosa", "PEW", "Becken 1", null, "female", null, null))))); + var html = SiteRenderer.Render(snap)["abgabetiere/index.html"]; + Assert.Contains("Weibchen", html); + Assert.Contains("Verfügbar", html); + Assert.Contains("cms-badge--free", html); + } + + [Fact] + public void AbgabetiereList_shows_dateOfBirth_and_characterNote() + { + var snap = MakeSnapshot(("abgabetiere", "Abgabetiere", + Blocks(AbgabetiereBlock("", + ("Pünktchen", "Siamesisch", null, null, "female", "2024-03-15", "Sehr neugierig und verspielt."))))); + var html = SiteRenderer.Render(snap)["abgabetiere/index.html"]; + Assert.Contains("März 2024", html); + Assert.Contains("Sehr neugierig und verspielt.", html); + } + + [Fact] + public void AbgabetiereList_solo_animal_without_group_gets_its_own_card() + { + var snap = MakeSnapshot(("abgabetiere", "Abgabetiere", + Blocks(AbgabetiereBlock("", + ("Einzelkind", "Zobel", null, null, "male", null, null))))); + var html = SiteRenderer.Render(snap)["abgabetiere/index.html"]; + Assert.Contains("Einzelkind", html); + Assert.Contains("Männchen", html); + Assert.Contains("cms-group-card", html); } [Fact] @@ -283,6 +324,44 @@ public class SiteRendererTests Assert.Equal(7, files.Count); } + [Fact] + public void Render_impressum_and_datenschutz_pages_are_rendered() + { + var snap = MakeSnapshot( + ("start", "Startseite", Blocks()), + ("impressum", "Impressum", Blocks(RichTextBlock("§ 5 TMG Platzhalter"))), + ("datenschutz", "Datenschutz", Blocks(RichTextBlock("Datenschutz Platzhalter")))); + var files = SiteRenderer.Render(snap); + Assert.True(files.ContainsKey("impressum/index.html")); + Assert.True(files.ContainsKey("datenschutz/index.html")); + Assert.Contains("§ 5 TMG Platzhalter", files["impressum/index.html"]); + } + + [Fact] + public void Footer_contains_impressum_and_datenschutz_links() + { + var snap = MakeSnapshot( + ("start", "Startseite", Blocks()), + ("kontakt", "Kontakt", Blocks())); + var startHtml = SiteRenderer.Render(snap)["index.html"]; + Assert.Contains("impressum/index.html", startHtml); + Assert.Contains("datenschutz/index.html", startHtml); + // links in footer, not nav + Assert.Contains("site-footer-link", startHtml); + } + + [Fact] + public void Footer_links_use_correct_relative_path_from_subpage() + { + var snap = MakeSnapshot( + ("start", "Startseite", Blocks()), + ("kontakt", "Kontakt", Blocks())); + var kontaktHtml = SiteRenderer.Render(snap)["kontakt/index.html"]; + // subpage needs ../ prefix for footer links + Assert.Contains("../impressum/index.html", kontaktHtml); + Assert.Contains("../datenschutz/index.html", kontaktHtml); + } + // ── Markdown tests ─────────────────────────────────────────────────────── [Theory] diff --git a/GerbilManagerWebAPI/ApplicationContext.cs b/GerbilManagerWebAPI/ApplicationContext.cs index 1db676b..2c770e0 100644 --- a/GerbilManagerWebAPI/ApplicationContext.cs +++ b/GerbilManagerWebAPI/ApplicationContext.cs @@ -193,6 +193,15 @@ public class ApplicationContext : DbContext (6, "kontakt", "Kontakt"), }; + // WEB-6a: footer-only legal pages (not in main nav) + (int n, string Slug, string Title, string BodyMd)[] legalPages = + { + (7, "impressum", "Impressum", + "**Angaben gemäß § 5 TMG**\\n\\nSeitenbetreiber: [Name und vollständige Adresse eintragen]\\n\\nE-Mail: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*"), + (8, "datenschutz", "Datenschutz", + "**Datenschutzerklärung**\\n\\nDiese Webseite dient der Vorstellung unserer Rennmauszucht. Es werden keine personenbezogenen Daten gespeichert oder weitergegeben.\\n\\nBei datenschutzbezogenen Fragen: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*"), + }; + var pageRows = new List(); var blockRows = new List(); foreach (var p in pages) @@ -211,6 +220,12 @@ public class ApplicationContext : DbContext Id = Bid(10), PageId = Pid(3), Order = 1, Type = BlockType.AbgabetiereList, Data = "{\"mode\":\"auto\",\"intro\":\"\"}", }); + foreach (var lp in legalPages) + { + pageRows.Add(new Page { Id = Pid(lp.n), Slug = lp.Slug, Title = lp.Title, Status = PageStatus.Published }); + blockRows.Add(new Block { Id = Bid(lp.n), PageId = Pid(lp.n), Order = 0, Type = BlockType.Heading, Data = $"{{\"text\":\"{lp.Title}\",\"level\":1}}" }); + blockRows.Add(new Block { Id = Bid(lp.n * 10), PageId = Pid(lp.n), Order = 1, Type = BlockType.RichText, Data = $"{{\"markdown\":\"{lp.BodyMd}\"}}" }); + } modelBuilder.Entity().HasData(pageRows); modelBuilder.Entity().HasData(blockRows); diff --git a/GerbilManagerWebAPI/Cms/SiteRenderer.cs b/GerbilManagerWebAPI/Cms/SiteRenderer.cs index fef9644..09a181a 100644 --- a/GerbilManagerWebAPI/Cms/SiteRenderer.cs +++ b/GerbilManagerWebAPI/Cms/SiteRenderer.cs @@ -131,7 +131,8 @@ namespace GerbilManagerWebAPI.Cms private static string RenderAbgabetiereList(JsonObject d) { var intro = Str(d["intro"]); - var animals = d["animals"] as JsonArray ?? []; + var animals = (d["animals"] as JsonArray ?? []) + .OfType().ToList(); var sb = new StringBuilder("\n
\n"); if (!string.IsNullOrWhiteSpace(intro)) @@ -140,39 +141,80 @@ namespace GerbilManagerWebAPI.Cms if (animals.Count == 0) { sb.Append("

Zurzeit stehen keine Tiere zur Abgabe bereit.

\n"); + sb.Append("
\n"); + return sb.ToString(); } - else + + // Group by Becken name; null group → solo card keyed by animal name + var groups = animals + .GroupBy(a => Str(a["group"]) ?? ("__solo__" + (Str(a["name"]) ?? ""))) + .ToList(); + + sb.Append("
\n"); + foreach (var grp in groups) { - sb.Append("
\n"); - foreach (var node in animals) + var isSolo = grp.Key.StartsWith("__solo__", StringComparison.Ordinal); + var groupName = isSolo ? null : grp.Key; + var memberList = grp.ToList(); + + // Determine group gender label + var genders = memberList.Select(a => Str(a["gender"]) ?? "unknown").Distinct().ToList(); + string genderLabel = genders.Count == 1 + ? genders[0] switch { "female" => "Weibchen", "male" => "Männchen", _ => "" } + : genders.Any(g => g != "unknown") ? "Gemischt" : ""; + + sb.Append("
\n"); + sb.Append("
\n"); + if (!string.IsNullOrEmpty(groupName)) + sb.Append($"

{H(groupName)}

\n"); + var metaParts = new List(); + if (!string.IsNullOrEmpty(genderLabel)) metaParts.Add(H(genderLabel)); + metaParts.Add("Verfügbar"); + sb.Append($" {string.Join(" · ", metaParts)}\n"); + sb.Append("
\n"); + + // Gallery: first photo of each animal in the group + var gallery = memberList + .Select(a => ( + url: (a["photos"] as JsonArray ?? []).FirstOrDefault()?.GetValue(), + name: Str(a["name"]) ?? "")) + .Where(x => !string.IsNullOrEmpty(x.url)) + .ToList(); + if (gallery.Count > 0) { - if (node is not JsonObject a) continue; - var name = H(Str(a["name"]) ?? ""); - var farbe = H(Str(a["farbschlag"]) ?? ""); - var group = Str(a["group"]); - var saleText = Str(a["aiSaleText"]); - var photos = a["photos"] as JsonArray ?? []; - - sb.Append("
\n"); - - // Profile photo (first photo) - var firstPhoto = photos.FirstOrDefault()?.GetValue(); - if (!string.IsNullOrEmpty(firstPhoto)) - sb.Append($" \"{name}\"\n"); - - sb.Append("
\n"); - sb.Append($"

{name}

\n"); - if (!string.IsNullOrEmpty(farbe)) - sb.Append($"

{farbe}

\n"); - if (!string.IsNullOrWhiteSpace(group)) - sb.Append($"

Gruppe: {H(group)}

\n"); - if (!string.IsNullOrWhiteSpace(saleText)) - sb.Append($"

{H(saleText)}

\n"); + sb.Append("
\n"); + foreach (var (url, name) in gallery) + sb.Append($" \"{H(name)}\"\n"); sb.Append("
\n"); - sb.Append("
\n"); } - sb.Append("
\n"); + + // Per-animal detail rows + sb.Append("
    \n"); + foreach (var a in memberList) + { + var name = H(Str(a["name"]) ?? ""); + var farbe = Str(a["farbschlag"]); + var dob = Str(a["dateOfBirth"]); + var note = Str(a["characterNote"]); + var saleText = Str(a["aiSaleText"]); + + sb.Append("
  • \n"); + sb.Append($" {name}\n"); + var details = new List(); + if (!string.IsNullOrEmpty(farbe)) details.Add(H(farbe)); + if (!string.IsNullOrEmpty(dob) && DateOnly.TryParse(dob, out var dob2)) + details.Add($"* {dob2.ToString("MMMM yyyy", System.Globalization.CultureInfo.GetCultureInfo("de-DE"))}"); + if (details.Count > 0) + sb.Append($" {string.Join(" · ", details)}\n"); + var personalityText = note ?? saleText; + if (!string.IsNullOrWhiteSpace(personalityText)) + sb.Append($" {H(personalityText)}\n"); + sb.Append("
  • \n"); + } + sb.Append("
\n"); + sb.Append(" \n"); } + sb.Append("
\n"); sb.Append("\n"); return sb.ToString(); @@ -212,6 +254,10 @@ namespace GerbilManagerWebAPI.Cms @@ -228,6 +274,8 @@ namespace GerbilManagerWebAPI.Cms ["abgabebedingungen"] = "Abgabebedingungen", ["farben-genetik"] = "Farben & Genetik", ["kontakt"] = "Kontakt", + ["impressum"] = "Impressum", + ["datenschutz"] = "Datenschutz", }; var sb = new StringBuilder(); @@ -336,121 +384,256 @@ namespace GerbilManagerWebAPI.Cms internal static string SiteCss() => """ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { - --color-bg: #fdf8f3; + --color-bg: #fdf7f0; --color-surface: #fff; - --color-text: #2c2c2c; - --color-muted: #6b6b6b; - --color-accent: #c0392b; - --color-border: #e0d8d0; - --font-body: system-ui, sans-serif; - --max-w: 860px; + --color-surface-warm: #fef9f4; + --color-text: #2a2118; + --color-muted: #7a6d62; + --color-accent: #b5331a; + --color-accent-hover: #8f2815; + --color-accent-soft: #fdf0ed; + --color-border: #e8ddd4; + --color-badge-free: #2d7a4a; + --color-badge-free-bg: #e6f4ec; + --font-body: Georgia, "Times New Roman", serif; + --font-ui: system-ui, -apple-system, sans-serif; + --max-w: 900px; + --radius: 10px; } body { font-family: var(--font-body); background: var(--color-bg); color: var(--color-text); - line-height: 1.65; + line-height: 1.7; + font-size: 1rem; } a { color: var(--color-accent); text-decoration: none; } - a:hover { text-decoration: underline; } + a:hover { text-decoration: underline; color: var(--color-accent-hover); } img { max-width: 100%; height: auto; display: block; } + p { margin-bottom: .75rem; } /* ── Header ── */ .site-header { background: var(--color-surface); - border-bottom: 1px solid var(--color-border); - padding: .75rem 1rem; + border-bottom: 2px solid var(--color-border); + padding: 0 1rem; display: flex; - flex-wrap: wrap; - align-items: center; - gap: .5rem 1.5rem; + align-items: stretch; + gap: 0; + min-height: 56px; } .site-logo { + font-family: var(--font-body); font-weight: 700; - font-size: 1.1rem; + font-size: 1.15rem; color: var(--color-text); white-space: nowrap; - } - .site-nav { display: flex; flex-wrap: wrap; gap: .25rem .75rem; } - .site-nav-link { - font-size: .9rem; - color: var(--color-muted); - padding: .2rem .4rem; - border-radius: 4px; - } - .site-nav-link:hover, .site-nav-link[aria-current="page"] { - color: var(--color-accent); - background: #fef0ee; + display: flex; + align-items: center; + padding-right: 1.5rem; + border-right: 1px solid var(--color-border); text-decoration: none; + flex-shrink: 0; + } + .site-logo:hover { color: var(--color-accent); text-decoration: none; } + .site-nav { + display: flex; + align-items: center; + gap: 0; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: .5rem; + } + .site-nav::-webkit-scrollbar { display: none; } + .site-nav-link { + font-family: var(--font-ui); + font-size: .875rem; + color: var(--color-muted); + padding: .5rem .65rem; + border-radius: 6px; + white-space: nowrap; + transition: color .15s, background .15s; + } + .site-nav-link:hover { color: var(--color-accent); background: var(--color-accent-soft); text-decoration: none; } + .site-nav-link[aria-current="page"] { + color: var(--color-accent); + background: var(--color-accent-soft); + font-weight: 600; } /* ── Main ── */ .site-main { max-width: var(--max-w); - margin: 2rem auto; - padding: 0 1rem 3rem; + margin: 2.5rem auto; + padding: 0 1rem 4rem; } /* ── Footer ── */ .site-footer { - border-top: 1px solid var(--color-border); - padding: 1.5rem 1rem; + background: var(--color-surface); + border-top: 2px solid var(--color-border); + padding: 1.75rem 1rem; text-align: center; + font-family: var(--font-ui); font-size: .85rem; color: var(--color-muted); } + .site-footer-nav { margin-top: .6rem; display: flex; justify-content: center; gap: 1.25rem; } + .site-footer-link { color: var(--color-muted); font-size: .8rem; } + .site-footer-link:hover { color: var(--color-accent); } /* ── CMS blocks ── */ - .cms-heading { margin: 1.5rem 0 .5rem; } - h1.cms-heading { font-size: 1.8rem; } - h2.cms-heading { font-size: 1.4rem; } + .cms-heading { margin: 2rem 0 .6rem; line-height: 1.3; } + h1.cms-heading { font-size: 2rem; margin-top: 0; } + h2.cms-heading { font-size: 1.5rem; } + h3.cms-heading { font-size: 1.2rem; } .cms-richtext { margin: 1rem 0; } .cms-richtext p { margin-bottom: .75rem; } .cms-richtext ul, .cms-richtext ol { margin: .5rem 0 .75rem 1.5rem; } - .cms-richtext li { margin-bottom: .3rem; } - .cms-image { margin: 1.5rem 0; } - .cms-image img { border-radius: 6px; } + .cms-richtext li { margin-bottom: .35rem; } + .cms-richtext hr { border: none; border-top: 1px solid var(--color-border); margin: 1.5rem 0; } + .cms-image { margin: 1.75rem 0; } + .cms-image img { border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0,0,0,.08); } .cms-gallery { display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .75rem; - margin: 1.5rem 0; + margin: 1.75rem 0; } - .cms-gallery-item img { border-radius: 4px; aspect-ratio: 1; object-fit: cover; } + .cms-gallery-item img { border-radius: 6px; aspect-ratio: 1; object-fit: cover; } .cms-contact { font-style: normal; + font-family: var(--font-ui); display: flex; flex-direction: column; - gap: .4rem; - margin: 1rem 0; + gap: .5rem; + margin: 1.25rem 0; + background: var(--color-surface-warm); + border: 1px solid var(--color-border); + border-radius: var(--radius); + padding: 1.25rem; } - .cms-contact-row { display: flex; gap: .5rem; align-items: flex-start; } + .cms-contact-row { display: flex; gap: .6rem; align-items: flex-start; } - /* ── Abgabetiere ── */ - .cms-abgabe { margin: 1rem 0; } - .cms-abgabe-intro { margin-bottom: 1.25rem; font-size: 1.05rem; } + /* ── Abgabetiere — group cards ── */ + .cms-abgabe { margin: 1.25rem 0; } + .cms-abgabe-intro { margin-bottom: 1.5rem; font-size: 1.05rem; } .cms-abgabe-empty { color: var(--color-muted); font-style: italic; } - .cms-animal-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 1.25rem; - } - .cms-animal-card { + + .cms-group-list { display: flex; flex-direction: column; gap: 2rem; } + + .cms-group-card { background: var(--color-surface); border: 1px solid var(--color-border); - border-radius: 8px; + border-radius: var(--radius); overflow: hidden; + box-shadow: 0 2px 6px rgba(0,0,0,.06); + } + .cms-group-header { + padding: 1rem 1.25rem .75rem; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: .5rem .75rem; + border-bottom: 1px solid var(--color-border); + background: var(--color-surface-warm); + } + .cms-group-name { + font-family: var(--font-body); + font-size: 1.2rem; + font-weight: 700; + color: var(--color-text); + } + .cms-group-meta { + font-family: var(--font-ui); + font-size: .875rem; + color: var(--color-muted); + display: flex; + align-items: center; + gap: .5rem; } - .cms-animal-photo { width: 100%; aspect-ratio: 4/3; object-fit: cover; } - .cms-animal-info { padding: .875rem; } - .cms-animal-name { font-size: 1.1rem; font-weight: 600; margin-bottom: .25rem; } - .cms-animal-farbe { font-size: .9rem; color: var(--color-muted); margin-bottom: .25rem; } - .cms-animal-group { font-size: .85rem; color: var(--color-muted); } - .cms-animal-text { font-size: .9rem; margin-top: .5rem; } - @media (max-width: 480px) { - h1.cms-heading { font-size: 1.4rem; } - .cms-animal-grid { grid-template-columns: 1fr; } + /* availability badge */ + .cms-badge { + display: inline-block; + font-size: .75rem; + font-weight: 600; + font-family: var(--font-ui); + padding: .2rem .55rem; + border-radius: 20px; + letter-spacing: .02em; + text-transform: uppercase; + } + .cms-badge--free { + background: var(--color-badge-free-bg); + color: var(--color-badge-free); + } + + /* group photo strip */ + .cms-group-gallery { + display: flex; + gap: .375rem; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding: .75rem 1.25rem; + background: #f5ede4; + } + .cms-group-gallery::-webkit-scrollbar { display: none; } + .cms-group-photo { + width: 140px; + height: 140px; + object-fit: cover; + border-radius: 6px; + flex-shrink: 0; + } + + /* animal detail list */ + .cms-animal-list { + list-style: none; + padding: .75rem 1.25rem 1.25rem; + display: flex; + flex-direction: column; + gap: .875rem; + } + .cms-animal-row { + display: flex; + flex-direction: column; + gap: .15rem; + padding-bottom: .875rem; + border-bottom: 1px solid var(--color-border); + } + .cms-animal-row:last-child { border-bottom: none; padding-bottom: 0; } + .cms-animal-name { + font-family: var(--font-body); + font-size: 1.05rem; + font-weight: 700; + color: var(--color-text); + } + .cms-animal-detail { + font-family: var(--font-ui); + font-size: .875rem; + color: var(--color-muted); + } + .cms-animal-note { + font-style: italic; + font-size: .925rem; + color: var(--color-text); + margin-top: .1rem; + } + + /* ── Responsive ── */ + @media (max-width: 600px) { + .site-header { padding: 0 .75rem; min-height: 50px; } + .site-logo { font-size: 1rem; padding-right: 1rem; } + h1.cms-heading { font-size: 1.5rem; } + .site-main { margin-top: 1.5rem; } + .cms-group-photo { width: 110px; height: 110px; } + } + @media (min-width: 700px) { + .cms-animal-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; } + .cms-animal-row { border-bottom: none; padding-bottom: 0; border: 1px solid var(--color-border); border-radius: 8px; padding: .75rem; } } """; diff --git a/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs b/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs index 19aaf9b..6e1bcb4 100644 --- a/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs +++ b/GerbilManagerWebAPI/Cms/SiteSnapshotService.cs @@ -82,6 +82,9 @@ namespace GerbilManagerWebAPI.Cms { g.Id, g.Name, + g.Gender, + g.DateOfBirth, + g.CharacterNote, 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. @@ -109,6 +112,9 @@ namespace GerbilManagerWebAPI.Cms arr.Add(new JsonObject { ["name"] = a.Name, + ["gender"] = a.Gender.ToString(), + ["dateOfBirth"] = a.DateOfBirth?.ToString("yyyy-MM-dd"), + ["characterNote"] = a.CharacterNote, ["farbschlag"] = a.Farbschlag, ["group"] = a.Group, ["photos"] = photoArr, diff --git a/GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.Designer.cs b/GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.Designer.cs new file mode 100644 index 0000000..3f61fc3 --- /dev/null +++ b/GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.Designer.cs @@ -0,0 +1,1348 @@ +// +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("20260606131032_AddImpressumDatenschutzPages")] + partial class AddImpressumDatenschutzPages + { + /// + 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" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000007"), + Data = "{\"text\":\"Impressum\",\"level\":1}", + Order = 0, + PageId = new Guid("51720001-0000-0000-0000-000000000007"), + Type = "Heading" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000070"), + Data = "{\"markdown\":\"**Angaben gemäß § 5 TMG**\\n\\nSeitenbetreiber: [Name und vollständige Adresse eintragen]\\n\\nE-Mail: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", + Order = 1, + PageId = new Guid("51720001-0000-0000-0000-000000000007"), + Type = "RichText" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000008"), + Data = "{\"text\":\"Datenschutz\",\"level\":1}", + Order = 0, + PageId = new Guid("51720001-0000-0000-0000-000000000008"), + Type = "Heading" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000080"), + Data = "{\"markdown\":\"**Datenschutzerklärung**\\n\\nDiese Webseite dient der Vorstellung unserer Rennmauszucht. Es werden keine personenbezogenen Daten gespeichert oder weitergegeben.\\n\\nBei datenschutzbezogenen Fragen: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", + Order = 1, + PageId = new Guid("51720001-0000-0000-0000-000000000008"), + Type = "RichText" + }); + }); + + 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 = "Rotaugenschimmel", + SortOrder = 4 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000006"), + CanonicalGenotype = "AA CC DD EE GG PP spsp rere", + Name = "Agouti", + SortOrder = 5 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000007"), + CanonicalGenotype = "aa CC DD EE GG PP spsp rere", + Name = "Schwarz", + SortOrder = 6 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000008"), + CanonicalGenotype = "AA CC DD EE gg PP spsp rere", + Name = "Silberagouti", + SortOrder = 7 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000009"), + CanonicalGenotype = "aa CC DD EE gg PP spsp rere", + Name = "Anthrazit", + SortOrder = 8 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000010"), + CanonicalGenotype = "AA CC DD ee GG PP spsp rere", + Name = "Algierfuchs", + SortOrder = 9 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000011"), + CanonicalGenotype = "aa CC dd EE GG PP spsp rere", + Name = "Blau", + SortOrder = 10 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000012"), + CanonicalGenotype = "AA CC DD EE GG pp spsp rere", + Name = "Gold", + SortOrder = 11 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000013"), + CanonicalGenotype = "aa CC DD EE GG pp spsp rere", + Name = "Platin", + SortOrder = 12 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000014"), + CanonicalGenotype = "AA CC DD ee GG pp spsp rere", + Name = "Goldfuchs", + SortOrder = 13 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000015"), + CanonicalGenotype = "aa CC DD ee GG pp spsp rere", + Name = "Rotfuchs", + SortOrder = 14 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000016"), + CanonicalGenotype = "AA CC dd EE GG pp spsp rere", + Name = "dd Gold", + SortOrder = 15 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000017"), + CanonicalGenotype = "aa CC dd EE GG pp spsp rere", + Name = "dd Platin", + SortOrder = 16 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000018"), + CanonicalGenotype = "aa CC DD EE gg pp spsp rere", + Name = "Altweiss (REW)", + SortOrder = 17 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000019"), + CanonicalGenotype = "AA CC DD ee gg pp spsp rere", + Name = "Apricot (Blassfuchs)", + SortOrder = 18 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000020"), + CanonicalGenotype = "aa CC DD ee gg PP spsp rere", + Name = "Blaufuchs", + SortOrder = 19 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000021"), + CanonicalGenotype = "aa CC DD ee gg pp spsp rere", + Name = "C-Separator", + SortOrder = 20 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000022"), + CanonicalGenotype = "AA CC DD EE gg pp spsp rere", + Name = "Elfenbein", + SortOrder = 21 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000023"), + CanonicalGenotype = "aa CC DD ee GG PP spsp rere", + Name = "Kohlfuchs", + SortOrder = 22 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000024"), + CanonicalGenotype = "AA CC DD ee gg PP spsp rere", + Name = "Polarfuchs", + SortOrder = 23 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000025"), + CanonicalGenotype = "aa CC DD EE GG pp spsp rere", + Name = "Saphir", + SortOrder = 24 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000026"), + CanonicalGenotype = "AA CC DD efef GG PP spsp rere", + Name = "Orangeschimmel", + SortOrder = 25 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000027"), + CanonicalGenotype = "AA CC DD EE GG pp spsp rere", + Name = "Topas", + SortOrder = 26 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000028"), + CanonicalGenotype = "aa CC DD EE GG pp spsp rere", + Name = "Platin-Hell", + SortOrder = 27 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000029"), + CanonicalGenotype = "AA CC dd EE GG PP spsp rere", + Name = "Agouti dd", + SortOrder = 28 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000030"), + CanonicalGenotype = "AA CC dd EE gg PP spsp rere", + Name = "Silberagouti dd", + SortOrder = 29 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000031"), + CanonicalGenotype = "aa CC dd ee GG PP spsp rere", + Name = "Kohlfuchs dd", + SortOrder = 30 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000032"), + CanonicalGenotype = "aa CC dd EE gg PP spsp rere", + Name = "Anthrazit dd", + SortOrder = 31 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000033"), + CanonicalGenotype = "AA CC DD efef gg PP spsp rere", + Name = "Silberschimmel", + SortOrder = 32 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000034"), + CanonicalGenotype = "AA CC DD efef gg PP spsp rere", + Name = "Polarfuchsschimmel", + SortOrder = 33 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000035"), + CanonicalGenotype = "AA CC DD efef GG PP spsp rere", + Name = "Algierfuchsschimmel", + SortOrder = 34 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000036"), + CanonicalGenotype = "aa CC DD efef GG PP spsp rere", + Name = "Kohlfuchsschimmel", + SortOrder = 35 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000037"), + CanonicalGenotype = "aa CC DD efef gg PP spsp rere", + Name = "Blaufuchsschimmel", + SortOrder = 36 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000038"), + CanonicalGenotype = "aa CC DD ee GG PP spsp rere", + Name = "Kohlfuchs, hell", + SortOrder = 37 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000039"), + CanonicalGenotype = "AA CC DD ee GG pp spsp rere", + Name = "Goldfuchs, hell", + SortOrder = 38 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000040"), + CanonicalGenotype = "AA CC DD efef GG pp spsp rere", + Name = "Goldfuchsschimmel", + SortOrder = 39 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000041"), + CanonicalGenotype = "AA CC DD EE GG pp spsp rere", + Name = "Gold-Hell", + SortOrder = 40 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000042"), + CanonicalGenotype = "aa CC DD ee gg PP spsp rere", + Name = "Blaufuchs, hell", + SortOrder = 41 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000043"), + CanonicalGenotype = "aa CC DD efef GG pp spsp rere", + Name = "Rotfuchsschimmel", + SortOrder = 42 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000044"), + CanonicalGenotype = "AA CC DD ee gg PP spsp rere", + Name = "Polarfuchs, hell", + SortOrder = 43 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000045"), + CanonicalGenotype = "aa CC DD efef GG PP spsp rere", + Name = "Kohlfuchsschimmel, hell", + SortOrder = 44 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000046"), + CanonicalGenotype = "aa CC DD ee GG pp spsp rere", + Name = "Rotfuchs, hell", + SortOrder = 45 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000047"), + CanonicalGenotype = "aa CC DD ee GG PP spsp rere", + Name = "Kohlfuchs-Hell", + SortOrder = 46 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000048"), + CanonicalGenotype = "AA CC DD ee GG PP spsp rere", + Name = "Algierfuchs, hell", + SortOrder = 47 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000049"), + CanonicalGenotype = "AA CC dd EE GG pp spsp rere", + Name = "Topas dd", + SortOrder = 48 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000050"), + CanonicalGenotype = "aa CC dd ee gg pp spsp rere", + Name = "Blaufuchs dd", + SortOrder = 49 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000051"), + CanonicalGenotype = "aa cchmcchm DD EE GG PP spsp rere", + Name = "Marder", + SortOrder = 50 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000052"), + CanonicalGenotype = "aa cchmch DD EE GG PP spsp rere", + Name = "Siam", + SortOrder = 51 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000053"), + CanonicalGenotype = "aa cchmch DD EE gg PP spsp rere", + Name = "Zobel-Hell", + SortOrder = 52 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000054"), + CanonicalGenotype = "AA cchmcchm DD EE GG PP spsp rere", + Name = "CP-Agouti", + SortOrder = 53 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000055"), + CanonicalGenotype = "AA cchmcchm DD EE gg PP spsp rere", + Name = "CP-Silberagouti", + SortOrder = 54 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000056"), + CanonicalGenotype = "AA cchmcchm DD ee GG PP spsp rere", + Name = "CP-Algierfuchs", + SortOrder = 55 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000057"), + CanonicalGenotype = "AA cchmcchm DD ee gg PP spsp rere", + Name = "CP-Polarfuchs", + SortOrder = 56 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000058"), + CanonicalGenotype = "AA cchmcchm dd ee GG PP spsp rere", + Name = "CP-Fuchs", + SortOrder = 57 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000059"), + CanonicalGenotype = "AA cchmch dd ee GG PP spsp rere", + Name = "CP-Fuchs-Hell", + SortOrder = 58 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000060"), + CanonicalGenotype = "AA cchmcchm dd ee gg PP spsp rere", + Name = "CP-Blaufuchs", + SortOrder = 59 + }, + new + { + Id = new Guid("00000000-0000-0000-0000-000000000061"), + CanonicalGenotype = "AA cchmcchm DD efef GG PP spsp rere", + Name = "CP-Orangeschimmel", + SortOrder = 60 + }); + }); + + 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("CharacterNote") + .HasColumnType("text"); + + b.Property("CharacterTraits") + .IsRequired() + .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("IsDeaf") + .HasColumnType("boolean"); + + b.Property("IsResident") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + 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.MailSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppPasswordProtected") + .HasColumnType("text"); + + b.Property("BackgroundPollEnabled") + .HasColumnType("boolean"); + + b.Property("Folder") + .IsRequired() + .HasColumnType("text"); + + b.Property("GmailAddress") + .HasColumnType("text"); + + b.Property("LastUid") + .HasColumnType("bigint"); + + b.Property("PollIntervalMinutes") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("MailSettings"); + + b.HasData( + new + { + Id = new Guid("ab0c0000-0000-0000-0000-000000000001"), + BackgroundPollEnabled = false, + Folder = "INBOX", + LastUid = 0L, + PollIntervalMinutes = 15 + }); + }); + + 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" + }, + new + { + Id = new Guid("51720001-0000-0000-0000-000000000007"), + Slug = "impressum", + Status = "Published", + Title = "Impressum" + }, + new + { + Id = new Guid("51720001-0000-0000-0000-000000000008"), + Slug = "datenschutz", + Status = "Published", + Title = "Datenschutz" + }); + }); + + modelBuilder.Entity("GerbilManagerWebAPI.Models.Request", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AnsweredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AssignedContactId") + .HasColumnType("uuid"); + + b.Property("BodyText") + .HasColumnType("text"); + + b.Property("DraftReply") + .HasColumnType("text"); + + b.Property("FromAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromName") + .HasColumnType("text"); + + b.Property("GmailMessageId") + .IsRequired() + .HasColumnType("text"); + + b.Property("InReplyToMessageId") + .HasColumnType("text"); + + b.Property("ReceivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferencesHeader") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .HasColumnType("text"); + + b.Property("ThreadId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AssignedContactId"); + + b.HasIndex("GmailMessageId") + .IsUnique(); + + b.ToTable("Requests"); + }); + + 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.Request", b => + { + b.HasOne("GerbilManagerWebAPI.Models.Contact", "AssignedContact") + .WithMany() + .HasForeignKey("AssignedContactId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AssignedContact"); + }); + + 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/20260606131032_AddImpressumDatenschutzPages.cs b/GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.cs new file mode 100644 index 0000000..7e48828 --- /dev/null +++ b/GerbilManagerWebAPI/Migrations/20260606131032_AddImpressumDatenschutzPages.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace GerbilManagerWebAPI.Migrations +{ + /// + public partial class AddImpressumDatenschutzPages : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "Pages", + columns: new[] { "Id", "SeoDescription", "Slug", "Status", "Title" }, + values: new object[,] + { + { new Guid("51720001-0000-0000-0000-000000000007"), null, "impressum", "Published", "Impressum" }, + { new Guid("51720001-0000-0000-0000-000000000008"), null, "datenschutz", "Published", "Datenschutz" } + }); + + migrationBuilder.InsertData( + table: "Blocks", + columns: new[] { "Id", "Data", "Order", "PageId", "Type" }, + values: new object[,] + { + { new Guid("51720002-0000-0000-0000-000000000007"), "{\"text\":\"Impressum\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000007"), "Heading" }, + { new Guid("51720002-0000-0000-0000-000000000008"), "{\"text\":\"Datenschutz\",\"level\":1}", 0, new Guid("51720001-0000-0000-0000-000000000008"), "Heading" }, + { new Guid("51720002-0000-0000-0000-000000000070"), "{\"markdown\":\"**Angaben gemäß § 5 TMG**\\n\\nSeitenbetreiber: [Name und vollständige Adresse eintragen]\\n\\nE-Mail: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", 1, new Guid("51720001-0000-0000-0000-000000000007"), "RichText" }, + { new Guid("51720002-0000-0000-0000-000000000080"), "{\"markdown\":\"**Datenschutzerklärung**\\n\\nDiese Webseite dient der Vorstellung unserer Rennmauszucht. Es werden keine personenbezogenen Daten gespeichert oder weitergegeben.\\n\\nBei datenschutzbezogenen Fragen: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", 1, new Guid("51720001-0000-0000-0000-000000000008"), "RichText" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "Blocks", + keyColumn: "Id", + keyValue: new Guid("51720002-0000-0000-0000-000000000007")); + + migrationBuilder.DeleteData( + table: "Blocks", + keyColumn: "Id", + keyValue: new Guid("51720002-0000-0000-0000-000000000008")); + + migrationBuilder.DeleteData( + table: "Blocks", + keyColumn: "Id", + keyValue: new Guid("51720002-0000-0000-0000-000000000070")); + + migrationBuilder.DeleteData( + table: "Blocks", + keyColumn: "Id", + keyValue: new Guid("51720002-0000-0000-0000-000000000080")); + + migrationBuilder.DeleteData( + table: "Pages", + keyColumn: "Id", + keyValue: new Guid("51720001-0000-0000-0000-000000000007")); + + migrationBuilder.DeleteData( + table: "Pages", + keyColumn: "Id", + keyValue: new Guid("51720001-0000-0000-0000-000000000008")); + } + } +} diff --git a/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs b/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs index 0149023..fc71641 100644 --- a/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs +++ b/GerbilManagerWebAPI/Migrations/ApplicationContextModelSnapshot.cs @@ -103,6 +103,38 @@ namespace GerbilManagerWebAPI.Migrations Order = 1, PageId = new Guid("51720001-0000-0000-0000-000000000003"), Type = "AbgabetiereList" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000007"), + Data = "{\"text\":\"Impressum\",\"level\":1}", + Order = 0, + PageId = new Guid("51720001-0000-0000-0000-000000000007"), + Type = "Heading" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000070"), + Data = "{\"markdown\":\"**Angaben gemäß § 5 TMG**\\n\\nSeitenbetreiber: [Name und vollständige Adresse eintragen]\\n\\nE-Mail: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", + Order = 1, + PageId = new Guid("51720001-0000-0000-0000-000000000007"), + Type = "RichText" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000008"), + Data = "{\"text\":\"Datenschutz\",\"level\":1}", + Order = 0, + PageId = new Guid("51720001-0000-0000-0000-000000000008"), + Type = "Heading" + }, + new + { + Id = new Guid("51720002-0000-0000-0000-000000000080"), + Data = "{\"markdown\":\"**Datenschutzerklärung**\\n\\nDiese Webseite dient der Vorstellung unserer Rennmauszucht. Es werden keine personenbezogenen Daten gespeichert oder weitergegeben.\\n\\nBei datenschutzbezogenen Fragen: [E-Mail-Adresse eintragen]\\n\\n---\\n\\n*Diese Seite wird vom Seitenbetreiber noch vervollständigt.*\"}", + Order = 1, + PageId = new Guid("51720001-0000-0000-0000-000000000008"), + Type = "RichText" }); }); @@ -989,6 +1021,20 @@ namespace GerbilManagerWebAPI.Migrations Slug = "kontakt", Status = "Published", Title = "Kontakt" + }, + new + { + Id = new Guid("51720001-0000-0000-0000-000000000007"), + Slug = "impressum", + Status = "Published", + Title = "Impressum" + }, + new + { + Id = new Guid("51720001-0000-0000-0000-000000000008"), + Slug = "datenschutz", + Status = "Published", + Title = "Datenschutz" }); });