diff --git a/src/MyWebLog.Data/Extensions/CategoryExtensions.cs b/src/MyWebLog.Data/Extensions/CategoryExtensions.cs new file mode 100644 index 0000000..37d4dd8 --- /dev/null +++ b/src/MyWebLog.Data/Extensions/CategoryExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace MyWebLog.Data; + +public static class CategoryEtensions +{ + /// + /// Count all categories + /// + /// A count of all categories + public static async Task CountAll(this DbSet db) => + await db.CountAsync().ConfigureAwait(false); +} diff --git a/src/MyWebLog.Data/Extensions/PageExtensions.cs b/src/MyWebLog.Data/Extensions/PageExtensions.cs index c105815..2f90f65 100644 --- a/src/MyWebLog.Data/Extensions/PageExtensions.cs +++ b/src/MyWebLog.Data/Extensions/PageExtensions.cs @@ -4,11 +4,26 @@ namespace MyWebLog.Data; public static class PageExtensions { + /// + /// Count the number of pages + /// + /// The number of pages + public static async Task CountAll(this DbSet db) => + await db.CountAsync().ConfigureAwait(false); + /// /// Retrieve a page by its ID (non-tracked) /// /// The ID of the page to retrieve /// The requested page (or null if it is not found) public static async Task FindById(this DbSet db, string id) => - await db.FirstOrDefaultAsync(p => p.Id == id).ConfigureAwait(false); + await db.SingleOrDefaultAsync(p => p.Id == id).ConfigureAwait(false); + + /// + /// Retrieve a page by its permalink (non-tracked) + /// + /// The permalink + /// The requested page (or null if it is not found) + public static async Task FindByPermalink(this DbSet db, string permalink) => + await db.SingleOrDefaultAsync(p => p.Permalink == permalink).ConfigureAwait(false); } diff --git a/src/MyWebLog.Data/Extensions/PostExtensions.cs b/src/MyWebLog.Data/Extensions/PostExtensions.cs index 818d3ca..dd28fe1 100644 --- a/src/MyWebLog.Data/Extensions/PostExtensions.cs +++ b/src/MyWebLog.Data/Extensions/PostExtensions.cs @@ -4,6 +4,22 @@ namespace MyWebLog.Data; public static class PostExtensions { + /// + /// Count the posts in the given status + /// + /// The status for which posts should be counted + /// A count of the posts in the given status + public static async Task CountByStatus(this DbSet db, PostStatus status) => + await db.CountAsync(p => p.Status == status).ConfigureAwait(false); + + /// + /// Retrieve a post by its permalink (non-tracked) + /// + /// The possible post permalink + /// The post matching the permalink, or null if none is found + public static async Task FindByPermalink(this DbSet db, string permalink) => + await db.SingleOrDefaultAsync(p => p.Id == permalink).ConfigureAwait(false); + /// /// Retrieve a page of published posts (non-tracked) /// diff --git a/src/MyWebLog/Features/Admin/AdminController.cs b/src/MyWebLog/Features/Admin/AdminController.cs index 088ee6e..efd99e0 100644 --- a/src/MyWebLog/Features/Admin/AdminController.cs +++ b/src/MyWebLog/Features/Admin/AdminController.cs @@ -12,8 +12,12 @@ public class AdminController : MyWebLogController public AdminController(WebLogDbContext db) : base(db) { } [HttpGet("")] - public IActionResult Index() - { - return View(); - } + public async Task Index() => + View(new DashboardModel(WebLog) + { + Posts = await Db.Posts.CountByStatus(PostStatus.Published), + Drafts = await Db.Posts.CountByStatus(PostStatus.Draft), + Pages = await Db.Pages.CountAll(), + Categories = await Db.Categories.CountAll() + }); } diff --git a/src/MyWebLog/Features/Admin/DashboardModel.cs b/src/MyWebLog/Features/Admin/DashboardModel.cs new file mode 100644 index 0000000..2342334 --- /dev/null +++ b/src/MyWebLog/Features/Admin/DashboardModel.cs @@ -0,0 +1,30 @@ +namespace MyWebLog.Features.Admin; + +/// +/// The model used to display the dashboard +/// +public class DashboardModel : MyWebLogModel +{ + /// + /// The number of published posts + /// + public int Posts { get; set; } = 0; + + /// + /// The number of post drafts + /// + public int Drafts { get; set; } = 0; + + /// + /// The number of pages + /// + public int Pages { get; set; } = 0; + + /// + /// The number of categories + /// + public int Categories { get; set; } = 0; + + /// + public DashboardModel(WebLogDetails webLog) : base(webLog) { } +} diff --git a/src/MyWebLog/Features/Admin/Index.cshtml b/src/MyWebLog/Features/Admin/Index.cshtml index 52fecf9..b9f7ff7 100644 --- a/src/MyWebLog/Features/Admin/Index.cshtml +++ b/src/MyWebLog/Features/Admin/Index.cshtml @@ -1,5 +1,35 @@ -@{ +@model DashboardModel +@{ Layout = "_AdminLayout"; ViewBag.Title = Resources.Dashboard; } -

You're logged on!

+ diff --git a/src/MyWebLog/Features/Categories/CategoryController.cs b/src/MyWebLog/Features/Categories/CategoryController.cs new file mode 100644 index 0000000..5ad1b81 --- /dev/null +++ b/src/MyWebLog/Features/Categories/CategoryController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MyWebLog.Features.Categories; + +/// +/// Handle routes for categories +/// +[Route("/category")] +[Authorize] +public class CategoryController : MyWebLogController +{ + /// + public CategoryController(WebLogDbContext db) : base(db) { } + + [HttpGet("all")] + public async Task All() + { + await Task.CompletedTask; + throw new NotImplementedException(); + } + + [HttpGet("{id}/edit")] + public async Task Edit(string id) + { + await Task.CompletedTask; + throw new NotImplementedException(); + } +} diff --git a/src/MyWebLog/Features/Pages/PageController.cs b/src/MyWebLog/Features/Pages/PageController.cs new file mode 100644 index 0000000..353b5a8 --- /dev/null +++ b/src/MyWebLog/Features/Pages/PageController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MyWebLog.Features.Pages; + +/// +/// Handle routes for pages +/// +[Route("/page")] +[Authorize] +public class PageController : MyWebLogController +{ + /// + public PageController(WebLogDbContext db) : base(db) { } + + [HttpGet("all")] + public async Task All() + { + await Task.CompletedTask; + throw new NotImplementedException(); + } + + [HttpGet("{id}/edit")] + public async Task Edit(string id) + { + await Task.CompletedTask; + throw new NotImplementedException(); + } +} diff --git a/src/MyWebLog/Features/Pages/SinglePageModel.cs b/src/MyWebLog/Features/Pages/SinglePageModel.cs new file mode 100644 index 0000000..6138cac --- /dev/null +++ b/src/MyWebLog/Features/Pages/SinglePageModel.cs @@ -0,0 +1,22 @@ +namespace MyWebLog.Features.Pages; + +/// +/// The model used to render a single page +/// +public class SinglePageModel : MyWebLogModel +{ + /// + /// The page to be rendered + /// + public Page Page { get; init; } + + /// + /// Constructor + /// + /// The page to be rendered + /// The details for the web log + public SinglePageModel(Page page, WebLogDetails webLog) : base(webLog) + { + Page = page; + } +} diff --git a/src/MyWebLog/Features/Posts/MultiplePostModel.cs b/src/MyWebLog/Features/Posts/MultiplePostModel.cs new file mode 100644 index 0000000..20c3fcb --- /dev/null +++ b/src/MyWebLog/Features/Posts/MultiplePostModel.cs @@ -0,0 +1,22 @@ +namespace MyWebLog.Features.Posts; + +/// +/// The model used to render multiple posts +/// +public class MultiplePostModel : MyWebLogModel +{ + /// + /// The posts to be rendered + /// + public IEnumerable Posts { get; init; } + + /// + /// Constructor + /// + /// The posts to be rendered + /// The details for the web log + public MultiplePostModel(IEnumerable posts, WebLogDetails webLog) : base(webLog) + { + Posts = posts; + } +} diff --git a/src/MyWebLog/Features/Posts/PostController.cs b/src/MyWebLog/Features/Posts/PostController.cs index 123d6c8..93c2b1f 100644 --- a/src/MyWebLog/Features/Posts/PostController.cs +++ b/src/MyWebLog/Features/Posts/PostController.cs @@ -1,25 +1,68 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MyWebLog.Features.Pages; namespace MyWebLog.Features.Posts; /// /// Handle post-related requests /// +[Route("/post")] +[Authorize] public class PostController : MyWebLogController { /// public PostController(WebLogDbContext db) : base(db) { } [HttpGet("~/")] + [AllowAnonymous] public async Task Index() { - var webLog = WebLogCache.Get(HttpContext); - if (webLog.DefaultPage == "posts") + if (WebLog.DefaultPage == "posts") return await PageOfPosts(1); + + var page = await Db.Pages.FindById(WebLog.DefaultPage); + return page is null ? NotFound() : ThemedView("SinglePage", new SinglePageModel(page, WebLog)); + } + + [HttpGet("~/page/{pageNbr:int}")] + [AllowAnonymous] + public async Task PageOfPosts(int pageNbr) => + ThemedView("Index", + new MultiplePostModel(await Db.Posts.FindPageOfPublishedPosts(pageNbr, WebLog.PostsPerPage), WebLog)); + + [HttpGet("~/{*permalink}")] + public async Task CatchAll(string permalink) + { + Console.Write($"Got permalink |{permalink}|"); + var post = await Db.Posts.FindByPermalink(permalink); + if (post != null) { - var posts = await Db.Posts.FindPageOfPublishedPosts(1, webLog.PostsPerPage); - return ThemedView("Index", posts); + // TODO: return via single-post action } - var page = await Db.Pages.FindById(webLog.DefaultPage); - return page is null ? NotFound() : ThemedView("SinglePage", page); + + var page = await Db.Pages.FindByPermalink(permalink); + if (page != null) + { + return ThemedView("SinglePage", new SinglePageModel(page, WebLog)); + } + + // TOOD: search prior permalinks for posts and pages + + await Task.CompletedTask; + throw new NotImplementedException(); + } + + [HttpGet("all")] + public async Task All() + { + await Task.CompletedTask; + throw new NotImplementedException(); + } + + [HttpGet("{id}/edit")] + public async Task Edit(string id) + { + await Task.CompletedTask; + throw new NotImplementedException(); } } diff --git a/src/MyWebLog/Features/Shared/MyWebLogController.cs b/src/MyWebLog/Features/Shared/MyWebLogController.cs index a6bd7b3..c45d05d 100644 --- a/src/MyWebLog/Features/Shared/MyWebLogController.cs +++ b/src/MyWebLog/Features/Shared/MyWebLogController.cs @@ -9,11 +9,16 @@ public abstract class MyWebLogController : Controller /// protected WebLogDbContext Db { get; init; } + /// + /// The details for the current web log + /// + protected WebLogDetails WebLog => WebLogCache.Get(HttpContext); + /// /// Constructor /// /// The data context to use to fulfil this request - protected MyWebLogController(WebLogDbContext db) + protected MyWebLogController(WebLogDbContext db) : base() { Db = db; } diff --git a/src/MyWebLog/Features/Shared/MyWebLogModel.cs b/src/MyWebLog/Features/Shared/MyWebLogModel.cs new file mode 100644 index 0000000..48d6e7f --- /dev/null +++ b/src/MyWebLog/Features/Shared/MyWebLogModel.cs @@ -0,0 +1,21 @@ +namespace MyWebLog.Features.Shared; + +/// +/// Base model class for myWebLog views +/// +public class MyWebLogModel +{ + /// + /// The details for the web log + /// + public WebLogDetails WebLog { get; init; } + + /// + /// Constructor + /// + /// The details for the web log + protected MyWebLogModel(WebLogDetails webLog) + { + WebLog = webLog; + } +} diff --git a/src/MyWebLog/Features/Shared/_AdminLayout.cshtml b/src/MyWebLog/Features/Shared/_AdminLayout.cshtml index d32e32f..6eebde7 100644 --- a/src/MyWebLog/Features/Shared/_AdminLayout.cshtml +++ b/src/MyWebLog/Features/Shared/_AdminLayout.cshtml @@ -1,12 +1,9 @@ -@inject IHttpContextAccessor ctxAcc -@{ - var details = WebLogCache.Get(ctxAcc.HttpContext!); -} +@model MyWebLogModel - @ViewBag.Title « @Resources.Admin « @details.Name + @ViewBag.Title « @Resources.Admin « @Model.WebLog.Name @@ -15,7 +12,7 @@