From b667c3f9c6e5d45e109881e38cb404ce9c6768a3 Mon Sep 17 00:00:00 2001
From: Denis-RZ <77514212+Denis-RZ@users.noreply.github.com>
Date: Tue, 17 Jun 2025 19:22:10 +0800
Subject: [PATCH 01/27] Rename PageSection Area to Zone and update references
---
website/MyWebApp.Tests/PageSectionTests.cs | 4 ++--
website/MyWebApp.Tests/SanitizationTests.cs | 20 ++++++++--------
.../AdminBlockTemplateController.cs | 18 +++++++--------
.../Controllers/AdminContentController.cs | 8 +++----
.../Controllers/AdminPageSectionController.cs | 20 ++++------------
website/MyWebApp/Data/ApplicationDbContext.cs | 6 ++---
.../Migrations/20250617_RenameAreaToZone.cs | 23 +++++++++++++++++++
.../ApplicationDbContextModelSnapshot.cs | 14 +++++++++++
website/MyWebApp/Models/PageSection.cs | 2 +-
website/MyWebApp/Program.cs | 12 +++++-----
website/MyWebApp/Services/LayoutService.cs | 16 ++++++-------
.../MyWebApp/Services/TokenRenderService.cs | 4 ++--
.../TagHelpers/PageBlocksTagHelper.cs | 4 ++--
.../Views/AdminContent/_SectionEditor.cshtml | 4 ++--
.../Views/AdminPageSection/Create.cshtml | 6 ++---
.../Views/AdminPageSection/Delete.cshtml | 2 +-
.../Views/AdminPageSection/Edit.cshtml | 6 ++---
.../Views/AdminPageSection/Index.cshtml | 4 ++--
.../MyWebApp/wwwroot/js/page-section-area.js | 19 ---------------
.../MyWebApp/wwwroot/js/page-section-zone.js | 19 +++++++++++++++
20 files changed, 119 insertions(+), 92 deletions(-)
create mode 100644 website/MyWebApp/Migrations/20250617_RenameAreaToZone.cs
create mode 100644 website/MyWebApp/Migrations/ApplicationDbContextModelSnapshot.cs
delete mode 100644 website/MyWebApp/wwwroot/js/page-section-area.js
create mode 100644 website/MyWebApp/wwwroot/js/page-section-zone.js
diff --git a/website/MyWebApp.Tests/PageSectionTests.cs b/website/MyWebApp.Tests/PageSectionTests.cs
index e6d1f72..29643b7 100644
--- a/website/MyWebApp.Tests/PageSectionTests.cs
+++ b/website/MyWebApp.Tests/PageSectionTests.cs
@@ -22,7 +22,7 @@ public void CanAddAndRetrievePageSection()
context.Pages.Add(page);
context.SaveChanges();
- context.PageSections.Add(new PageSection { PageId = page.Id, Area = "header", Html = "
hi
", Type = PageSectionType.Html });
+ context.PageSections.Add(new PageSection { PageId = page.Id, Zone = "header", Html = "hi
", Type = PageSectionType.Html });
context.SaveChanges();
}
@@ -30,7 +30,7 @@ public void CanAddAndRetrievePageSection()
using (var context = new ApplicationDbContext(options))
{
var section = context.PageSections.Include(s => s.Page)
- .Single(s => s.Area == "header" && s.Page!.Slug == "test");
+ .Single(s => s.Zone == "header" && s.Page!.Slug == "test");
Assert.Equal("hi
", section.Html);
Assert.Equal("test", section.Page!.Slug);
}
diff --git a/website/MyWebApp.Tests/SanitizationTests.cs b/website/MyWebApp.Tests/SanitizationTests.cs
index 44e1adc..3246fa6 100644
--- a/website/MyWebApp.Tests/SanitizationTests.cs
+++ b/website/MyWebApp.Tests/SanitizationTests.cs
@@ -40,7 +40,7 @@ public async Task CreatePage_SanitizesHtml()
Layout = "single-column",
Sections = new List
{
- new PageSection { Area = "main", Html = "b
" }
+ new PageSection { Zone = "main", Html = "b
" }
}
};
var result = await controller.Create(model);
@@ -55,7 +55,7 @@ public async Task CreateSection_SanitizesHtml()
var (ctx, layout, sanitizer) = CreateServices();
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
- var model = new PageSection { PageId = ctx.Pages.First().Id, Area = "test", Html = "hi
", Type = PageSectionType.Html };
+ var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "main", Html = "hi
", Type = PageSectionType.Html };
var result = await controller.Create(model, null);
Assert.IsType(result);
@@ -73,7 +73,7 @@ public async Task EditPage_SanitizesHtml()
Slug = "edit",
Title = "Edit",
Layout = "single-column",
- Sections = new List { new PageSection { Area = "main", Html = "a
" } }
+ Sections = new List { new PageSection { Zone = "main", Html = "a
" } }
};
await controller.Create(createModel);
var page = ctx.Pages.Single(p => p.Slug == "edit");
@@ -83,7 +83,7 @@ public async Task EditPage_SanitizesHtml()
Slug = page.Slug,
Title = page.Title,
Layout = page.Layout,
- Sections = new List { new PageSection { Area = "main", Html = "b
" } }
+ Sections = new List { new PageSection { Zone = "main", Html = "b
" } }
};
var result = await controller.Edit(model);
var section = ctx.PageSections.Single(s => s.PageId == page.Id);
@@ -96,10 +96,10 @@ public async Task CreateSection_MarkdownConverted()
{
var (ctx, layout, sanitizer) = CreateServices();
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
- var model = new PageSection { PageId = ctx.Pages.First().Id, Area = "md", Html = "# Hello\n", Type = PageSectionType.Markdown };
+ var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "md", Html = "# Hello\n", Type = PageSectionType.Markdown };
var result = await controller.Create(model, null);
Assert.IsType(result);
- var section = ctx.PageSections.First(s => s.Area == "md");
+ var section = ctx.PageSections.First(s => s.Zone == "md");
Assert.Contains("", section.Html);
Assert.DoesNotContain("
+
diff --git a/website/MyWebApp/Views/AdminPageSection/Delete.cshtml b/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
index a93a86a..a426762 100644
--- a/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
+++ b/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
@@ -6,7 +6,7 @@
Delete Section
diff --git a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
index 641ab5c..590e245 100644
--- a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
+++ b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
@@ -13,12 +13,12 @@
- Area
-
+ Zone
+
@await Html.PartialAsync("_SectionEditor", Model)
Save
-
+
diff --git a/website/MyWebApp/Views/AdminPageSection/Index.cshtml b/website/MyWebApp/Views/AdminPageSection/Index.cshtml
index eb878dc..8115b47 100644
--- a/website/MyWebApp/Views/AdminPageSection/Index.cshtml
+++ b/website/MyWebApp/Views/AdminPageSection/Index.cshtml
@@ -12,7 +12,7 @@
- Page Area Type
+ Page Zone Type
@@ -20,7 +20,7 @@
{
@s.Page?.Slug
- @s.Area
+ @s.Zone
@s.Type
diff --git a/website/MyWebApp/wwwroot/js/page-section-area.js b/website/MyWebApp/wwwroot/js/page-section-area.js
deleted file mode 100644
index c105b77..0000000
--- a/website/MyWebApp/wwwroot/js/page-section-area.js
+++ /dev/null
@@ -1,19 +0,0 @@
-window.addEventListener('load', () => {
- const pageSelect = document.querySelector('select[name="PageId"]');
- const areaSelect = document.getElementById('area-select');
- if (!pageSelect || !areaSelect) return;
-
- function loadAreas() {
- const id = pageSelect.value;
- if (!id) { areaSelect.innerHTML = ''; return; }
- fetch(`/AdminPageSection/GetAreasForPage/${id}`)
- .then(r => r.json())
- .then(list => {
- areaSelect.innerHTML = list.map(a => `${a} `).join('');
- if (areaSelect.dataset.selected)
- areaSelect.value = areaSelect.dataset.selected;
- });
- }
- loadAreas();
- pageSelect.addEventListener('change', loadAreas);
-});
diff --git a/website/MyWebApp/wwwroot/js/page-section-zone.js b/website/MyWebApp/wwwroot/js/page-section-zone.js
new file mode 100644
index 0000000..9b2d0d3
--- /dev/null
+++ b/website/MyWebApp/wwwroot/js/page-section-zone.js
@@ -0,0 +1,19 @@
+window.addEventListener('load', () => {
+ const pageSelect = document.querySelector('select[name="PageId"]');
+ const zoneSelect = document.getElementById('zone-select');
+ if (!pageSelect || !zoneSelect) return;
+
+ function loadZones() {
+ const id = pageSelect.value;
+ if (!id) { zoneSelect.innerHTML = ''; return; }
+ fetch(`/AdminPageSection/GetZonesForPage/${id}`)
+ .then(r => r.json())
+ .then(list => {
+ zoneSelect.innerHTML = list.map(a => `${a} `).join('');
+ if (zoneSelect.dataset.selected)
+ zoneSelect.value = zoneSelect.dataset.selected;
+ });
+ }
+ loadZones();
+ pageSelect.addEventListener('change', loadZones);
+});
From 8dacfcbf2d9703ab4846ce5ee13408d2edf93eb1 Mon Sep 17 00:00:00 2001
From: Denis-RZ <77514212+Denis-RZ@users.noreply.github.com>
Date: Tue, 17 Jun 2025 19:30:20 +0800
Subject: [PATCH 02/27] Read layout zones from config
---
website/MyWebApp.Tests/LayoutServiceTests.cs | 28 +++++++++++++++++++
website/MyWebApp.Tests/NavigationTests.cs | 11 +++++++-
website/MyWebApp.Tests/SanitizationTests.cs | 11 +++++++-
.../Controllers/AdminContentController.cs | 5 ++--
.../Controllers/AdminPageSectionController.cs | 2 +-
.../MyWebApp/Controllers/PagesController.cs | 4 +--
website/MyWebApp/Services/LayoutService.cs | 20 ++++++-------
.../Views/AdminContent/PageEditor.cshtml | 11 ++++++--
website/MyWebApp/appsettings.json | 4 +++
9 files changed, 76 insertions(+), 20 deletions(-)
create mode 100644 website/MyWebApp.Tests/LayoutServiceTests.cs
diff --git a/website/MyWebApp.Tests/LayoutServiceTests.cs b/website/MyWebApp.Tests/LayoutServiceTests.cs
new file mode 100644
index 0000000..2ca3585
--- /dev/null
+++ b/website/MyWebApp.Tests/LayoutServiceTests.cs
@@ -0,0 +1,28 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Caching.Memory;
+using MyWebApp.Services;
+using System.Collections.Generic;
+using Xunit;
+
+public class LayoutServiceTests
+{
+ [Fact]
+ public void CanReadZonesFromConfig()
+ {
+ var config = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ {"Layouts:single-column:0", "main"},
+ {"Layouts:two-column-sidebar:0", "main"},
+ {"Layouts:two-column-sidebar:1", "sidebar"}
+ })
+ .Build();
+ var memory = new MemoryCache(new MemoryCacheOptions());
+ var cache = new CacheService(memory);
+ var tokens = new TokenRenderService();
+ var service = new LayoutService(cache, tokens, config);
+
+ Assert.True(service.LayoutZones.ContainsKey("single-column"));
+ Assert.Contains("sidebar", service.LayoutZones["two-column-sidebar"]);
+ }
+}
diff --git a/website/MyWebApp.Tests/NavigationTests.cs b/website/MyWebApp.Tests/NavigationTests.cs
index 23677c1..dbbbeb8 100644
--- a/website/MyWebApp.Tests/NavigationTests.cs
+++ b/website/MyWebApp.Tests/NavigationTests.cs
@@ -1,6 +1,7 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Configuration;
using MyWebApp.Data;
using MyWebApp.Models;
using MyWebApp.Services;
@@ -23,7 +24,15 @@ public async Task PublishingPage_ShowsTitleOnceInHeader()
var memory = new MemoryCache(new MemoryCacheOptions());
var cache = new CacheService(memory);
var tokens = new TokenRenderService();
- var layout = new LayoutService(cache, tokens);
+ var config = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ {"Layouts:single-column:0", "main"},
+ {"Layouts:two-column-sidebar:0", "main"},
+ {"Layouts:two-column-sidebar:1", "sidebar"}
+ })
+ .Build();
+ var layout = new LayoutService(cache, tokens, config);
context.Pages.Add(new Page { Slug = "about", Title = "About", Layout = "single-column", IsPublished = true });
context.SaveChanges();
diff --git a/website/MyWebApp.Tests/SanitizationTests.cs b/website/MyWebApp.Tests/SanitizationTests.cs
index 3246fa6..1ae0bf2 100644
--- a/website/MyWebApp.Tests/SanitizationTests.cs
+++ b/website/MyWebApp.Tests/SanitizationTests.cs
@@ -1,6 +1,7 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using MyWebApp.Controllers;
@@ -23,7 +24,15 @@ private static (ApplicationDbContext ctx, LayoutService layout, HtmlSanitizerSer
var memory = new MemoryCache(new MemoryCacheOptions());
var cache = new CacheService(memory);
var tokens = new TokenRenderService();
- var layout = new LayoutService(cache, tokens);
+ var config = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ {"Layouts:single-column:0", "main"},
+ {"Layouts:two-column-sidebar:0", "main"},
+ {"Layouts:two-column-sidebar:1", "sidebar"}
+ })
+ .Build();
+ var layout = new LayoutService(cache, tokens, config);
var sanitizer = new HtmlSanitizerService();
return (ctx, layout, sanitizer);
}
diff --git a/website/MyWebApp/Controllers/AdminContentController.cs b/website/MyWebApp/Controllers/AdminContentController.cs
index bd78896..f6315cb 100644
--- a/website/MyWebApp/Controllers/AdminContentController.cs
+++ b/website/MyWebApp/Controllers/AdminContentController.cs
@@ -33,6 +33,7 @@ private async Task LoadTemplatesAsync()
.OrderBy(t => t.Name).ToListAsync();
ViewBag.Permissions = await _db.Permissions.AsNoTracking()
.OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
}
public async Task Index()
@@ -65,7 +66,7 @@ public async Task Create(Page model)
model.PublishDate = DateTime.UtcNow;
}
var sections = model.Sections?.ToList() ?? new List();
- if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
{
ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
}
@@ -131,7 +132,7 @@ public async Task Edit(Page model)
model.PublishDate = DateTime.UtcNow;
}
var sections = model.Sections?.ToList() ?? new List();
- if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
{
ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
}
diff --git a/website/MyWebApp/Controllers/AdminPageSectionController.cs b/website/MyWebApp/Controllers/AdminPageSectionController.cs
index fc748ee..c435ae2 100644
--- a/website/MyWebApp/Controllers/AdminPageSectionController.cs
+++ b/website/MyWebApp/Controllers/AdminPageSectionController.cs
@@ -158,7 +158,7 @@ public async Task DeleteConfirmed(int id)
public async Task GetZonesForPage(int id)
{
var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
- var zones = LayoutService.GetZones(layout);
+ var zones = _layout.GetZones(layout);
return Json(zones);
}
}
diff --git a/website/MyWebApp/Controllers/PagesController.cs b/website/MyWebApp/Controllers/PagesController.cs
index 8d5cae0..a952cf7 100644
--- a/website/MyWebApp/Controllers/PagesController.cs
+++ b/website/MyWebApp/Controllers/PagesController.cs
@@ -42,9 +42,9 @@ public async Task Show(string? slug)
}
var layoutName = string.IsNullOrWhiteSpace(page.Layout) ? "single-column" : page.Layout;
- if (!LayoutService.LayoutZones.TryGetValue(layoutName, out var zones))
+ if (!_layout.LayoutZones.TryGetValue(layoutName, out var zones))
{
- zones = LayoutService.LayoutZones["single-column"];
+ zones = _layout.LayoutZones["single-column"];
}
var zoneHtml = new Dictionary();
foreach (var z in zones)
diff --git a/website/MyWebApp/Services/LayoutService.cs b/website/MyWebApp/Services/LayoutService.cs
index a9aed2f..48a8bb1 100644
--- a/website/MyWebApp/Services/LayoutService.cs
+++ b/website/MyWebApp/Services/LayoutService.cs
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
using System.Linq;
using MyWebApp.Data;
@@ -8,29 +9,28 @@ public class LayoutService
{
private readonly CacheService _cache;
private readonly TokenRenderService _tokens;
+ private readonly Dictionary _zoneMap;
private const string HeaderKey = "layout_header";
private const string FooterKey = "layout_footer";
- public static readonly Dictionary LayoutZones = new()
- {
- ["single-column"] = new[] { "main" },
- ["two-column-sidebar"] = new[] { "main", "sidebar" }
- };
+ public IReadOnlyDictionary LayoutZones => _zoneMap;
- public static bool IsValidZone(string layout, string zone)
+ public bool IsValidZone(string layout, string zone)
{
- return LayoutZones.TryGetValue(layout, out var zones) && zones.Contains(zone);
+ return _zoneMap.TryGetValue(layout, out var zones) && zones.Contains(zone);
}
- public static string[] GetZones(string layout)
+ public string[] GetZones(string layout)
{
- return LayoutZones.TryGetValue(layout, out var zones) ? zones : Array.Empty();
+ return _zoneMap.TryGetValue(layout, out var zones) ? zones : Array.Empty();
}
- public LayoutService(CacheService cache, TokenRenderService tokens)
+ public LayoutService(CacheService cache, TokenRenderService tokens, IConfiguration configuration)
{
_cache = cache;
_tokens = tokens;
+ _zoneMap = configuration.GetSection("Layouts").Get>()
+ ?? new Dictionary();
}
public async Task GetHeaderAsync(ApplicationDbContext db)
diff --git a/website/MyWebApp/Views/AdminContent/PageEditor.cshtml b/website/MyWebApp/Views/AdminContent/PageEditor.cshtml
index 77bd2d0..8aa38f5 100644
--- a/website/MyWebApp/Views/AdminContent/PageEditor.cshtml
+++ b/website/MyWebApp/Views/AdminContent/PageEditor.cshtml
@@ -17,8 +17,13 @@
Layout
- Single Column
- Two Column with Sidebar
+@if (ViewBag.LayoutZones is IReadOnlyDictionary layouts)
+{
+ foreach (var entry in layouts)
+ {
+ @entry.Key
+ }
+}
@@ -62,7 +67,7 @@
@section Scripts {
+
+
+}
From 1143b2dafa80be233c849f2f01634ac854f62cf0 Mon Sep 17 00:00:00 2001
From: Denis-RZ
Date: Tue, 17 Jun 2025 23:33:46 +0800
Subject: [PATCH 07/27] Merge remote-tracking branch
'origin/ndmn4k-codex/rename-area-property-to-zone'
# Conflicts:
# website/MyWebApp/Controllers/AdminContentController.cs
# website/MyWebApp/Controllers/AdminPageSectionController.cs
# website/MyWebApp/Services/LayoutService.cs
# website/MyWebApp/Views/AdminContent/_SectionEditor.cshtml
# website/MyWebApp/Views/AdminPageSection/Index.cshtml
---
rename-zone.patch | 777 ++++++++++++++++++
.../AdminContentController_BACKUP_200.cs | 237 ++++++
.../AdminContentController_BACKUP_263.cs | 237 ++++++
.../AdminContentController_BACKUP_389.cs | 237 ++++++
.../AdminContentController_BASE_200.cs | 228 +++++
.../AdminContentController_BASE_263.cs | 228 +++++
.../AdminContentController_BASE_389.cs | 228 +++++
.../AdminContentController_LOCAL_200.cs | 229 ++++++
.../AdminContentController_LOCAL_263.cs | 229 ++++++
.../AdminContentController_LOCAL_389.cs | 229 ++++++
.../AdminContentController_REMOTE_200.cs | 228 +++++
.../AdminContentController_REMOTE_263.cs | 228 +++++
.../AdminContentController_REMOTE_389.cs | 228 +++++
.../AdminPageSectionController_BACKUP_326.cs | 195 +++++
.../AdminPageSectionController_BASE_326.cs | 164 ++++
.../AdminPageSectionController_LOCAL_326.cs | 180 ++++
.../AdminPageSectionController_REMOTE_326.cs | 174 ++++
17 files changed, 4256 insertions(+)
create mode 100644 rename-zone.patch
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_200.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_263.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_389.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_200.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_263.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_389.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_200.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_263.cs
create mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_389.cs
create mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_BACKUP_326.cs
create mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_BASE_326.cs
create mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_LOCAL_326.cs
create mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_REMOTE_326.cs
diff --git a/rename-zone.patch b/rename-zone.patch
new file mode 100644
index 0000000..7b5c6e6
--- /dev/null
+++ b/rename-zone.patch
@@ -0,0 +1,777 @@
+diff --git a/website/MyWebApp.Tests/PageSectionTests.cs b/website/MyWebApp.Tests/PageSectionTests.cs
+index e6d1f72..29643b7 100644
+--- a/website/MyWebApp.Tests/PageSectionTests.cs
++++ b/website/MyWebApp.Tests/PageSectionTests.cs
+@@ -22,7 +22,7 @@ public class PageSectionTests
+ context.Pages.Add(page);
+ context.SaveChanges();
+
+- context.PageSections.Add(new PageSection { PageId = page.Id, Area = "header", Html = "hi
", Type = PageSectionType.Html });
++ context.PageSections.Add(new PageSection { PageId = page.Id, Zone = "header", Html = "hi
", Type = PageSectionType.Html });
+
+ context.SaveChanges();
+ }
+@@ -30,7 +30,7 @@ public class PageSectionTests
+ using (var context = new ApplicationDbContext(options))
+ {
+ var section = context.PageSections.Include(s => s.Page)
+- .Single(s => s.Area == "header" && s.Page!.Slug == "test");
++ .Single(s => s.Zone == "header" && s.Page!.Slug == "test");
+ Assert.Equal("hi
", section.Html);
+ Assert.Equal("test", section.Page!.Slug);
+ }
+diff --git a/website/MyWebApp.Tests/SanitizationTests.cs b/website/MyWebApp.Tests/SanitizationTests.cs
+index 44e1adc..3246fa6 100644
+--- a/website/MyWebApp.Tests/SanitizationTests.cs
++++ b/website/MyWebApp.Tests/SanitizationTests.cs
+@@ -40,7 +40,7 @@ public class SanitizationTests
+ Layout = "single-column",
+ Sections = new List
+ {
+- new PageSection { Area = "main", Html = "b
" }
++ new PageSection { Zone = "main", Html = "b
" }
+ }
+ };
+ var result = await controller.Create(model);
+@@ -55,7 +55,7 @@ public class SanitizationTests
+ var (ctx, layout, sanitizer) = CreateServices();
+ var controller = new AdminPageSectionController(ctx, layout, sanitizer);
+
+- var model = new PageSection { PageId = ctx.Pages.First().Id, Area = "test", Html = "hi
", Type = PageSectionType.Html };
++ var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "main", Html = "hi
", Type = PageSectionType.Html };
+ var result = await controller.Create(model, null);
+
+ Assert.IsType(result);
+@@ -73,7 +73,7 @@ public class SanitizationTests
+ Slug = "edit",
+ Title = "Edit",
+ Layout = "single-column",
+- Sections = new List { new PageSection { Area = "main", Html = "a
" } }
++ Sections = new List { new PageSection { Zone = "main", Html = "a
" } }
+ };
+ await controller.Create(createModel);
+ var page = ctx.Pages.Single(p => p.Slug == "edit");
+@@ -83,7 +83,7 @@ public class SanitizationTests
+ Slug = page.Slug,
+ Title = page.Title,
+ Layout = page.Layout,
+- Sections = new List { new PageSection { Area = "main", Html = "b
" } }
++ Sections = new List { new PageSection { Zone = "main", Html = "b
" } }
+ };
+ var result = await controller.Edit(model);
+ var section = ctx.PageSections.Single(s => s.PageId == page.Id);
+@@ -96,10 +96,10 @@ public class SanitizationTests
+ {
+ var (ctx, layout, sanitizer) = CreateServices();
+ var controller = new AdminPageSectionController(ctx, layout, sanitizer);
+- var model = new PageSection { PageId = ctx.Pages.First().Id, Area = "md", Html = "# Hello\n", Type = PageSectionType.Markdown };
++ var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "md", Html = "# Hello\n", Type = PageSectionType.Markdown };
+ var result = await controller.Create(model, null);
+ Assert.IsType(result);
+- var section = ctx.PageSections.First(s => s.Area == "md");
++ var section = ctx.PageSections.First(s => s.Zone == "md");
+ Assert.Contains("", section.Html);
+ Assert.DoesNotContain("
++
+diff --git a/website/MyWebApp/Views/AdminPageSection/Delete.cshtml b/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
+index a93a86a..a426762 100644
+--- a/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
++++ b/website/MyWebApp/Views/AdminPageSection/Delete.cshtml
+@@ -6,7 +6,7 @@
+ Delete Section
+
+diff --git a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
+index 641ab5c..590e245 100644
+--- a/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
++++ b/website/MyWebApp/Views/AdminPageSection/Edit.cshtml
+@@ -13,12 +13,12 @@
+
+
+
+- Area
+-
++ Zone
++
+
+
+ @await Html.PartialAsync("_SectionEditor", Model)
+
+ Save
+
+-
++
+diff --git a/website/MyWebApp/Views/AdminPageSection/Index.cshtml b/website/MyWebApp/Views/AdminPageSection/Index.cshtml
+index eb878dc..8115b47 100644
+--- a/website/MyWebApp/Views/AdminPageSection/Index.cshtml
++++ b/website/MyWebApp/Views/AdminPageSection/Index.cshtml
+@@ -12,7 +12,7 @@
+
+
+
+- Page Area Type
++ Page Zone Type
+
+
+
+@@ -20,7 +20,7 @@
+ {
+
+ @s.Page?.Slug
+- @s.Area
++ @s.Zone
+
+ @s.Type
+
+diff --git a/website/MyWebApp/wwwroot/css/admin.css b/website/MyWebApp/wwwroot/css/admin.css
+index ac0c040..e891877 100644
+--- a/website/MyWebApp/wwwroot/css/admin.css
++++ b/website/MyWebApp/wwwroot/css/admin.css
+@@ -1278,15 +1278,15 @@ form.mb-3 {
+ background: #0ea5e9;
+ color: #fff;
+ }
+-.area-group {
++.zone-group {
+ border: 1px solid #e2e8f0;
+ padding: 0.5rem;
+ margin-bottom: 1rem;
+ }
+-.area-group h3 {
++.zone-group h3 {
+ margin: 0 0 0.5rem 0;
+ text-transform: capitalize;
+ }
+-.area-sections {
++.zone-sections {
+ min-height: 10px;
+ }
+diff --git a/website/MyWebApp/wwwroot/js/page-editor.js b/website/MyWebApp/wwwroot/js/page-editor.js
+index 9dec3f2..533e07c 100644
+--- a/website/MyWebApp/wwwroot/js/page-editor.js
++++ b/website/MyWebApp/wwwroot/js/page-editor.js
+@@ -10,32 +10,32 @@ window.addEventListener('load', () => {
+
+ function buildGroups() {
+ container.innerHTML = '';
+- (layoutZones[currentLayout] || []).forEach(a => {
++ (layoutZones[currentLayout] || []).forEach(z => {
+ const group = document.createElement('div');
+- group.className = 'area-group';
+- group.dataset.area = a;
++ group.className = 'zone-group';
++ group.dataset.zone = z;
+ const h = document.createElement('h3');
+- h.textContent = a;
++ h.textContent = z;
+ const div = document.createElement('div');
+- div.className = 'area-sections';
++ div.className = 'zone-sections';
+ group.appendChild(h);
+ group.appendChild(div);
+ container.appendChild(group);
+ });
+ }
+
+- function populateAreas(select) {
++ function populateZones(select) {
+ if (!select) return;
+ const current = select.dataset.selected || select.value;
+- select.innerHTML = (layoutZones[currentLayout] || []).map(a => `${a} `).join('');
++ select.innerHTML = (layoutZones[currentLayout] || []).map(z => `${z} `).join('');
+ if (current) select.value = current;
+ select.dataset.selected = '';
+ }
+
+ function placeSection(section) {
+- const select = section.querySelector('.area-select');
+- const area = select ? select.value : 'main';
+- const group = container.querySelector(`.area-group[data-area='${area}'] .area-sections`);
++ const select = section.querySelector('.zone-select');
++ const zone = select ? select.value : 'main';
++ const group = container.querySelector(`.zone-group[data-zone='${zone}'] .zone-sections`);
+ if (group) group.appendChild(section);
+ }
+
+@@ -43,11 +43,11 @@ window.addEventListener('load', () => {
+ const preview = document.getElementById('layout-preview');
+ if (!preview) return;
+ preview.innerHTML = '';
+- (layoutZones[currentLayout] || []).forEach(a => {
++ (layoutZones[currentLayout] || []).forEach(z => {
+ const div = document.createElement('div');
+ div.className = 'preview-zone';
+- div.dataset.area = a;
+- div.textContent = a;
++ div.dataset.zone = z;
++ div.textContent = z;
+ preview.appendChild(div);
+ });
+ }
+@@ -56,9 +56,9 @@ window.addEventListener('load', () => {
+ const zone = e.target.closest('.preview-zone');
+ if (!zone) return;
+ if (activeIndex !== null) {
+- const select = document.querySelector(`.area-select[data-index='${activeIndex}']`);
++ const select = document.querySelector(`.zone-select[data-index='${activeIndex}']`);
+ if (select) {
+- select.value = zone.dataset.area;
++ select.value = zone.dataset.zone;
+ placeSection(select.closest('.section-editor'));
+ updateIndexes();
+ }
+@@ -91,7 +91,7 @@ window.addEventListener('load', () => {
+ buildGroups();
+ existing.forEach(el => {
+ const idx = el.dataset.index;
+- populateAreas(el.querySelector('.area-select'));
++ populateZones(el.querySelector('.zone-select'));
+ placeSection(el);
+ initSectionEditor(idx);
+ });
+@@ -105,7 +105,7 @@ window.addEventListener('load', () => {
+ currentLayout = layoutSelect.value;
+ buildGroups();
+ document.querySelectorAll('.section-editor').forEach(sec => {
+- populateAreas(sec.querySelector('.area-select'));
++ populateZones(sec.querySelector('.zone-select'));
+ placeSection(sec);
+ });
+ updateIndexes();
+@@ -123,7 +123,7 @@ window.addEventListener('load', () => {
+ });
+
+ container.addEventListener('change', e => {
+- if (e.target.classList.contains('area-select')) {
++ if (e.target.classList.contains('zone-select')) {
+ const section = e.target.closest('.section-editor');
+ placeSection(section);
+ updateIndexes();
+@@ -137,7 +137,7 @@ window.addEventListener('load', () => {
+ temp.innerHTML = html;
+ const section = temp.firstElementChild;
+ section.dataset.index = index;
+- populateAreas(section.querySelector('.area-select'));
++ populateZones(section.querySelector('.zone-select'));
+ placeSection(section);
+ initSectionEditor(index);
+ updateIndexes();
+@@ -163,7 +163,7 @@ window.addEventListener('load', () => {
+ dest.value = src.value;
+ }
+ });
+- populateAreas(clone.querySelector('.area-select'));
++ populateZones(clone.querySelector('.zone-select'));
+ placeSection(clone);
+ initSectionEditor(index);
+ if (editors[original.dataset.index]) {
+diff --git a/website/MyWebApp/wwwroot/js/page-section-area.js b/website/MyWebApp/wwwroot/js/page-section-area.js
+deleted file mode 100644
+index c105b77..0000000
+--- a/website/MyWebApp/wwwroot/js/page-section-area.js
++++ /dev/null
+@@ -1,19 +0,0 @@
+-window.addEventListener('load', () => {
+- const pageSelect = document.querySelector('select[name="PageId"]');
+- const areaSelect = document.getElementById('area-select');
+- if (!pageSelect || !areaSelect) return;
+-
+- function loadAreas() {
+- const id = pageSelect.value;
+- if (!id) { areaSelect.innerHTML = ''; return; }
+- fetch(`/AdminPageSection/GetAreasForPage/${id}`)
+- .then(r => r.json())
+- .then(list => {
+- areaSelect.innerHTML = list.map(a => `${a} `).join('');
+- if (areaSelect.dataset.selected)
+- areaSelect.value = areaSelect.dataset.selected;
+- });
+- }
+- loadAreas();
+- pageSelect.addEventListener('change', loadAreas);
+-});
+diff --git a/website/MyWebApp/wwwroot/js/page-section-zone.js b/website/MyWebApp/wwwroot/js/page-section-zone.js
+new file mode 100644
+index 0000000..9b2d0d3
+--- /dev/null
++++ b/website/MyWebApp/wwwroot/js/page-section-zone.js
+@@ -0,0 +1,19 @@
++window.addEventListener('load', () => {
++ const pageSelect = document.querySelector('select[name="PageId"]');
++ const zoneSelect = document.getElementById('zone-select');
++ if (!pageSelect || !zoneSelect) return;
++
++ function loadZones() {
++ const id = pageSelect.value;
++ if (!id) { zoneSelect.innerHTML = ''; return; }
++ fetch(`/AdminPageSection/GetZonesForPage/${id}`)
++ .then(r => r.json())
++ .then(list => {
++ zoneSelect.innerHTML = list.map(a => `${a} `).join('');
++ if (zoneSelect.dataset.selected)
++ zoneSelect.value = zoneSelect.dataset.selected;
++ });
++ }
++ loadZones();
++ pageSelect.addEventListener('change', loadZones);
++});
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
new file mode 100644
index 0000000..4b53843
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
@@ -0,0 +1,237 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
new file mode 100644
index 0000000..4b53843
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
@@ -0,0 +1,237 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
new file mode 100644
index 0000000..4b53843
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
@@ -0,0 +1,237 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+<<<<<<< HEAD
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+=======
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BASE_200.cs b/website/MyWebApp/Controllers/AdminContentController_BASE_200.cs
new file mode 100644
index 0000000..bd78896
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BASE_200.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BASE_263.cs b/website/MyWebApp/Controllers/AdminContentController_BASE_263.cs
new file mode 100644
index 0000000..bd78896
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BASE_263.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BASE_389.cs b/website/MyWebApp/Controllers/AdminContentController_BASE_389.cs
new file mode 100644
index 0000000..bd78896
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_BASE_389.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_LOCAL_200.cs b/website/MyWebApp/Controllers/AdminContentController_LOCAL_200.cs
new file mode 100644
index 0000000..f6315cb
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_LOCAL_200.cs
@@ -0,0 +1,229 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_LOCAL_263.cs b/website/MyWebApp/Controllers/AdminContentController_LOCAL_263.cs
new file mode 100644
index 0000000..f6315cb
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_LOCAL_263.cs
@@ -0,0 +1,229 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_LOCAL_389.cs b/website/MyWebApp/Controllers/AdminContentController_LOCAL_389.cs
new file mode 100644
index 0000000..f6315cb
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_LOCAL_389.cs
@@ -0,0 +1,229 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ ViewBag.LayoutZones = _layout.LayoutZones;
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Zone == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_REMOTE_200.cs b/website/MyWebApp/Controllers/AdminContentController_REMOTE_200.cs
new file mode 100644
index 0000000..7e54fea
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_REMOTE_200.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_REMOTE_263.cs b/website/MyWebApp/Controllers/AdminContentController_REMOTE_263.cs
new file mode 100644
index 0000000..7e54fea
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_REMOTE_263.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminContentController_REMOTE_389.cs b/website/MyWebApp/Controllers/AdminContentController_REMOTE_389.cs
new file mode 100644
index 0000000..7e54fea
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminContentController_REMOTE_389.cs
@@ -0,0 +1,228 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System;
+using MyWebApp.Data;
+using System.Collections.Generic;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using System.Linq;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminContentController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ private async Task LoadTemplatesAsync()
+ {
+ ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
+ .OrderBy(t => t.Name).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking()
+ .OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Index()
+ {
+ var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ return View(pages);
+ }
+
+ public async Task Create()
+ {
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = new List();
+ return View("PageEditor", new Page());
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Pages.Add(model);
+ await _db.SaveChangesAsync();
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+
+ await LoadTemplatesAsync();
+ ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
+ .OrderBy(s => s.SortOrder).ToListAsync();
+ return View("PageEditor", page);
+
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Page model)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = model.Sections;
+ return View("PageEditor", model);
+ }
+ if (model.IsPublished && model.PublishDate == null)
+ {
+ model.PublishDate = DateTime.UtcNow;
+ }
+ var sections = model.Sections?.ToList() ?? new List();
+ if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!sections.Any(s => s.Area == "main"))
+ {
+ ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadTemplatesAsync();
+ ViewBag.Sections = sections;
+ model.Sections = sections;
+ return View("PageEditor", model);
+ }
+ model.Sections = new List();
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ var existing = _db.PageSections.Where(s => s.PageId == model.Id);
+ _db.PageSections.RemoveRange(existing);
+ if (sections.Count > 0)
+ {
+ var files = HttpContext.Request.Form.Files;
+ for (int i = 0; i < sections.Count; i++)
+ {
+ var s = sections[i];
+ s.Id = 0;
+ s.PageId = model.Id;
+ var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
+ await PrepareHtmlAsync(s, file);
+ _db.PageSections.Add(s);
+ }
+ await _db.SaveChangesAsync();
+ }
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page == null)
+ {
+ return NotFound();
+ }
+ return View(page);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var page = await _db.Pages.FindAsync(id);
+ if (page != null)
+ {
+ _db.Pages.Remove(page);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminPageSectionController_BACKUP_326.cs b/website/MyWebApp/Controllers/AdminPageSectionController_BACKUP_326.cs
new file mode 100644
index 0000000..94b1b74
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminPageSectionController_BACKUP_326.cs
@@ -0,0 +1,195 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using MyWebApp.Data;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminPageSectionController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ public async Task Index(string? q)
+ {
+ var query = _db.PageSections.AsNoTracking().Include(s => s.Page).AsQueryable();
+ if (!string.IsNullOrWhiteSpace(q))
+ {
+ q = q.ToLowerInvariant();
+ query = query.Where(s => s.Area.ToLower().Contains(q) || s.Html.ToLower().Contains(q) || s.Page.Slug.ToLower().Contains(q));
+ }
+ var sections = await query.OrderBy(s => s.Page.Slug).ThenBy(s => s.Area).ToListAsync();
+ ViewBag.Query = q;
+ return View(sections);
+ }
+
+ private async Task LoadPagesAsync()
+ {
+ ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking().OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Create()
+ {
+ await LoadPagesAsync();
+ return View(new PageSection());
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ var pageLayout = await _db.Pages.Where(p => p.Id == model.PageId).Select(p => p.Layout).FirstOrDefaultAsync();
+ if (!LayoutService.IsValidArea(pageLayout ?? "single-column", model.Area))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.PageSections.Add(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ await LoadPagesAsync();
+ return View(section);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ var pageLayout = await _db.Pages.Where(p => p.Id == model.PageId).Select(p => p.Layout).FirstOrDefaultAsync();
+ if (!LayoutService.IsValidArea(pageLayout ?? "single-column", model.Area))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ return View(section);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section != null)
+ {
+ _db.PageSections.Remove(section);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+
+ [HttpGet]
+ public async Task GetAreasForPage(int id)
+ {
+ var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
+<<<<<<< HEAD
+ var zones = _layout.GetZones(layout);
+ return Json(zones);
+=======
+ var areas = LayoutService.GetAreas(layout);
+ return Json(areas);
+>>>>>>> parent of 88cb6ce (Revert database file changes)
+ }
+
+ [HttpPost]
+ public async Task Reorder([FromForm] IList ids)
+ {
+ using var tx = _db.Database.BeginTransaction();
+ for (int i = 0; i < ids.Count; i++)
+ {
+ var sec = await _db.PageSections.FindAsync(ids[i]);
+ if (sec == null) continue;
+ sec.SortOrder = (i + 1) * 10;
+ _db.PageSections.Update(sec);
+ }
+ await _db.SaveChangesAsync();
+ await tx.CommitAsync();
+ return Ok();
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminPageSectionController_BASE_326.cs b/website/MyWebApp/Controllers/AdminPageSectionController_BASE_326.cs
new file mode 100644
index 0000000..fc748ee
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminPageSectionController_BASE_326.cs
@@ -0,0 +1,164 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using MyWebApp.Data;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminPageSectionController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ public async Task Index(string? q)
+ {
+ var query = _db.PageSections.AsNoTracking().Include(s => s.Page).AsQueryable();
+ if (!string.IsNullOrWhiteSpace(q))
+ {
+ q = q.ToLowerInvariant();
+ query = query.Where(s => s.Zone.ToLower().Contains(q) || s.Html.ToLower().Contains(q) || s.Page.Slug.ToLower().Contains(q));
+ }
+ var sections = await query.OrderBy(s => s.Page.Slug).ThenBy(s => s.Zone).ToListAsync();
+ ViewBag.Query = q;
+ return View(sections);
+ }
+
+ private async Task LoadPagesAsync()
+ {
+ ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking().OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Create()
+ {
+ await LoadPagesAsync();
+ return View(new PageSection());
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.PageSections.Add(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ await LoadPagesAsync();
+ return View(section);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ return View(section);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section != null)
+ {
+ _db.PageSections.Remove(section);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+
+ [HttpGet]
+ public async Task GetZonesForPage(int id)
+ {
+ var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
+ var zones = LayoutService.GetZones(layout);
+ return Json(zones);
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminPageSectionController_LOCAL_326.cs b/website/MyWebApp/Controllers/AdminPageSectionController_LOCAL_326.cs
new file mode 100644
index 0000000..efbebf3
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminPageSectionController_LOCAL_326.cs
@@ -0,0 +1,180 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using MyWebApp.Data;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminPageSectionController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ public async Task Index(string? q)
+ {
+ var query = _db.PageSections.AsNoTracking().Include(s => s.Page).AsQueryable();
+ if (!string.IsNullOrWhiteSpace(q))
+ {
+ q = q.ToLowerInvariant();
+ query = query.Where(s => s.Zone.ToLower().Contains(q) || s.Html.ToLower().Contains(q) || s.Page.Slug.ToLower().Contains(q));
+ }
+ var sections = await query.OrderBy(s => s.Page.Slug).ThenBy(s => s.Zone).ToListAsync();
+ ViewBag.Query = q;
+ return View(sections);
+ }
+
+ private async Task LoadPagesAsync()
+ {
+ ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking().OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Create()
+ {
+ await LoadPagesAsync();
+ return View(new PageSection());
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.PageSections.Add(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ await LoadPagesAsync();
+ return View(section);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ return View(section);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section != null)
+ {
+ _db.PageSections.Remove(section);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+
+ [HttpGet]
+ public async Task GetZonesForPage(int id)
+ {
+ var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
+ var zones = _layout.GetZones(layout);
+ return Json(zones);
+ }
+
+ [HttpPost]
+ public async Task Reorder([FromForm] IList ids)
+ {
+ using var tx = _db.Database.BeginTransaction();
+ for (int i = 0; i < ids.Count; i++)
+ {
+ var sec = await _db.PageSections.FindAsync(ids[i]);
+ if (sec == null) continue;
+ sec.SortOrder = (i + 1) * 10;
+ _db.PageSections.Update(sec);
+ }
+ await _db.SaveChangesAsync();
+ await tx.CommitAsync();
+ return Ok();
+ }
+}
diff --git a/website/MyWebApp/Controllers/AdminPageSectionController_REMOTE_326.cs b/website/MyWebApp/Controllers/AdminPageSectionController_REMOTE_326.cs
new file mode 100644
index 0000000..d440e5f
--- /dev/null
+++ b/website/MyWebApp/Controllers/AdminPageSectionController_REMOTE_326.cs
@@ -0,0 +1,174 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Http;
+using Markdig;
+using System.IO;
+using MyWebApp.Data;
+using MyWebApp.Filters;
+using MyWebApp.Models;
+using MyWebApp.Services;
+
+namespace MyWebApp.Controllers;
+
+[RoleAuthorize("Admin")]
+public class AdminPageSectionController : Controller
+{
+ private readonly ApplicationDbContext _db;
+ private readonly LayoutService _layout;
+ private readonly HtmlSanitizerService _sanitizer;
+
+ public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
+ {
+ _db = db;
+ _layout = layout;
+ _sanitizer = sanitizer;
+ }
+
+ public async Task Index(string? q)
+ {
+ var query = _db.PageSections.AsNoTracking().Include(s => s.Page).AsQueryable();
+ if (!string.IsNullOrWhiteSpace(q))
+ {
+ q = q.ToLowerInvariant();
+ query = query.Where(s => s.Area.ToLower().Contains(q) || s.Html.ToLower().Contains(q) || s.Page.Slug.ToLower().Contains(q));
+ }
+ var sections = await query.OrderBy(s => s.Page.Slug).ThenBy(s => s.Area).ToListAsync();
+ ViewBag.Query = q;
+ return View(sections);
+ }
+
+ private async Task LoadPagesAsync()
+ {
+ ViewBag.Pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
+ ViewBag.Permissions = await _db.Permissions.AsNoTracking().OrderBy(p => p.Name).ToListAsync();
+ }
+
+ public async Task Create()
+ {
+ await LoadPagesAsync();
+ return View(new PageSection());
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ var pageLayout = await _db.Pages.Where(p => p.Id == model.PageId).Select(p => p.Layout).FirstOrDefaultAsync();
+ if (!LayoutService.IsValidArea(pageLayout ?? "single-column", model.Area))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.PageSections.Add(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ public async Task Edit(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ await LoadPagesAsync();
+ return View(section);
+ }
+
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(PageSection model, IFormFile? file)
+ {
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ var pageLayout = await _db.Pages.Where(p => p.Id == model.PageId).Select(p => p.Layout).FirstOrDefaultAsync();
+ if (!LayoutService.IsValidArea(pageLayout ?? "single-column", model.Area))
+ {
+ ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
+ }
+ if (!ModelState.IsValid)
+ {
+ await LoadPagesAsync();
+ return View(model);
+ }
+ await PrepareHtmlAsync(model, file);
+ _db.Update(model);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ return RedirectToAction(nameof(Index));
+ }
+
+ private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
+ {
+ switch (model.Type)
+ {
+ case PageSectionType.Html:
+ model.Html = _sanitizer.Sanitize(model.Html);
+ break;
+ case PageSectionType.Markdown:
+ var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
+ model.Html = _sanitizer.Sanitize(html);
+ break;
+ case PageSectionType.Code:
+ model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
+ break;
+ case PageSectionType.Image:
+ case PageSectionType.Video:
+ if (file != null && file.Length > 0)
+ {
+ var uploads = Path.Combine("wwwroot", "uploads");
+ Directory.CreateDirectory(uploads);
+ var name = Path.GetFileName(file.FileName);
+ var path = Path.Combine(uploads, name);
+ using var stream = new FileStream(path, FileMode.Create);
+ await file.CopyToAsync(stream);
+ if (model.Type == PageSectionType.Image)
+ model.Html = $" ";
+ else
+ model.Html = $" ";
+ }
+ break;
+ }
+ }
+
+ public async Task Delete(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section == null) return NotFound();
+ return View(section);
+ }
+
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(int id)
+ {
+ var section = await _db.PageSections.FindAsync(id);
+ if (section != null)
+ {
+ _db.PageSections.Remove(section);
+ await _db.SaveChangesAsync();
+ _layout.Reset();
+ }
+ return RedirectToAction(nameof(Index));
+ }
+
+ [HttpGet]
+ public async Task GetAreasForPage(int id)
+ {
+ var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
+ var areas = LayoutService.GetAreas(layout);
+ return Json(areas);
+ }
+}
From 7a530fab770789844c313e1bafb21db04646dfeb Mon Sep 17 00:00:00 2001
From: Denis-RZ <77514212+Denis-RZ@users.noreply.github.com>
Date: Tue, 17 Jun 2025 23:59:19 +0800
Subject: [PATCH 08/27] Remove merge artifacts and fix build
---
website/MyWebApp.Tests/LayoutServiceTests.cs | 15 +-
website/MyWebApp.Tests/NavigationTests.cs | 2 +-
website/MyWebApp.Tests/SanitizationTests.cs | 2 +-
.../AdminContentController_BACKUP_200.cs | 237 ------------------
.../AdminContentController_BACKUP_263.cs | 237 ------------------
.../AdminContentController_BACKUP_389.cs | 237 ------------------
.../AdminContentController_BASE_200.cs | 228 -----------------
.../AdminContentController_BASE_263.cs | 228 -----------------
.../AdminContentController_BASE_389.cs | 228 -----------------
.../AdminContentController_LOCAL_200.cs | 229 -----------------
.../AdminContentController_LOCAL_263.cs | 229 -----------------
.../AdminContentController_LOCAL_389.cs | 229 -----------------
.../AdminContentController_REMOTE_200.cs | 228 -----------------
.../AdminContentController_REMOTE_263.cs | 228 -----------------
.../AdminContentController_REMOTE_389.cs | 228 -----------------
.../AdminPageSectionController_BACKUP_326.cs | 195 --------------
.../AdminPageSectionController_BASE_326.cs | 164 ------------
.../AdminPageSectionController_LOCAL_326.cs | 180 -------------
.../AdminPageSectionController_REMOTE_326.cs | 174 -------------
.../MyWebApp/Controllers/PagesController.cs | 4 +-
20 files changed, 8 insertions(+), 3494 deletions(-)
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_200.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_263.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_BASE_389.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_200.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_263.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_LOCAL_389.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_200.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_263.cs
delete mode 100644 website/MyWebApp/Controllers/AdminContentController_REMOTE_389.cs
delete mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_BACKUP_326.cs
delete mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_BASE_326.cs
delete mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_LOCAL_326.cs
delete mode 100644 website/MyWebApp/Controllers/AdminPageSectionController_REMOTE_326.cs
diff --git a/website/MyWebApp.Tests/LayoutServiceTests.cs b/website/MyWebApp.Tests/LayoutServiceTests.cs
index 2ca3585..906c6cb 100644
--- a/website/MyWebApp.Tests/LayoutServiceTests.cs
+++ b/website/MyWebApp.Tests/LayoutServiceTests.cs
@@ -9,20 +9,13 @@ public class LayoutServiceTests
[Fact]
public void CanReadZonesFromConfig()
{
- var config = new ConfigurationBuilder()
- .AddInMemoryCollection(new Dictionary
- {
- {"Layouts:single-column:0", "main"},
- {"Layouts:two-column-sidebar:0", "main"},
- {"Layouts:two-column-sidebar:1", "sidebar"}
- })
- .Build();
+ var config = new ConfigurationBuilder().Build();
var memory = new MemoryCache(new MemoryCacheOptions());
var cache = new CacheService(memory);
var tokens = new TokenRenderService();
- var service = new LayoutService(cache, tokens, config);
+ var service = new LayoutService(cache, tokens);
- Assert.True(service.LayoutZones.ContainsKey("single-column"));
- Assert.Contains("sidebar", service.LayoutZones["two-column-sidebar"]);
+ Assert.True(LayoutService.LayoutZones.ContainsKey("single-column"));
+ Assert.Contains("sidebar", LayoutService.LayoutZones["two-column-sidebar"]);
}
}
diff --git a/website/MyWebApp.Tests/NavigationTests.cs b/website/MyWebApp.Tests/NavigationTests.cs
index dbbbeb8..67b3227 100644
--- a/website/MyWebApp.Tests/NavigationTests.cs
+++ b/website/MyWebApp.Tests/NavigationTests.cs
@@ -32,7 +32,7 @@ public async Task PublishingPage_ShowsTitleOnceInHeader()
{"Layouts:two-column-sidebar:1", "sidebar"}
})
.Build();
- var layout = new LayoutService(cache, tokens, config);
+ var layout = new LayoutService(cache, tokens);
context.Pages.Add(new Page { Slug = "about", Title = "About", Layout = "single-column", IsPublished = true });
context.SaveChanges();
diff --git a/website/MyWebApp.Tests/SanitizationTests.cs b/website/MyWebApp.Tests/SanitizationTests.cs
index 1ae0bf2..9cc5073 100644
--- a/website/MyWebApp.Tests/SanitizationTests.cs
+++ b/website/MyWebApp.Tests/SanitizationTests.cs
@@ -32,7 +32,7 @@ private static (ApplicationDbContext ctx, LayoutService layout, HtmlSanitizerSer
{"Layouts:two-column-sidebar:1", "sidebar"}
})
.Build();
- var layout = new LayoutService(cache, tokens, config);
+ var layout = new LayoutService(cache, tokens);
var sanitizer = new HtmlSanitizerService();
return (ctx, layout, sanitizer);
}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
deleted file mode 100644
index 4b53843..0000000
--- a/website/MyWebApp/Controllers/AdminContentController_BACKUP_200.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using System;
-using MyWebApp.Data;
-using System.Collections.Generic;
-using MyWebApp.Filters;
-using MyWebApp.Models;
-using MyWebApp.Services;
-using Microsoft.AspNetCore.Http;
-using Markdig;
-using System.IO;
-using System.Linq;
-
-namespace MyWebApp.Controllers;
-
-[RoleAuthorize("Admin")]
-public class AdminContentController : Controller
-{
- private readonly ApplicationDbContext _db;
- private readonly LayoutService _layout;
- private readonly HtmlSanitizerService _sanitizer;
-
- public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
- {
- _db = db;
- _layout = layout;
- _sanitizer = sanitizer;
- }
-
- private async Task LoadTemplatesAsync()
- {
- ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
- .OrderBy(t => t.Name).ToListAsync();
- ViewBag.Permissions = await _db.Permissions.AsNoTracking()
- .OrderBy(p => p.Name).ToListAsync();
- ViewBag.LayoutZones = _layout.LayoutZones;
- }
-
- public async Task Index()
- {
- var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
- return View(pages);
- }
-
- public async Task Create()
- {
-
- await LoadTemplatesAsync();
- ViewBag.Sections = new List();
- return View("PageEditor", new Page());
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Create(Page model)
- {
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = model.Sections;
- return View("PageEditor", model);
- }
- if (model.IsPublished && model.PublishDate == null)
- {
- model.PublishDate = DateTime.UtcNow;
- }
- var sections = model.Sections?.ToList() ?? new List();
-<<<<<<< HEAD
- if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
-=======
- if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
->>>>>>> parent of 88cb6ce (Revert database file changes)
- {
- ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
- }
- if (!sections.Any(s => s.Area == "main"))
- {
- ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
- }
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = sections;
- model.Sections = sections;
- return View("PageEditor", model);
- }
- model.Sections = new List();
- _db.Pages.Add(model);
- await _db.SaveChangesAsync();
- if (sections.Count > 0)
- {
- var files = HttpContext.Request.Form.Files;
- for (int i = 0; i < sections.Count; i++)
- {
- var s = sections[i];
- s.Id = 0;
- s.PageId = model.Id;
- var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
- await PrepareHtmlAsync(s, file);
- _db.PageSections.Add(s);
- }
- await _db.SaveChangesAsync();
- }
- _layout.Reset();
- return RedirectToAction(nameof(Index));
- }
-
- public async Task Edit(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page == null)
- {
- return NotFound();
- }
-
- await LoadTemplatesAsync();
- ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
- .OrderBy(s => s.SortOrder).ToListAsync();
- return View("PageEditor", page);
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Edit(Page model)
- {
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = model.Sections;
- return View("PageEditor", model);
- }
- if (model.IsPublished && model.PublishDate == null)
- {
- model.PublishDate = DateTime.UtcNow;
- }
- var sections = model.Sections?.ToList() ?? new List();
-<<<<<<< HEAD
- if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
-=======
- if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
->>>>>>> parent of 88cb6ce (Revert database file changes)
- {
- ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
- }
- if (!sections.Any(s => s.Area == "main"))
- {
- ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
- }
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = sections;
- model.Sections = sections;
- return View("PageEditor", model);
- }
- model.Sections = new List();
- _db.Update(model);
- await _db.SaveChangesAsync();
- var existing = _db.PageSections.Where(s => s.PageId == model.Id);
- _db.PageSections.RemoveRange(existing);
- if (sections.Count > 0)
- {
- var files = HttpContext.Request.Form.Files;
- for (int i = 0; i < sections.Count; i++)
- {
- var s = sections[i];
- s.Id = 0;
- s.PageId = model.Id;
- var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
- await PrepareHtmlAsync(s, file);
- _db.PageSections.Add(s);
- }
- await _db.SaveChangesAsync();
- }
- _layout.Reset();
- return RedirectToAction(nameof(Index));
- }
-
- private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
- {
- switch (model.Type)
- {
- case PageSectionType.Html:
- model.Html = _sanitizer.Sanitize(model.Html);
- break;
- case PageSectionType.Markdown:
- var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
- model.Html = _sanitizer.Sanitize(html);
- break;
- case PageSectionType.Code:
- model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
- break;
- case PageSectionType.Image:
- case PageSectionType.Video:
- if (file != null && file.Length > 0)
- {
- var uploads = Path.Combine("wwwroot", "uploads");
- Directory.CreateDirectory(uploads);
- var name = Path.GetFileName(file.FileName);
- var path = Path.Combine(uploads, name);
- using var stream = new FileStream(path, FileMode.Create);
- await file.CopyToAsync(stream);
- if (model.Type == PageSectionType.Image)
- model.Html = $" ";
- else
- model.Html = $" ";
- }
- break;
- }
- }
-
- public async Task Delete(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page == null)
- {
- return NotFound();
- }
- return View(page);
- }
-
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public async Task DeleteConfirmed(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page != null)
- {
- _db.Pages.Remove(page);
- await _db.SaveChangesAsync();
- _layout.Reset();
- }
- return RedirectToAction(nameof(Index));
- }
-}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
deleted file mode 100644
index 4b53843..0000000
--- a/website/MyWebApp/Controllers/AdminContentController_BACKUP_263.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using System;
-using MyWebApp.Data;
-using System.Collections.Generic;
-using MyWebApp.Filters;
-using MyWebApp.Models;
-using MyWebApp.Services;
-using Microsoft.AspNetCore.Http;
-using Markdig;
-using System.IO;
-using System.Linq;
-
-namespace MyWebApp.Controllers;
-
-[RoleAuthorize("Admin")]
-public class AdminContentController : Controller
-{
- private readonly ApplicationDbContext _db;
- private readonly LayoutService _layout;
- private readonly HtmlSanitizerService _sanitizer;
-
- public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
- {
- _db = db;
- _layout = layout;
- _sanitizer = sanitizer;
- }
-
- private async Task LoadTemplatesAsync()
- {
- ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
- .OrderBy(t => t.Name).ToListAsync();
- ViewBag.Permissions = await _db.Permissions.AsNoTracking()
- .OrderBy(p => p.Name).ToListAsync();
- ViewBag.LayoutZones = _layout.LayoutZones;
- }
-
- public async Task Index()
- {
- var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
- return View(pages);
- }
-
- public async Task Create()
- {
-
- await LoadTemplatesAsync();
- ViewBag.Sections = new List();
- return View("PageEditor", new Page());
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Create(Page model)
- {
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = model.Sections;
- return View("PageEditor", model);
- }
- if (model.IsPublished && model.PublishDate == null)
- {
- model.PublishDate = DateTime.UtcNow;
- }
- var sections = model.Sections?.ToList() ?? new List();
-<<<<<<< HEAD
- if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
-=======
- if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
->>>>>>> parent of 88cb6ce (Revert database file changes)
- {
- ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
- }
- if (!sections.Any(s => s.Area == "main"))
- {
- ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
- }
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = sections;
- model.Sections = sections;
- return View("PageEditor", model);
- }
- model.Sections = new List();
- _db.Pages.Add(model);
- await _db.SaveChangesAsync();
- if (sections.Count > 0)
- {
- var files = HttpContext.Request.Form.Files;
- for (int i = 0; i < sections.Count; i++)
- {
- var s = sections[i];
- s.Id = 0;
- s.PageId = model.Id;
- var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
- await PrepareHtmlAsync(s, file);
- _db.PageSections.Add(s);
- }
- await _db.SaveChangesAsync();
- }
- _layout.Reset();
- return RedirectToAction(nameof(Index));
- }
-
- public async Task Edit(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page == null)
- {
- return NotFound();
- }
-
- await LoadTemplatesAsync();
- ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
- .OrderBy(s => s.SortOrder).ToListAsync();
- return View("PageEditor", page);
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Edit(Page model)
- {
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = model.Sections;
- return View("PageEditor", model);
- }
- if (model.IsPublished && model.PublishDate == null)
- {
- model.PublishDate = DateTime.UtcNow;
- }
- var sections = model.Sections?.ToList() ?? new List();
-<<<<<<< HEAD
- if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
-=======
- if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
->>>>>>> parent of 88cb6ce (Revert database file changes)
- {
- ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
- }
- if (!sections.Any(s => s.Area == "main"))
- {
- ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
- }
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = sections;
- model.Sections = sections;
- return View("PageEditor", model);
- }
- model.Sections = new List();
- _db.Update(model);
- await _db.SaveChangesAsync();
- var existing = _db.PageSections.Where(s => s.PageId == model.Id);
- _db.PageSections.RemoveRange(existing);
- if (sections.Count > 0)
- {
- var files = HttpContext.Request.Form.Files;
- for (int i = 0; i < sections.Count; i++)
- {
- var s = sections[i];
- s.Id = 0;
- s.PageId = model.Id;
- var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
- await PrepareHtmlAsync(s, file);
- _db.PageSections.Add(s);
- }
- await _db.SaveChangesAsync();
- }
- _layout.Reset();
- return RedirectToAction(nameof(Index));
- }
-
- private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
- {
- switch (model.Type)
- {
- case PageSectionType.Html:
- model.Html = _sanitizer.Sanitize(model.Html);
- break;
- case PageSectionType.Markdown:
- var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
- model.Html = _sanitizer.Sanitize(html);
- break;
- case PageSectionType.Code:
- model.Html = $"{System.Net.WebUtility.HtmlEncode(model.Html)} ";
- break;
- case PageSectionType.Image:
- case PageSectionType.Video:
- if (file != null && file.Length > 0)
- {
- var uploads = Path.Combine("wwwroot", "uploads");
- Directory.CreateDirectory(uploads);
- var name = Path.GetFileName(file.FileName);
- var path = Path.Combine(uploads, name);
- using var stream = new FileStream(path, FileMode.Create);
- await file.CopyToAsync(stream);
- if (model.Type == PageSectionType.Image)
- model.Html = $" ";
- else
- model.Html = $" ";
- }
- break;
- }
- }
-
- public async Task Delete(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page == null)
- {
- return NotFound();
- }
- return View(page);
- }
-
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public async Task DeleteConfirmed(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page != null)
- {
- _db.Pages.Remove(page);
- await _db.SaveChangesAsync();
- _layout.Reset();
- }
- return RedirectToAction(nameof(Index));
- }
-}
diff --git a/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs b/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
deleted file mode 100644
index 4b53843..0000000
--- a/website/MyWebApp/Controllers/AdminContentController_BACKUP_389.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using System;
-using MyWebApp.Data;
-using System.Collections.Generic;
-using MyWebApp.Filters;
-using MyWebApp.Models;
-using MyWebApp.Services;
-using Microsoft.AspNetCore.Http;
-using Markdig;
-using System.IO;
-using System.Linq;
-
-namespace MyWebApp.Controllers;
-
-[RoleAuthorize("Admin")]
-public class AdminContentController : Controller
-{
- private readonly ApplicationDbContext _db;
- private readonly LayoutService _layout;
- private readonly HtmlSanitizerService _sanitizer;
-
- public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
- {
- _db = db;
- _layout = layout;
- _sanitizer = sanitizer;
- }
-
- private async Task LoadTemplatesAsync()
- {
- ViewBag.Templates = await _db.BlockTemplates.AsNoTracking()
- .OrderBy(t => t.Name).ToListAsync();
- ViewBag.Permissions = await _db.Permissions.AsNoTracking()
- .OrderBy(p => p.Name).ToListAsync();
- ViewBag.LayoutZones = _layout.LayoutZones;
- }
-
- public async Task Index()
- {
- var pages = await _db.Pages.AsNoTracking().OrderBy(p => p.Slug).ToListAsync();
- return View(pages);
- }
-
- public async Task Create()
- {
-
- await LoadTemplatesAsync();
- ViewBag.Sections = new List();
- return View("PageEditor", new Page());
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task Create(Page model)
- {
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = model.Sections;
- return View("PageEditor", model);
- }
- if (model.IsPublished && model.PublishDate == null)
- {
- model.PublishDate = DateTime.UtcNow;
- }
- var sections = model.Sections?.ToList() ?? new List();
-<<<<<<< HEAD
- if (sections.Any(s => !_layout.IsValidZone(model.Layout, s.Zone)))
-=======
- if (sections.Any(s => !LayoutService.IsValidArea(model.Layout, s.Area)))
->>>>>>> parent of 88cb6ce (Revert database file changes)
- {
- ModelState.AddModelError(string.Empty, "Invalid area for selected layout.");
- }
- if (!sections.Any(s => s.Area == "main"))
- {
- ModelState.AddModelError(string.Empty, "Main area cannot be empty.");
- }
- if (!ModelState.IsValid)
- {
- await LoadTemplatesAsync();
- ViewBag.Sections = sections;
- model.Sections = sections;
- return View("PageEditor", model);
- }
- model.Sections = new List();
- _db.Pages.Add(model);
- await _db.SaveChangesAsync();
- if (sections.Count > 0)
- {
- var files = HttpContext.Request.Form.Files;
- for (int i = 0; i < sections.Count; i++)
- {
- var s = sections[i];
- s.Id = 0;
- s.PageId = model.Id;
- var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
- await PrepareHtmlAsync(s, file);
- _db.PageSections.Add(s);
- }
- await _db.SaveChangesAsync();
- }
- _layout.Reset();
- return RedirectToAction(nameof(Index));
- }
-
- public async Task Edit(int id)
- {
- var page = await _db.Pages.FindAsync(id);
- if (page == null)
- {
- return NotFound();
- }
-
- await LoadTemplatesAsync();
- ViewBag.Sections = await _db.PageSections.Where(s => s.PageId == id)
- .OrderBy(s => s.SortOrder).ToListAsync();
- return View("PageEditor", page);
-
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task