Skip to content

Commit 000648c

Browse files
committed
Refactor content editors and endpoints
1 parent 126a792 commit 000648c

13 files changed

Lines changed: 156 additions & 207 deletions

TEST_PLAN.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Test Plan
2+
3+
1. **Build and restore dependencies**
4+
- Run `./setup.sh` to restore NuGet and client libraries.
5+
- Ensure the command completes without errors.
6+
7+
2. **Run unit tests**
8+
- Execute `dotnet test website/MyWebApp.sln`.
9+
- All tests should pass.
10+
11+
3. **Manual UI smoke test**
12+
- Start the application with `dotnet run --project website/MyWebApp`.
13+
- Verify admin pages load correctly:
14+
- Create and edit pages and sections using the unified section editor.
15+
- Insert blocks via the updated API endpoints.
16+
- Confirm media uploads still work and page layouts render as before.
17+

website/MyWebApp.Tests/SanitizationTests.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
public class SanitizationTests
1414
{
15-
private static (ApplicationDbContext ctx, LayoutService layout, HtmlSanitizerService sanitizer) CreateServices()
15+
private static (ApplicationDbContext ctx, LayoutService layout, ContentProcessingService content) CreateServices()
1616
{
1717
var connection = new SqliteConnection("DataSource=:memory:");
1818
connection.Open();
@@ -34,14 +34,15 @@ private static (ApplicationDbContext ctx, LayoutService layout, HtmlSanitizerSer
3434
.Build();
3535
var layout = new LayoutService(cache, tokens);
3636
var sanitizer = new HtmlSanitizerService();
37-
return (ctx, layout, sanitizer);
37+
var content = new ContentProcessingService(sanitizer);
38+
return (ctx, layout, content);
3839
}
3940

4041
[Fact(Skip = "Create sanitization covered by section tests")]
4142
public async Task CreatePage_SanitizesHtml()
4243
{
43-
var (ctx, layout, sanitizer) = CreateServices();
44-
var controller = new AdminContentController(ctx, layout, sanitizer);
44+
var (ctx, layout, content) = CreateServices();
45+
var controller = new AdminContentController(ctx, layout, content);
4546
var model = new Page
4647
{
4748
Slug = "test",
@@ -61,8 +62,8 @@ public async Task CreatePage_SanitizesHtml()
6162
[Fact]
6263
public async Task CreateSection_SanitizesHtml()
6364
{
64-
var (ctx, layout, sanitizer) = CreateServices();
65-
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
65+
var (ctx, layout, content) = CreateServices();
66+
var controller = new AdminPageSectionController(ctx, layout, content);
6667

6768
var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "main", Html = "<div>hi</div><script>bad()</script>", Type = PageSectionType.Html };
6869
var result = await controller.Create(model, null);
@@ -75,8 +76,8 @@ public async Task CreateSection_SanitizesHtml()
7576
[Fact(Skip = "Edit sanitization covered by section tests")]
7677
public async Task EditPage_SanitizesHtml()
7778
{
78-
var (ctx, layout, sanitizer) = CreateServices();
79-
var controller = new AdminContentController(ctx, layout, sanitizer);
79+
var (ctx, layout, content) = CreateServices();
80+
var controller = new AdminContentController(ctx, layout, content);
8081
var createModel = new Page
8182
{
8283
Slug = "edit",
@@ -103,8 +104,8 @@ public async Task EditPage_SanitizesHtml()
103104
[Fact]
104105
public async Task CreateSection_MarkdownConverted()
105106
{
106-
var (ctx, layout, sanitizer) = CreateServices();
107-
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
107+
var (ctx, layout, content) = CreateServices();
108+
var controller = new AdminPageSectionController(ctx, layout, content);
108109
var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "md", Html = "# Hello\n<script>bad()</script>", Type = PageSectionType.Markdown };
109110
var result = await controller.Create(model, null);
110111
Assert.IsType<RedirectToActionResult>(result);
@@ -116,8 +117,8 @@ public async Task CreateSection_MarkdownConverted()
116117
[Fact]
117118
public async Task CreateSection_CodeEncoded()
118119
{
119-
var (ctx, layout, sanitizer) = CreateServices();
120-
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
120+
var (ctx, layout, content) = CreateServices();
121+
var controller = new AdminPageSectionController(ctx, layout, content);
121122
var model = new PageSection { PageId = ctx.Pages.First().Id, Zone = "code", Html = "<b>test</b>", Type = PageSectionType.Code };
122123
var result = await controller.Create(model, null);
123124
Assert.IsType<RedirectToActionResult>(result);
@@ -128,8 +129,8 @@ public async Task CreateSection_CodeEncoded()
128129
[Fact]
129130
public async Task CreateSection_ImageStoresTag()
130131
{
131-
var (ctx, layout, sanitizer) = CreateServices();
132-
var controller = new AdminPageSectionController(ctx, layout, sanitizer);
132+
var (ctx, layout, content) = CreateServices();
133+
var controller = new AdminPageSectionController(ctx, layout, content);
133134
var bytes = new byte[] { 1, 2, 3 };
134135
using var stream = new System.IO.MemoryStream(bytes);
135136
var file = new FormFile(stream, 0, bytes.Length, "file", "img.png");

website/MyWebApp/Controllers/AdminBlockTemplateController.cs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -169,35 +169,4 @@ public async Task<IActionResult> AddToPage(int id, int pageId, string zone)
169169
return RedirectToAction(nameof(Index));
170170
}
171171

172-
[HttpGet]
173-
public async Task<IActionResult> GetBlocks()
174-
{
175-
var items = await _db.BlockTemplates.AsNoTracking()
176-
.OrderBy(t => t.Name)
177-
.Select(t => new { t.Id, t.Name })
178-
.ToListAsync();
179-
return Json(items);
180-
}
181-
182-
[HttpGet]
183-
public async Task<IActionResult> GetPages()
184-
{
185-
var pages = await _db.Pages.AsNoTracking()
186-
.OrderBy(p => p.Slug)
187-
.Select(p => new { p.Id, p.Slug })
188-
.ToListAsync();
189-
return Json(pages);
190-
}
191-
192-
[HttpGet]
193-
public async Task<IActionResult> GetSections(int id)
194-
{
195-
var zones = await _db.PageSections.AsNoTracking()
196-
.Where(s => s.PageId == id)
197-
.Select(s => s.Zone)
198-
.Distinct()
199-
.OrderBy(a => a)
200-
.ToListAsync();
201-
return Json(zones);
202-
}
203172
}

website/MyWebApp/Controllers/AdminContentController.cs

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
using MyWebApp.Models;
88
using MyWebApp.Services;
99
using Microsoft.AspNetCore.Http;
10-
using Markdig;
11-
using System.IO;
1210
using System.Linq;
1311

1412
namespace MyWebApp.Controllers;
@@ -18,13 +16,13 @@ public class AdminContentController : Controller
1816
{
1917
private readonly ApplicationDbContext _db;
2018
private readonly LayoutService _layout;
21-
private readonly HtmlSanitizerService _sanitizer;
19+
private readonly ContentProcessingService _content;
2220

23-
public AdminContentController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
21+
public AdminContentController(ApplicationDbContext db, LayoutService layout, ContentProcessingService content)
2422
{
2523
_db = db;
2624
_layout = layout;
27-
_sanitizer = sanitizer;
25+
_content = content;
2826
}
2927

3028
private async Task LoadTemplatesAsync()
@@ -92,7 +90,7 @@ public async Task<IActionResult> Create(Page model)
9290
s.Id = 0;
9391
s.PageId = model.Id;
9492
var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
95-
await PrepareHtmlAsync(s, file);
93+
await _content.PrepareHtmlAsync(s, file);
9694
_db.PageSections.Add(s);
9795
}
9896
await _db.SaveChangesAsync();
@@ -160,7 +158,7 @@ public async Task<IActionResult> Edit(Page model)
160158
s.Id = 0;
161159
s.PageId = model.Id;
162160
var file = files.FirstOrDefault(f => f.Name == $"Sections[{i}].File");
163-
await PrepareHtmlAsync(s, file);
161+
await _content.PrepareHtmlAsync(s, file);
164162
_db.PageSections.Add(s);
165163
}
166164
await _db.SaveChangesAsync();
@@ -169,38 +167,6 @@ public async Task<IActionResult> Edit(Page model)
169167
return RedirectToAction(nameof(Index));
170168
}
171169

172-
private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
173-
{
174-
switch (model.Type)
175-
{
176-
case PageSectionType.Html:
177-
model.Html = _sanitizer.Sanitize(model.Html);
178-
break;
179-
case PageSectionType.Markdown:
180-
var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
181-
model.Html = _sanitizer.Sanitize(html);
182-
break;
183-
case PageSectionType.Code:
184-
model.Html = $"<pre><code>{System.Net.WebUtility.HtmlEncode(model.Html)}</code></pre>";
185-
break;
186-
case PageSectionType.Image:
187-
case PageSectionType.Video:
188-
if (file != null && file.Length > 0)
189-
{
190-
var uploads = Path.Combine("wwwroot", "uploads");
191-
Directory.CreateDirectory(uploads);
192-
var name = Path.GetFileName(file.FileName);
193-
var path = Path.Combine(uploads, name);
194-
using var stream = new FileStream(path, FileMode.Create);
195-
await file.CopyToAsync(stream);
196-
if (model.Type == PageSectionType.Image)
197-
model.Html = $"<img src='/uploads/{name}' alt='' />";
198-
else
199-
model.Html = $"<video controls src='/uploads/{name}'></video>";
200-
}
201-
break;
202-
}
203-
}
204170

205171
public async Task<IActionResult> Delete(int id)
206172
{

website/MyWebApp/Controllers/AdminPageSectionController.cs

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using Microsoft.AspNetCore.Mvc;
22
using Microsoft.EntityFrameworkCore;
3-
using Microsoft.AspNetCore.Mvc.Rendering;
43
using Microsoft.AspNetCore.Http;
5-
using Markdig;
6-
using System.IO;
74
using MyWebApp.Data;
85
using MyWebApp.Filters;
96
using MyWebApp.Models;
@@ -16,13 +13,13 @@ public class AdminPageSectionController : Controller
1613
{
1714
private readonly ApplicationDbContext _db;
1815
private readonly LayoutService _layout;
19-
private readonly HtmlSanitizerService _sanitizer;
16+
private readonly ContentProcessingService _content;
2017

21-
public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, HtmlSanitizerService sanitizer)
18+
public AdminPageSectionController(ApplicationDbContext db, LayoutService layout, ContentProcessingService content)
2219
{
2320
_db = db;
2421
_layout = layout;
25-
_sanitizer = sanitizer;
22+
_content = content;
2623
}
2724

2825
public async Task<IActionResult> Index(string? q)
@@ -64,7 +61,7 @@ public async Task<IActionResult> Create(PageSection model, IFormFile? file)
6461
await LoadPagesAsync();
6562
return View(model);
6663
}
67-
await PrepareHtmlAsync(model, file);
64+
await _content.PrepareHtmlAsync(model, file);
6865
_db.PageSections.Add(model);
6966
await _db.SaveChangesAsync();
7067
_layout.Reset();
@@ -93,45 +90,13 @@ public async Task<IActionResult> Edit(PageSection model, IFormFile? file)
9390
await LoadPagesAsync();
9491
return View(model);
9592
}
96-
await PrepareHtmlAsync(model, file);
93+
await _content.PrepareHtmlAsync(model, file);
9794
_db.Update(model);
9895
await _db.SaveChangesAsync();
9996
_layout.Reset();
10097
return RedirectToAction(nameof(Index));
10198
}
10299

103-
private async Task PrepareHtmlAsync(PageSection model, IFormFile? file)
104-
{
105-
switch (model.Type)
106-
{
107-
case PageSectionType.Html:
108-
model.Html = _sanitizer.Sanitize(model.Html);
109-
break;
110-
case PageSectionType.Markdown:
111-
var html = Markdig.Markdown.ToHtml(model.Html ?? string.Empty);
112-
model.Html = _sanitizer.Sanitize(html);
113-
break;
114-
case PageSectionType.Code:
115-
model.Html = $"<pre><code>{System.Net.WebUtility.HtmlEncode(model.Html)}</code></pre>";
116-
break;
117-
case PageSectionType.Image:
118-
case PageSectionType.Video:
119-
if (file != null && file.Length > 0)
120-
{
121-
var uploads = Path.Combine("wwwroot", "uploads");
122-
Directory.CreateDirectory(uploads);
123-
var name = Path.GetFileName(file.FileName);
124-
var path = Path.Combine(uploads, name);
125-
using var stream = new FileStream(path, FileMode.Create);
126-
await file.CopyToAsync(stream);
127-
if (model.Type == PageSectionType.Image)
128-
model.Html = $"<img src='/uploads/{name}' alt='' />";
129-
else
130-
model.Html = $"<video controls src='/uploads/{name}'></video>";
131-
}
132-
break;
133-
}
134-
}
135100

136101
public async Task<IActionResult> Delete(int id)
137102
{
@@ -154,11 +119,4 @@ public async Task<IActionResult> DeleteConfirmed(int id)
154119
return RedirectToAction(nameof(Index));
155120
}
156121

157-
[HttpGet]
158-
public async Task<IActionResult> GetZonesForPage(int id)
159-
{
160-
var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
161-
var zones = LayoutService.GetZones(layout);
162-
return Json(zones);
163-
}
164122
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.EntityFrameworkCore;
3+
using MyWebApp.Data;
4+
using MyWebApp.Filters;
5+
using MyWebApp.Services;
6+
7+
namespace MyWebApp.Controllers;
8+
9+
[RoleAuthorize("Admin")]
10+
public class ApiController : Controller
11+
{
12+
private readonly ApplicationDbContext _db;
13+
14+
public ApiController(ApplicationDbContext db)
15+
{
16+
_db = db;
17+
}
18+
19+
[HttpGet]
20+
public async Task<IActionResult> GetBlocks()
21+
{
22+
var items = await _db.BlockTemplates.AsNoTracking()
23+
.OrderBy(t => t.Name)
24+
.Select(t => new { t.Id, t.Name })
25+
.ToListAsync();
26+
return Json(items);
27+
}
28+
29+
[HttpGet]
30+
public async Task<IActionResult> GetPages()
31+
{
32+
var pages = await _db.Pages.AsNoTracking()
33+
.OrderBy(p => p.Slug)
34+
.Select(p => new { p.Id, p.Slug })
35+
.ToListAsync();
36+
return Json(pages);
37+
}
38+
39+
[HttpGet]
40+
public async Task<IActionResult> GetSections(int id)
41+
{
42+
var zones = await _db.PageSections.AsNoTracking()
43+
.Where(s => s.PageId == id)
44+
.Select(s => s.Zone)
45+
.Distinct()
46+
.OrderBy(a => a)
47+
.ToListAsync();
48+
return Json(zones);
49+
}
50+
51+
[HttpGet]
52+
public async Task<IActionResult> GetZonesForPage(int id)
53+
{
54+
var layout = await _db.Pages.Where(p => p.Id == id).Select(p => p.Layout).FirstOrDefaultAsync() ?? "single-column";
55+
var zones = LayoutService.GetZones(layout);
56+
return Json(zones);
57+
}
58+
}

website/MyWebApp/Data/ApplicationDbContext.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
111111
Type = PageSectionType.Html,
112112

113113
Html = "<div class=\"container-fluid nav-container\"><a class=\"logo\" href=\"/\">Screen Area Recorder Pro</a><nav class=\"site-nav\"><a href=\"/\">Home</a> {{nav}} <a href=\"/Download\">Download</a> <a href=\"/Home/Faq\">FAQ</a> <a href=\"/Home/Privacy\">Privacy</a> <a href=\"/Setup\">Setup</a> <a href=\"/Account/Login\">Login</a></nav></div>"
114-
,
115-
ViewCount = 0
114+
116115
},
117116
new PageSection
118117
{

website/MyWebApp/Models/PageSection.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class PageSection
3737

3838
public int? PermissionId { get; set; }
3939

40-
public int ViewCount { get; set; }
4140

4241
public Page? Page { get; set; }
4342

0 commit comments

Comments
 (0)