From f8c58dbc3d321546f59b5811111eaa2ad7a0fcf2 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 27 Feb 2022 23:05:22 -0500 Subject: [PATCH] Add settings update page More dashboard UI work --- .../Extensions/CategoryExtensions.cs | 9 +- .../Extensions/PageExtensions.cs | 14 +++ .../Extensions/WebLogDetailsExtensions.cs | 10 +- .../Features/Admin/AdminController.cs | 34 ++++- src/MyWebLog/Features/Admin/DashboardModel.cs | 10 ++ src/MyWebLog/Features/Admin/Index.cshtml | 72 +++++++---- src/MyWebLog/Features/Admin/Settings.cshtml | 50 ++++++++ src/MyWebLog/Features/Admin/SettingsModel.cs | 76 ++++++++++++ src/MyWebLog/Features/Posts/PostController.cs | 6 +- .../Features/Shared/_AdminLayout.cshtml | 1 - src/MyWebLog/Features/Users/LogOn.cshtml | 3 +- src/MyWebLog/Properties/Resources.Designer.cs | 117 ++++++++++++++++++ src/MyWebLog/Properties/Resources.resx | 39 ++++++ 13 files changed, 407 insertions(+), 34 deletions(-) create mode 100644 src/MyWebLog/Features/Admin/Settings.cshtml create mode 100644 src/MyWebLog/Features/Admin/SettingsModel.cs diff --git a/src/MyWebLog.Data/Extensions/CategoryExtensions.cs b/src/MyWebLog.Data/Extensions/CategoryExtensions.cs index 37d4dd8..138cc32 100644 --- a/src/MyWebLog.Data/Extensions/CategoryExtensions.cs +++ b/src/MyWebLog.Data/Extensions/CategoryExtensions.cs @@ -2,7 +2,7 @@ namespace MyWebLog.Data; -public static class CategoryEtensions +public static class CategoryExtensions { /// /// Count all categories @@ -10,4 +10,11 @@ public static class CategoryEtensions /// A count of all categories public static async Task CountAll(this DbSet db) => await db.CountAsync().ConfigureAwait(false); + + /// + /// Count top-level categories (those that do not have a parent) + /// + /// A count of all top-level categories + public static async Task CountTopLevel(this DbSet db) => + await db.CountAsync(c => c.ParentId == null).ConfigureAwait(false); } diff --git a/src/MyWebLog.Data/Extensions/PageExtensions.cs b/src/MyWebLog.Data/Extensions/PageExtensions.cs index 2f90f65..235cb94 100644 --- a/src/MyWebLog.Data/Extensions/PageExtensions.cs +++ b/src/MyWebLog.Data/Extensions/PageExtensions.cs @@ -11,6 +11,20 @@ public static class PageExtensions public static async Task CountAll(this DbSet db) => await db.CountAsync().ConfigureAwait(false); + /// + /// Count the number of pages in the page list + /// + /// The number of pages in the page list + public static async Task CountListed(this DbSet db) => + await db.CountAsync(p => p.ShowInPageList).ConfigureAwait(false); + + /// + /// Retrieve all pages (non-tracked) + /// + /// A list of all pages + public static async Task> FindAll(this DbSet db) => + await db.OrderBy(p => p.Title).ToListAsync().ConfigureAwait(false); + /// /// Retrieve a page by its ID (non-tracked) /// diff --git a/src/MyWebLog.Data/Extensions/WebLogDetailsExtensions.cs b/src/MyWebLog.Data/Extensions/WebLogDetailsExtensions.cs index 9d3f8e1..821cbf4 100644 --- a/src/MyWebLog.Data/Extensions/WebLogDetailsExtensions.cs +++ b/src/MyWebLog.Data/Extensions/WebLogDetailsExtensions.cs @@ -5,10 +5,18 @@ namespace MyWebLog.Data; public static class WebLogDetailsExtensions { /// - /// Find the details of a web log by its host + /// Find the details of a web log by its host (non-tracked) /// /// The host /// The web log (or null if not found) public static async Task FindByHost(this DbSet db, string host) => await db.FirstOrDefaultAsync(wld => wld.UrlBase == host).ConfigureAwait(false); + + /// + /// Get the details of a web log by its host (tracked) + /// + /// The host + /// The web log (or null if not found) + public static async Task GetByHost(this DbSet db, string host) => + await db.AsTracking().FirstOrDefaultAsync(wld => wld.UrlBase == host).ConfigureAwait(false); } diff --git a/src/MyWebLog/Features/Admin/AdminController.cs b/src/MyWebLog/Features/Admin/AdminController.cs index efd99e0..af2be0f 100644 --- a/src/MyWebLog/Features/Admin/AdminController.cs +++ b/src/MyWebLog/Features/Admin/AdminController.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; namespace MyWebLog.Features.Admin; @@ -6,6 +8,7 @@ namespace MyWebLog.Features.Admin; /// Controller for admin-specific displays and routes /// [Route("/admin")] +[Authorize] public class AdminController : MyWebLogController { /// @@ -18,6 +21,33 @@ public class AdminController : MyWebLogController Posts = await Db.Posts.CountByStatus(PostStatus.Published), Drafts = await Db.Posts.CountByStatus(PostStatus.Draft), Pages = await Db.Pages.CountAll(), - Categories = await Db.Categories.CountAll() + ListedPages = await Db.Pages.CountListed(), + Categories = await Db.Categories.CountAll(), + TopLevelCategories = await Db.Categories.CountTopLevel() }); + + [HttpGet("settings")] + public async Task Settings() => + View(new SettingsModel(WebLog) + { + DefaultPages = Enumerable.Repeat(new SelectListItem($"- {Resources.FirstPageOfPosts} -", "posts"), 1) + .Concat((await Db.Pages.FindAll()).Select(p => new SelectListItem(p.Title, p.Id))) + }); + + [HttpPost("settings")] + public async Task SaveSettings(SettingsModel model) + { + var details = await Db.WebLogDetails.GetByHost(WebLog.UrlBase); + if (details is null) return NotFound(); + + model.PopulateSettings(details); + await Db.SaveChangesAsync(); + + // Update cache + WebLogCache.Set(WebLogCache.HostToDb(HttpContext), (await Db.WebLogDetails.FindByHost(WebLog.UrlBase))!); + + // TODO: confirmation message + + return RedirectToAction(nameof(Index)); + } } diff --git a/src/MyWebLog/Features/Admin/DashboardModel.cs b/src/MyWebLog/Features/Admin/DashboardModel.cs index 2342334..3307a59 100644 --- a/src/MyWebLog/Features/Admin/DashboardModel.cs +++ b/src/MyWebLog/Features/Admin/DashboardModel.cs @@ -20,11 +20,21 @@ public class DashboardModel : MyWebLogModel /// public int Pages { get; set; } = 0; + /// + /// The number of pages in the page list + /// + public int ListedPages { get; set; } = 0; + /// /// The number of categories /// public int Categories { get; set; } = 0; + /// + /// The top-level categories + /// + public int TopLevelCategories { 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 b9f7ff7..27fb44d 100644 --- a/src/MyWebLog/Features/Admin/Index.cshtml +++ b/src/MyWebLog/Features/Admin/Index.cshtml @@ -3,33 +3,55 @@ Layout = "_AdminLayout"; ViewBag.Title = Resources.Dashboard; } -
+
-
-

@Resources.Posts

-

@string.Format(Resources.ThereAreXPublishedPostsAndYDrafts, Model.Posts, Model.Drafts)

-

- View All - Write a New Post -

+
+
+
@Resources.Posts
+
+
+ @Resources.Published @Model.Posts +   @Resources.Drafts @Model.Drafts +
+ View All + Write a New Post +
+
-
-

@Resources.Pages

-

@string.Format(Resources.ThereAreXPages, Model.Pages)

-

- View All - Create a New Page -

-
-
-

@Resources.Categories

-

@string.Format(Resources.ThereAreXCategories, Model.Categories)

-

- View All - - Add a New Category - -

+
+
+
@Resources.Pages
+
+
+ @Resources.All @Model.Pages +   @Resources.ShownInPageList @Model.ListedPages +
+ View All + Create a New Page +
+
+
+
+
+
@Resources.Categories
+
+
+ @Resources.All @Model.Categories +   @Resources.TopLevel @Model.TopLevelCategories +
+ View All + + Add a New Category + +
+
+
+
+
+ +
diff --git a/src/MyWebLog/Features/Admin/Settings.cshtml b/src/MyWebLog/Features/Admin/Settings.cshtml new file mode 100644 index 0000000..7ef5d76 --- /dev/null +++ b/src/MyWebLog/Features/Admin/Settings.cshtml @@ -0,0 +1,50 @@ +@model SettingsModel +@{ + Layout = "_AdminLayout"; + ViewBag.Title = "Web Log Settings"; +} +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ +
+
+
+
+
diff --git a/src/MyWebLog/Features/Admin/SettingsModel.cs b/src/MyWebLog/Features/Admin/SettingsModel.cs new file mode 100644 index 0000000..c1cfc7d --- /dev/null +++ b/src/MyWebLog/Features/Admin/SettingsModel.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using System.ComponentModel.DataAnnotations; + +namespace MyWebLog.Features.Admin; + +/// +/// View model for editing web log settings +/// +public class SettingsModel : MyWebLogModel +{ + /// + /// The name of the web log + /// + [Required(AllowEmptyStrings = false)] + [Display(ResourceType = typeof(Resources), Name = "Name")] + public string Name { get; set; } = ""; + + /// + /// The subtitle of the web log + /// + [Display(ResourceType = typeof(Resources), Name = "Subtitle")] + public string Subtitle { get; set; } = ""; + + /// + /// The default page + /// + [Required] + [Display(ResourceType = typeof(Resources), Name = "DefaultPage")] + public string DefaultPage { get; set; } = ""; + + /// + /// How many posts should appear on index pages + /// + [Required] + [Display(ResourceType = typeof(Resources), Name = "PostsPerPage")] + [Range(0, 50)] + public byte PostsPerPage { get; set; } = 10; + + /// + /// The time zone in which dates/times should be displayed + /// + [Required] + [Display(ResourceType = typeof(Resources), Name = "TimeZone")] + public string TimeZone { get; set; } = ""; + + /// + /// Possible values for the default page + /// + public IEnumerable DefaultPages { get; set; } = Enumerable.Empty(); + + [Obsolete("Only used for model binding; use the WebLogDetails constructor")] + public SettingsModel() : base(new()) { } + + /// + public SettingsModel(WebLogDetails webLog) : base(webLog) + { + Name = webLog.Name; + Subtitle = webLog.Subtitle ?? ""; + DefaultPage = webLog.DefaultPage; + PostsPerPage = webLog.PostsPerPage; + TimeZone = webLog.TimeZone; + } + + /// + /// Populate the settings object from the data in this form + /// + /// The settings to be updated + public void PopulateSettings(WebLogDetails settings) + { + settings.Name = Name; + settings.Subtitle = Subtitle == "" ? null : Subtitle; + settings.DefaultPage = DefaultPage; + settings.PostsPerPage = PostsPerPage; + settings.TimeZone = TimeZone; + } +} diff --git a/src/MyWebLog/Features/Posts/PostController.cs b/src/MyWebLog/Features/Posts/PostController.cs index 93c2b1f..90054f5 100644 --- a/src/MyWebLog/Features/Posts/PostController.cs +++ b/src/MyWebLog/Features/Posts/PostController.cs @@ -33,7 +33,6 @@ public class PostController : MyWebLogController [HttpGet("~/{*permalink}")] public async Task CatchAll(string permalink) { - Console.Write($"Got permalink |{permalink}|"); var post = await Db.Posts.FindByPermalink(permalink); if (post != null) { @@ -48,8 +47,9 @@ public class PostController : MyWebLogController // TOOD: search prior permalinks for posts and pages - await Task.CompletedTask; - throw new NotImplementedException(); + // We tried, we really tried... + Console.Write($"Returning 404 for permalink |{permalink}|"); + return NotFound(); } [HttpGet("all")] diff --git a/src/MyWebLog/Features/Shared/_AdminLayout.cshtml b/src/MyWebLog/Features/Shared/_AdminLayout.cshtml index 6eebde7..3b62f66 100644 --- a/src/MyWebLog/Features/Shared/_AdminLayout.cshtml +++ b/src/MyWebLog/Features/Shared/_AdminLayout.cshtml @@ -25,7 +25,6 @@
-

@ViewBag.Title

@* Each.Messages @Current.ToDisplay @EndEach *@ diff --git a/src/MyWebLog/Features/Users/LogOn.cshtml b/src/MyWebLog/Features/Users/LogOn.cshtml index 9a034e9..9c77857 100644 --- a/src/MyWebLog/Features/Users/LogOn.cshtml +++ b/src/MyWebLog/Features/Users/LogOn.cshtml @@ -3,7 +3,8 @@ Layout = "_AdminLayout"; ViewBag.Title = @Resources.LogOn; } -
+

@Resources.LogOnTo @Model.WebLog.Name

+
diff --git a/src/MyWebLog/Properties/Resources.Designer.cs b/src/MyWebLog/Properties/Resources.Designer.cs index 4448d9b..8396edf 100644 --- a/src/MyWebLog/Properties/Resources.Designer.cs +++ b/src/MyWebLog/Properties/Resources.Designer.cs @@ -69,6 +69,15 @@ namespace MyWebLog.Properties { } } + /// + /// Looks up a localized string similar to All. + /// + public static string All { + get { + return ResourceManager.GetString("All", resourceCulture); + } + } + /// /// Looks up a localized string similar to Categories. /// @@ -87,6 +96,24 @@ namespace MyWebLog.Properties { } } + /// + /// Looks up a localized string similar to Default Page. + /// + public static string DefaultPage { + get { + return ResourceManager.GetString("DefaultPage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Drafts. + /// + public static string Drafts { + get { + return ResourceManager.GetString("Drafts", resourceCulture); + } + } + /// /// Looks up a localized string similar to E-mail Address. /// @@ -96,6 +123,15 @@ namespace MyWebLog.Properties { } } + /// + /// Looks up a localized string similar to First Page of Posts. + /// + public static string FirstPageOfPosts { + get { + return ResourceManager.GetString("FirstPageOfPosts", resourceCulture); + } + } + /// /// Looks up a localized string similar to Log Off. /// @@ -114,6 +150,24 @@ namespace MyWebLog.Properties { } } + /// + /// Looks up a localized string similar to Log On to. + /// + public static string LogOnTo { + get { + return ResourceManager.GetString("LogOnTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string Name { + get { + return ResourceManager.GetString("Name", resourceCulture); + } + } + /// /// Looks up a localized string similar to Pages. /// @@ -141,6 +195,51 @@ namespace MyWebLog.Properties { } } + /// + /// Looks up a localized string similar to Posts per Page. + /// + public static string PostsPerPage { + get { + return ResourceManager.GetString("PostsPerPage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Published. + /// + public static string Published { + get { + return ResourceManager.GetString("Published", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save Changes. + /// + public static string SaveChanges { + get { + return ResourceManager.GetString("SaveChanges", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shown in Page List. + /// + public static string ShownInPageList { + get { + return ResourceManager.GetString("ShownInPageList", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Subtitle. + /// + public static string Subtitle { + get { + return ResourceManager.GetString("Subtitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to There are {0} categories. /// @@ -167,5 +266,23 @@ namespace MyWebLog.Properties { return ResourceManager.GetString("ThereAreXPublishedPostsAndYDrafts", resourceCulture); } } + + /// + /// Looks up a localized string similar to Time Zone. + /// + public static string TimeZone { + get { + return ResourceManager.GetString("TimeZone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Top Level. + /// + public static string TopLevel { + get { + return ResourceManager.GetString("TopLevel", resourceCulture); + } + } } } diff --git a/src/MyWebLog/Properties/Resources.resx b/src/MyWebLog/Properties/Resources.resx index 9c1bbeb..d56cd09 100644 --- a/src/MyWebLog/Properties/Resources.resx +++ b/src/MyWebLog/Properties/Resources.resx @@ -120,21 +120,39 @@ Admin + + All + Categories Dashboard + + Default Page + + + Drafts + E-mail Address + + First Page of Posts + Log Off Log On + + Log On to + + + Name + Pages @@ -144,6 +162,21 @@ Posts + + Posts per Page + + + Published + + + Save Changes + + + Shown in Page List + + + Subtitle + There are {0} categories @@ -153,4 +186,10 @@ There are {0} published posts and {1} drafts + + Time Zone + + + Top Level + \ No newline at end of file