From 8ec84e8680f8e330a6c32c6db21d1953690c8976 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 16 Dec 2023 17:59:33 -0500 Subject: [PATCH] WIP on module/member conversion View models complete --- src/MyWebLog.Domain/ViewModels.fs | 761 +++++++++++++++--------------- src/MyWebLog/Handlers/Admin.fs | 72 +-- src/MyWebLog/Handlers/Feed.fs | 10 +- src/MyWebLog/Handlers/Helpers.fs | 10 +- src/MyWebLog/Handlers/Page.fs | 20 +- src/MyWebLog/Handlers/Post.fs | 22 +- src/MyWebLog/Handlers/Upload.fs | 8 +- src/MyWebLog/Handlers/User.fs | 26 +- 8 files changed, 453 insertions(+), 476 deletions(-) diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs index d8ec0b4..db7f8d8 100644 --- a/src/MyWebLog.Domain/ViewModels.fs +++ b/src/MyWebLog.Domain/ViewModels.fs @@ -9,7 +9,7 @@ open NodaTime module private Helpers = /// Create a string option if a string is blank - let noneIfBlank (it : string) = + let noneIfBlank it = match (defaultArg (Option.ofObj it) "").Trim() with "" -> None | trimmed -> Some trimmed @@ -19,31 +19,31 @@ module PublicHelpers = /// If the web log is not being served from the domain root, add the path information to relative URLs in page and /// post text - let addBaseToRelativeUrls extra (text : string) = + let addBaseToRelativeUrls extra (text: string) = if extra = "" then text - else text.Replace("href=\"/", $"href=\"{extra}/").Replace ("src=\"/", $"src=\"{extra}/") + else text.Replace("href=\"/", $"href=\"{extra}/").Replace("src=\"/", $"src=\"{extra}/") /// The model used to display the admin dashboard [] type DashboardModel = { /// The number of published posts - Posts : int + Posts: int /// The number of post drafts - Drafts : int + Drafts: int /// The number of pages - Pages : int + Pages: int /// The number of pages in the page list - ListedPages : int + ListedPages: int /// The number of categories - Categories : int + Categories: int /// The top-level categories - TopLevelCategories : int + TopLevelCategories: int } @@ -51,22 +51,22 @@ type DashboardModel = { [] type DisplayCategory = { /// The ID of the category - Id : string + Id: string /// The slug for the category - Slug : string + Slug: string /// The name of the category - Name : string + Name: string /// A description of the category - Description : string option + Description: string option /// The parent category names for this (sub)category - ParentNames : string[] + ParentNames: string array /// The number of posts in this category - PostCount : int + PostCount: int } @@ -83,13 +83,10 @@ type DisplayCustomFeed = { /// Whether this custom feed is for a podcast IsPodcast: bool -} - -/// Support functions for custom feed displays -module DisplayCustomFeed = +} with /// Create a display version from a custom feed - let fromFeed (cats: DisplayCategory array) (feed: CustomFeed) : DisplayCustomFeed = + static member FromFeed (cats: DisplayCategory array) (feed: CustomFeed) = let source = match feed.Source with | Category (CategoryId catId) -> $"Category: {(cats |> Array.find (fun cat -> cat.Id = catId)).Name}" @@ -168,17 +165,14 @@ type DisplayRevision = { /// The format of the text of the revision Format: string -} - -/// Functions to support displaying revisions -module DisplayRevision = +} with /// Create a display revision from an actual revision - let fromRevision (webLog: WebLog) (rev : Revision) = - { AsOf = rev.AsOf.ToDateTimeUtc () - AsOfLocal = webLog.LocalTime rev.AsOf - Format = rev.Text.SourceType - } + static member FromRevision (webLog: WebLog) (rev : Revision) = { + AsOf = rev.AsOf.ToDateTimeUtc() + AsOfLocal = webLog.LocalTime rev.AsOf + Format = rev.Text.SourceType + } open System.IO @@ -203,20 +197,17 @@ type DisplayTheme = { /// Whether the theme .zip file exists on the filesystem IsOnDisk: bool -} - -/// Functions to support displaying themes -module DisplayTheme = +} with /// Create a display theme from a theme - let fromTheme inUseFunc (theme: Theme) = - { Id = string theme.Id - Name = theme.Name - Version = theme.Version - TemplateCount = List.length theme.Templates - IsInUse = inUseFunc theme.Id - IsOnDisk = File.Exists $"{theme.Id}-theme.zip" - } + static member FromTheme inUseFunc (theme: Theme) = { + Id = string theme.Id + Name = theme.Name + Version = theme.Version + TemplateCount = List.length theme.Templates + IsInUse = inUseFunc theme.Id + IsOnDisk = File.Exists $"{theme.Id}-theme.zip" + } /// Information about an uploaded file used for display @@ -236,13 +227,10 @@ type DisplayUpload = { /// The source for this file (created from UploadDestination DU) Source: string -} - -/// Functions to support displaying uploads -module DisplayUpload = +} with /// Create a display uploaded file - let fromUpload (webLog: WebLog) (source: UploadDestination) (upload: Upload) = + static member FromUpload (webLog: WebLog) (source: UploadDestination) (upload: Upload) = let path = string upload.Path let name = Path.GetFileName path { Id = string upload.Id @@ -282,13 +270,10 @@ type DisplayUser = { /// When the user last logged on LastSeenOn: Nullable -} - -/// Functions to support displaying a user's information -module DisplayUser = +} with /// Construct a displayed user from a web log user - let fromUser (webLog: WebLog) (user: WebLogUser) = { + static member FromUser (webLog: WebLog) (user: WebLogUser) = { Id = string user.Id Email = user.Email FirstName = user.FirstName @@ -321,119 +306,119 @@ type EditCategoryModel = { } with /// Create an edit model from an existing category - static member fromCategory (cat: Category) = - { CategoryId = string cat.Id - Name = cat.Name - Slug = cat.Slug - Description = defaultArg cat.Description "" - ParentId = cat.ParentId |> Option.map string |> Option.defaultValue "" - } + static member FromCategory (cat: Category) = { + CategoryId = string cat.Id + Name = cat.Name + Slug = cat.Slug + Description = defaultArg cat.Description "" + ParentId = cat.ParentId |> Option.map string |> Option.defaultValue "" + } /// Is this a new category? - member this.IsNew = this.CategoryId = "new" + member this.IsNew = + this.CategoryId = "new" /// View model to edit a custom RSS feed [] -type EditCustomFeedModel = - { /// The ID of the feed being editing - Id : string - - /// The type of source for this feed ("category" or "tag") - SourceType : string - - /// The category ID or tag on which this feed is based - SourceValue : string - - /// The relative path at which this feed is served - Path : string - - /// Whether this feed defines a podcast - IsPodcast : bool - - /// The title of the podcast - Title : string - - /// A subtitle for the podcast - Subtitle : string - - /// The number of items in the podcast feed - ItemsInFeed : int - - /// A summary of the podcast (iTunes field) - Summary : string - - /// The display name of the podcast author (iTunes field) - DisplayedAuthor : string - - /// The e-mail address of the user who registered the podcast at iTunes - Email : string - - /// The link to the image for the podcast - ImageUrl : string - - /// The category from Apple Podcasts (iTunes) under which this podcast is categorized - AppleCategory : string - - /// A further refinement of the categorization of this podcast (Apple Podcasts/iTunes field / values) - AppleSubcategory : string - - /// The explictness rating (iTunes field) - Explicit : string - - /// The default media type for files in this podcast - DefaultMediaType : string - - /// The base URL for relative URL media files for this podcast (optional; defaults to web log base) - MediaBaseUrl : string - - /// The URL for funding information for the podcast - FundingUrl : string - - /// The text for the funding link - FundingText : string - - /// A unique identifier to follow this podcast - PodcastGuid : string - - /// The medium for the content of this podcast - Medium : string - } +type EditCustomFeedModel = { + /// The ID of the feed being editing + Id: string + + /// The type of source for this feed ("category" or "tag") + SourceType: string + + /// The category ID or tag on which this feed is based + SourceValue: string + + /// The relative path at which this feed is served + Path: string + + /// Whether this feed defines a podcast + IsPodcast: bool + + /// The title of the podcast + Title: string + + /// A subtitle for the podcast + Subtitle: string + + /// The number of items in the podcast feed + ItemsInFeed: int + + /// A summary of the podcast (iTunes field) + Summary: string + + /// The display name of the podcast author (iTunes field) + DisplayedAuthor: string + + /// The e-mail address of the user who registered the podcast at iTunes + Email: string + + /// The link to the image for the podcast + ImageUrl: string + + /// The category from Apple Podcasts (iTunes) under which this podcast is categorized + AppleCategory: string + + /// A further refinement of the categorization of this podcast (Apple Podcasts/iTunes field / values) + AppleSubcategory: string + + /// The explictness rating (iTunes field) + Explicit: string + + /// The default media type for files in this podcast + DefaultMediaType: string + + /// The base URL for relative URL media files for this podcast (optional; defaults to web log base) + MediaBaseUrl: string + + /// The URL for funding information for the podcast + FundingUrl: string + + /// The text for the funding link + FundingText: string + + /// A unique identifier to follow this podcast + PodcastGuid: string + + /// The medium for the content of this podcast + Medium: string +} with /// An empty custom feed model - static member empty = - { Id = "" - SourceType = "category" - SourceValue = "" - Path = "" - IsPodcast = false - Title = "" - Subtitle = "" - ItemsInFeed = 25 - Summary = "" - DisplayedAuthor = "" - Email = "" - ImageUrl = "" - AppleCategory = "" - AppleSubcategory = "" - Explicit = "no" - DefaultMediaType = "audio/mpeg" - MediaBaseUrl = "" - FundingUrl = "" - FundingText = "" - PodcastGuid = "" - Medium = "" - } + static member Empty = { + Id = "" + SourceType = "category" + SourceValue = "" + Path = "" + IsPodcast = false + Title = "" + Subtitle = "" + ItemsInFeed = 25 + Summary = "" + DisplayedAuthor = "" + Email = "" + ImageUrl = "" + AppleCategory = "" + AppleSubcategory = "" + Explicit = "no" + DefaultMediaType = "audio/mpeg" + MediaBaseUrl = "" + FundingUrl = "" + FundingText = "" + PodcastGuid = "" + Medium = "" + } /// Create a model from a custom feed - static member fromFeed (feed: CustomFeed) = + static member FromFeed (feed: CustomFeed) = let rss = - { EditCustomFeedModel.empty with + { EditCustomFeedModel.Empty with Id = string feed.Id SourceType = match feed.Source with Category _ -> "category" | Tag _ -> "tag" SourceValue = match feed.Source with Category (CategoryId catId) -> catId | Tag tag -> tag - Path = string feed.Path - } + Path = string feed.Path } match feed.Podcast with | Some p -> { rss with @@ -453,12 +438,11 @@ type EditCustomFeedModel = FundingUrl = defaultArg p.FundingUrl "" FundingText = defaultArg p.FundingText "" PodcastGuid = p.PodcastGuid |> Option.map _.ToString().ToLowerInvariant() |> Option.defaultValue "" - Medium = p.Medium |> Option.map string |> Option.defaultValue "" - } + Medium = p.Medium |> Option.map string |> Option.defaultValue "" } | None -> rss /// Update a feed with values from this model - member this.UpdateFeed (feed : CustomFeed) = + member this.UpdateFeed (feed: CustomFeed) = { feed with Source = if this.SourceType = "tag" then Tag this.SourceValue else Category (CategoryId this.SourceValue) Path = Permalink this.Path @@ -480,40 +464,38 @@ type EditCustomFeedModel = PodcastGuid = noneIfBlank this.PodcastGuid |> Option.map Guid.Parse FundingUrl = noneIfBlank this.FundingUrl FundingText = noneIfBlank this.FundingText - Medium = noneIfBlank this.Medium |> Option.map PodcastMedium.Parse - } + Medium = noneIfBlank this.Medium |> Option.map PodcastMedium.Parse } else - None - } + None } /// View model for a user to edit their own information [] -type EditMyInfoModel = - { /// The user's first name - FirstName : string - - /// The user's last name - LastName : string - - /// The user's preferred name - PreferredName : string - - /// A new password for the user - NewPassword : string - - /// A new password for the user, confirmed - NewPasswordConfirm : string - } +type EditMyInfoModel = { + /// The user's first name + FirstName: string + + /// The user's last name + LastName: string + + /// The user's preferred name + PreferredName: string + + /// A new password for the user + NewPassword: string + + /// A new password for the user, confirmed + NewPasswordConfirm: string +} with /// Create an edit model from a user - static member fromUser (user : WebLogUser) = - { FirstName = user.FirstName - LastName = user.LastName - PreferredName = user.PreferredName - NewPassword = "" - NewPasswordConfirm = "" - } + static member FromUser (user: WebLogUser) = { + FirstName = user.FirstName + LastName = user.LastName + PreferredName = user.PreferredName + NewPassword = "" + NewPasswordConfirm = "" + } /// View model to edit a page @@ -548,7 +530,7 @@ type EditPageModel = { } with /// Create an edit model from an existing page - static member fromPage (page: Page) = + static member FromPage (page: Page) = let latest = match page.Revisions |> List.sortByDescending _.AsOf |> List.tryHead with | Some rev -> rev @@ -566,7 +548,8 @@ type EditPageModel = { } /// Whether this is a new page - member this.IsNew = this.PageId = "new" + member this.IsNew = + this.PageId = "new" /// Update a page with values from this model member this.UpdatePage (page: Page) now = @@ -588,12 +571,11 @@ type EditPageModel = { Metadata = Seq.zip this.MetaNames this.MetaValues |> Seq.filter (fun it -> fst it > "") |> Seq.map (fun it -> { Name = fst it; Value = snd it }) - |> Seq.sortBy (fun it -> $"{it.Name.ToLower ()} {it.Value.ToLower ()}") + |> Seq.sortBy (fun it -> $"{it.Name.ToLower()} {it.Value.ToLower()}") |> List.ofSeq Revisions = match page.Revisions |> List.tryHead with | Some r when r.Text = revision.Text -> page.Revisions - | _ -> revision :: page.Revisions - } + | _ -> revision :: page.Revisions } /// View model to edit a post @@ -700,7 +682,7 @@ type EditPostModel = { } with /// Create an edit model from an existing past - static member fromPost (webLog: WebLog) (post: Post) = + static member FromPost (webLog: WebLog) (post: Post) = let latest = match post.Revisions |> List.sortByDescending _.AsOf |> List.tryHead with | Some rev -> rev @@ -712,7 +694,7 @@ type EditPostModel = { Permalink = string post.Permalink Source = latest.Text.SourceType Text = latest.Text.Text - Tags = String.Join (", ", post.Tags) + Tags = String.Join(", ", post.Tags) Template = defaultArg post.Template "" CategoryIds = post.CategoryIds |> List.map string |> Array.ofList Status = string post.Status @@ -743,7 +725,8 @@ type EditPostModel = { } /// Whether this is a new post - member this.IsNew = this.PostId = "new" + member this.IsNew = + this.PostId = "new" /// Update a post with values from the submitted form member this.UpdatePost (post: Post) now = @@ -763,17 +746,17 @@ type EditPostModel = { Text = revision.Text.AsHtml() Tags = this.Tags.Split "," |> Seq.ofArray - |> Seq.map (fun it -> it.Trim().ToLower ()) + |> Seq.map _.Trim().ToLower() |> Seq.filter (fun it -> it <> "") |> Seq.sort |> List.ofSeq - Template = match this.Template.Trim () with "" -> None | tmpl -> Some tmpl + Template = match this.Template.Trim() with "" -> None | tmpl -> Some tmpl CategoryIds = this.CategoryIds |> Array.map CategoryId |> List.ofArray Status = if this.DoPublish then Published else post.Status Metadata = Seq.zip this.MetaNames this.MetaValues |> Seq.filter (fun it -> fst it > "") |> Seq.map (fun it -> { Name = fst it; Value = snd it }) - |> Seq.sortBy (fun it -> $"{it.Name.ToLower ()} {it.Value.ToLower ()}") + |> Seq.sortBy (fun it -> $"{it.Name.ToLower()} {it.Value.ToLower()}") |> List.ofSeq Revisions = match post.Revisions |> List.tryHead with | Some r when r.Text = revision.Text -> post.Revisions @@ -805,113 +788,111 @@ type EditPostModel = { EpisodeDescription = noneIfBlank this.EpisodeDescription } else - None - } + None } /// View model to add/edit a redirect rule [] -type EditRedirectRuleModel = - { /// The ID (index) of the rule being edited - RuleId : int +type EditRedirectRuleModel = { + /// The ID (index) of the rule being edited + RuleId: int - /// The "from" side of the rule - From : string + /// The "from" side of the rule + From: string - /// The "to" side of the rule - To : string + /// The "to" side of the rule + To: string - /// Whether this rule uses a regular expression - IsRegex : bool + /// Whether this rule uses a regular expression + IsRegex: bool - /// Whether a new rule should be inserted at the top or appended to the end (ignored for edits) - InsertAtTop : bool - } + /// Whether a new rule should be inserted at the top or appended to the end (ignored for edits) + InsertAtTop: bool +} with /// Create a model from an existing rule - static member fromRule idx (rule : RedirectRule) = - { RuleId = idx - From = rule.From - To = rule.To - IsRegex = rule.IsRegex - InsertAtTop = false - } + static member FromRule idx (rule: RedirectRule) = { + RuleId = idx + From = rule.From + To = rule.To + IsRegex = rule.IsRegex + InsertAtTop = false + } /// Update a rule with the values from this model - member this.UpdateRule (rule : RedirectRule) = - { rule with - From = this.From - To = this.To - IsRegex = this.IsRegex - } + member this.ToRule() = { + From = this.From + To = this.To + IsRegex = this.IsRegex + } /// View model to edit RSS settings [] -type EditRssModel = - { /// Whether the site feed of posts is enabled - IsFeedEnabled : bool - - /// The name of the file generated for the site feed - FeedName : string - - /// Override the "posts per page" setting for the site feed - ItemsInFeed : int - - /// Whether feeds are enabled for all categories - IsCategoryEnabled : bool - - /// Whether feeds are enabled for all tags - IsTagEnabled : bool - - /// A copyright string to be placed in all feeds - Copyright : string - } +type EditRssModel = { + /// Whether the site feed of posts is enabled + IsFeedEnabled: bool + + /// The name of the file generated for the site feed + FeedName: string + + /// Override the "posts per page" setting for the site feed + ItemsInFeed: int + + /// Whether feeds are enabled for all categories + IsCategoryEnabled: bool + + /// Whether feeds are enabled for all tags + IsTagEnabled: bool + + /// A copyright string to be placed in all feeds + Copyright: string +} with /// Create an edit model from a set of RSS options - static member fromRssOptions (rss : RssOptions) = - { IsFeedEnabled = rss.IsFeedEnabled - FeedName = rss.FeedName - ItemsInFeed = defaultArg rss.ItemsInFeed 0 - IsCategoryEnabled = rss.IsCategoryEnabled - IsTagEnabled = rss.IsTagEnabled - Copyright = defaultArg rss.Copyright "" - } + static member FromRssOptions (rss: RssOptions) = { + IsFeedEnabled = rss.IsFeedEnabled + FeedName = rss.FeedName + ItemsInFeed = defaultArg rss.ItemsInFeed 0 + IsCategoryEnabled = rss.IsCategoryEnabled + IsTagEnabled = rss.IsTagEnabled + Copyright = defaultArg rss.Copyright "" + } /// Update RSS options from values in this model - member this.UpdateOptions (rss : RssOptions) = + member this.UpdateOptions (rss: RssOptions) = { rss with IsFeedEnabled = this.IsFeedEnabled FeedName = this.FeedName ItemsInFeed = if this.ItemsInFeed = 0 then None else Some this.ItemsInFeed IsCategoryEnabled = this.IsCategoryEnabled IsTagEnabled = this.IsTagEnabled - Copyright = noneIfBlank this.Copyright - } + Copyright = noneIfBlank this.Copyright } /// View model to edit a tag mapping [] -type EditTagMapModel = - { /// The ID of the tag mapping being edited - Id : string - - /// The tag being mapped to a different link value - Tag : string - - /// The link value for the tag - UrlValue : string +type EditTagMapModel = { + /// The ID of the tag mapping being edited + Id: string + + /// The tag being mapped to a different link value + Tag: string + + /// The link value for the tag + UrlValue: string +} with + + /// Create an edit model from the tag mapping + static member FromMapping (tagMap: TagMap) : EditTagMapModel = { + Id = string tagMap.Id + Tag = tagMap.Tag + UrlValue = tagMap.UrlValue } /// Whether this is a new tag mapping - member this.IsNew = this.Id = "new" - - /// Create an edit model from the tag mapping - static member fromMapping (tagMap : TagMap) : EditTagMapModel = - { Id = string tagMap.Id - Tag = tagMap.Tag - UrlValue = tagMap.UrlValue - } + member this.IsNew = + this.Id = "new" /// View model to display a user's information @@ -945,21 +926,22 @@ type EditUserModel = { PasswordConfirm: string } with - /// Construct a displayed user from a web log user - static member fromUser (user: WebLogUser) = - { Id = string user.Id - AccessLevel = string user.AccessLevel - Url = defaultArg user.Url "" - Email = user.Email - FirstName = user.FirstName - LastName = user.LastName - PreferredName = user.PreferredName - Password = "" - PasswordConfirm = "" - } + /// Construct a user edit form from a web log user + static member FromUser (user: WebLogUser) = { + Id = string user.Id + AccessLevel = string user.AccessLevel + Url = defaultArg user.Url "" + Email = user.Email + FirstName = user.FirstName + LastName = user.LastName + PreferredName = user.PreferredName + Password = "" + PasswordConfirm = "" + } /// Is this a new user? - member this.IsNew = this.Id = "new" + member this.IsNew = + this.Id = "new" /// Update a user with values from this model (excludes password) member this.UpdateUser (user: WebLogUser) = @@ -969,25 +951,24 @@ type EditUserModel = { Url = noneIfBlank this.Url FirstName = this.FirstName LastName = this.LastName - PreferredName = this.PreferredName - } + PreferredName = this.PreferredName } /// The model to use to allow a user to log on [] -type LogOnModel = - { /// The user's e-mail address - EmailAddress : string +type LogOnModel = { + /// The user's e-mail address + EmailAddress : string + + /// The user's password + Password : string - /// The user's password - Password : string - - /// Where the user should be redirected once they have logged on - ReturnTo : string option - } + /// Where the user should be redirected once they have logged on + ReturnTo : string option +} with /// An empty log on model - static member empty = + static member Empty = { EmailAddress = ""; Password = ""; ReturnTo = None } @@ -1011,55 +992,55 @@ type ManagePermalinksModel = { } with /// Create a permalink model from a page - static member fromPage (pg: Page) = - { Id = string pg.Id - Entity = "page" - CurrentTitle = pg.Title - CurrentPermalink = string pg.Permalink - Prior = pg.PriorPermalinks |> List.map string |> Array.ofList - } + static member FromPage (page: Page) = { + Id = string page.Id + Entity = "page" + CurrentTitle = page.Title + CurrentPermalink = string page.Permalink + Prior = page.PriorPermalinks |> List.map string |> Array.ofList + } /// Create a permalink model from a post - static member fromPost (post: Post) = - { Id = string post.Id - Entity = "post" - CurrentTitle = post.Title - CurrentPermalink = string post.Permalink - Prior = post.PriorPermalinks |> List.map string |> Array.ofList - } + static member FromPost (post: Post) = { + Id = string post.Id + Entity = "post" + CurrentTitle = post.Title + CurrentPermalink = string post.Permalink + Prior = post.PriorPermalinks |> List.map string |> Array.ofList + } /// View model to manage revisions [] -type ManageRevisionsModel = - { /// The ID for the entity being edited - Id : string - - /// The type of entity being edited ("page" or "post") - Entity : string - - /// The current title of the page or post - CurrentTitle : string - - /// The revisions for the page or post - Revisions : DisplayRevision array - } +type ManageRevisionsModel = { + /// The ID for the entity being edited + Id: string + + /// The type of entity being edited ("page" or "post") + Entity: string + + /// The current title of the page or post + CurrentTitle: string + + /// The revisions for the page or post + Revisions: DisplayRevision array +} with /// Create a revision model from a page - static member fromPage webLog (pg: Page) = - { Id = string pg.Id - Entity = "page" - CurrentTitle = pg.Title - Revisions = pg.Revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList - } + static member FromPage webLog (page: Page) = { + Id = string page.Id + Entity = "page" + CurrentTitle = page.Title + Revisions = page.Revisions |> List.map (DisplayRevision.FromRevision webLog) |> Array.ofList + } /// Create a revision model from a post - static member fromPost webLog (post: Post) = - { Id = string post.Id - Entity = "post" - CurrentTitle = post.Title - Revisions = post.Revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList - } + static member FromPost webLog (post: Post) = { + Id = string post.Id + Entity = "post" + CurrentTitle = post.Title + Revisions = post.Revisions |> List.map (DisplayRevision.FromRevision webLog) |> Array.ofList + } /// View model for posts in a list @@ -1103,7 +1084,7 @@ type PostListItem = { } with /// Create a post list item from a post - static member fromPost (webLog: WebLog) (post: Post) = { + static member FromPost (webLog: WebLog) (post: Post) = { Id = string post.Id AuthorId = string post.AuthorId Status = string post.Status @@ -1120,28 +1101,28 @@ type PostListItem = { /// View model for displaying posts -type PostDisplay = - { /// The posts to be displayed - Posts : PostListItem[] - - /// Author ID -> name lookup - Authors : MetaItem list - - /// A subtitle for the page - Subtitle : string option - - /// The link to view newer (more recent) posts - NewerLink : string option - - /// The name of the next newer post (single-post only) - NewerName : string option - - /// The link to view older (less recent) posts - OlderLink : string option - - /// The name of the next older post (single-post only) - OlderName : string option - } +type PostDisplay = { + /// The posts to be displayed + Posts: PostListItem array + + /// Author ID -> name lookup + Authors: MetaItem list + + /// A subtitle for the page + Subtitle: string option + + /// The link to view newer (more recent) posts + NewerLink: string option + + /// The name of the next newer post (single-post only) + NewerName: string option + + /// The link to view older (less recent) posts + OlderLink: string option + + /// The name of the next older post (single-post only) + OlderName: string option +} /// View model for editing web log settings @@ -1176,20 +1157,20 @@ type SettingsModel = { } with /// Create a settings model from a web log - static member fromWebLog (webLog: WebLog) = - { Name = webLog.Name - Slug = webLog.Slug - Subtitle = defaultArg webLog.Subtitle "" - DefaultPage = webLog.DefaultPage - PostsPerPage = webLog.PostsPerPage - TimeZone = webLog.TimeZone - ThemeId = string webLog.ThemeId - AutoHtmx = webLog.AutoHtmx - Uploads = string webLog.Uploads - } + static member FromWebLog(webLog: WebLog) = { + Name = webLog.Name + Slug = webLog.Slug + Subtitle = defaultArg webLog.Subtitle "" + DefaultPage = webLog.DefaultPage + PostsPerPage = webLog.PostsPerPage + TimeZone = webLog.TimeZone + ThemeId = string webLog.ThemeId + AutoHtmx = webLog.AutoHtmx + Uploads = string webLog.Uploads + } /// Update a web log with settings from the form - member this.update (webLog : WebLog) = + member this.Update(webLog: WebLog) = { webLog with Name = this.Name Slug = this.Slug @@ -1199,53 +1180,49 @@ type SettingsModel = { TimeZone = this.TimeZone ThemeId = ThemeId this.ThemeId AutoHtmx = this.AutoHtmx - Uploads = UploadDestination.Parse this.Uploads - } + Uploads = UploadDestination.Parse this.Uploads } /// View model for uploading a file [] -type UploadFileModel = - { /// The upload destination - Destination : string - } +type UploadFileModel = { + /// The upload destination + Destination : string +} /// View model for uploading a theme [] -type UploadThemeModel = - { /// Whether the uploaded theme should overwrite an existing theme - DoOverwrite : bool - } +type UploadThemeModel = { + /// Whether the uploaded theme should overwrite an existing theme + DoOverwrite : bool +} /// A message displayed to the user [] -type UserMessage = - { /// The level of the message - Level : string - - /// The message - Message : string - - /// Further details about the message - Detail : string option - } - -/// Functions to support user messages -module UserMessage = +type UserMessage = { + /// The level of the message + Level: string + + /// The message + Message: string + + /// Further details about the message + Detail: string option +} with /// An empty user message (use one of the others for pre-filled level) - let empty = { Level = ""; Message = ""; Detail = None } + static member Empty = { Level = ""; Message = ""; Detail = None } /// A blank success message - let success = { empty with Level = "success" } + static member Success = { UserMessage.Empty with Level = "success" } /// A blank informational message - let info = { empty with Level = "primary" } + static member Info = { UserMessage.Empty with Level = "primary" } /// A blank warning message - let warning = { empty with Level = "warning" } + static member Warning = { UserMessage.Empty with Level = "warning" } /// A blank error message - let error = { empty with Level = "danger" } + static member Error = { UserMessage.Empty with Level = "danger" } diff --git a/src/MyWebLog/Handlers/Admin.fs b/src/MyWebLog/Handlers/Admin.fs index 6fd5d5f..bae83c0 100644 --- a/src/MyWebLog/Handlers/Admin.fs +++ b/src/MyWebLog/Handlers/Admin.fs @@ -44,7 +44,7 @@ module Dashboard = |> withAntiCsrf ctx |> addToHash "themes" ( themes - |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) + |> List.map (DisplayTheme.FromTheme WebLogCache.isThemeInUse) |> Array.ofList) |> addToHash "cached_themes" ( themes @@ -87,7 +87,7 @@ module Cache = do! PageListCache.refresh webLog data do! CategoryCache.refresh webLog.Id data do! addMessage ctx - { UserMessage.success with Message = "Successfully refresh web log cache for all web logs" } + { UserMessage.Success with Message = "Successfully refresh web log cache for all web logs" } else match! data.WebLog.FindById (WebLogId webLogId) with | Some webLog -> @@ -95,9 +95,9 @@ module Cache = do! PageListCache.refresh webLog data do! CategoryCache.refresh webLog.Id data do! addMessage ctx - { UserMessage.success with Message = $"Successfully refreshed web log cache for {webLog.Name}" } + { UserMessage.Success with Message = $"Successfully refreshed web log cache for {webLog.Name}" } | None -> - do! addMessage ctx { UserMessage.error with Message = $"No web log exists with ID {webLogId}" } + do! addMessage ctx { UserMessage.Error with Message = $"No web log exists with ID {webLogId}" } return! toAdminDashboard next ctx } @@ -108,7 +108,7 @@ module Cache = TemplateCache.empty () do! ThemeAssetCache.fill data do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = "Successfully cleared template cache and refreshed theme asset cache" } else @@ -117,11 +117,11 @@ module Cache = TemplateCache.invalidateTheme theme.Id do! ThemeAssetCache.refreshTheme theme.Id data do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = $"Successfully cleared template cache and refreshed theme asset cache for {theme.Name}" } | None -> - do! addMessage ctx { UserMessage.error with Message = $"No theme exists with ID {themeId}" } + do! addMessage ctx { UserMessage.Error with Message = $"No theme exists with ID {themeId}" } return! toAdminDashboard next ctx } @@ -167,7 +167,7 @@ module Category = return! hashForPage title |> withAntiCsrf ctx - |> addToHash ViewContext.Model (EditCategoryModel.fromCategory cat) + |> addToHash ViewContext.Model (EditCategoryModel.FromCategory cat) |> adminBareView "category-edit" next ctx | None -> return! Error.notFound next ctx } @@ -190,7 +190,7 @@ module Category = } do! (if model.IsNew then data.Category.Add else data.Category.Update) updatedCat do! CategoryCache.update ctx - do! addMessage ctx { UserMessage.success with Message = "Category saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Category saved successfully" } return! bare next ctx | None -> return! Error.notFound next ctx } @@ -207,9 +207,9 @@ module Category = | ReassignedChildCategories -> Some "(Its child categories were reassigned to its parent category)" | _ -> None - do! addMessage ctx { UserMessage.success with Message = "Category deleted successfully"; Detail = detail } + do! addMessage ctx { UserMessage.Success with Message = "Category deleted successfully"; Detail = detail } | CategoryNotFound -> - do! addMessage ctx { UserMessage.error with Message = "Category not found; cannot delete" } + do! addMessage ctx { UserMessage.Error with Message = "Category not found; cannot delete" } return! bare next ctx } @@ -233,7 +233,7 @@ module RedirectRules = if idx = -1 then return! hashForPage "Add Redirect Rule" - |> addToHash "model" (EditRedirectRuleModel.fromRule -1 RedirectRule.Empty) + |> addToHash "model" (EditRedirectRuleModel.FromRule -1 RedirectRule.Empty) |> withAntiCsrf ctx |> adminBareView "redirect-edit" next ctx else @@ -243,7 +243,7 @@ module RedirectRules = else return! hashForPage "Edit Redirect Rule" - |> addToHash "model" (EditRedirectRuleModel.fromRule idx (List.item idx rules)) + |> addToHash "model" (EditRedirectRuleModel.FromRule idx (List.item idx rules)) |> withAntiCsrf ctx |> adminBareView "redirect-edit" next ctx } @@ -257,17 +257,17 @@ module RedirectRules = // POST /admin/settings/redirect-rules/[index] let save idx : HttpHandler = fun next ctx -> task { - let! model = ctx.BindFormAsync () + let! model = ctx.BindFormAsync() let isNew = idx = -1 let rules = ctx.WebLog.RedirectRules - let rule = model.UpdateRule (if isNew then RedirectRule.Empty else List.item idx rules) + let rule = model.ToRule() let newRules = match isNew with | true when model.InsertAtTop -> List.insertAt 0 rule rules - | true -> List.insertAt (rules.Length) rule rules + | true -> List.insertAt rules.Length rule rules | false -> rules |> List.removeAt idx |> List.insertAt idx rule do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = newRules } - do! addMessage ctx { UserMessage.success with Message = "Redirect rule saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Redirect rule saved successfully" } return! all next ctx } @@ -300,7 +300,7 @@ module RedirectRules = else let rules = ctx.WebLog.RedirectRules |> List.removeAt idx do! updateRedirectRules ctx { ctx.WebLog with RedirectRules = rules } - do! addMessage ctx { UserMessage.success with Message = "Redirect rule deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Redirect rule deleted successfully" } return! all next ctx } @@ -340,7 +340,7 @@ module TagMapping = return! hashForPage (if isNew then "Add Tag Mapping" else $"Mapping for {tm.Tag} Tag") |> withAntiCsrf ctx - |> addToHash ViewContext.Model (EditTagMapModel.fromMapping tm) + |> addToHash ViewContext.Model (EditTagMapModel.FromMapping tm) |> adminBareView "tag-mapping-edit" next ctx | None -> return! Error.notFound next ctx } @@ -355,7 +355,7 @@ module TagMapping = match! tagMap with | Some tm -> do! data.TagMap.Save { tm with Tag = model.Tag.ToLower(); UrlValue = model.UrlValue.ToLower() } - do! addMessage ctx { UserMessage.success with Message = "Tag mapping saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Tag mapping saved successfully" } return! all next ctx | None -> return! Error.notFound next ctx } @@ -363,8 +363,8 @@ module TagMapping = // POST /admin/settings/tag-mapping/{id}/delete let delete tagMapId : HttpHandler = fun next ctx -> task { match! ctx.Data.TagMap.Delete (TagMapId tagMapId) ctx.WebLog.Id with - | true -> do! addMessage ctx { UserMessage.success with Message = "Tag mapping deleted successfully" } - | false -> do! addMessage ctx { UserMessage.error with Message = "Tag mapping not found; nothing deleted" } + | true -> do! addMessage ctx { UserMessage.Success with Message = "Tag mapping deleted successfully" } + | false -> do! addMessage ctx { UserMessage.Error with Message = "Tag mapping not found; nothing deleted" } return! all next ctx } @@ -384,7 +384,7 @@ module Theme = return! hashForPage "Themes" |> withAntiCsrf ctx - |> addToHash "themes" (themes |> List.map (DisplayTheme.fromTheme WebLogCache.isThemeInUse) |> Array.ofList) + |> addToHash "themes" (themes |> List.map (DisplayTheme.FromTheme WebLogCache.isThemeInUse) |> Array.ofList) |> adminBareView "theme-list-body" next ctx } @@ -488,21 +488,21 @@ module Theme = use file = new FileStream($"{themeId}-theme.zip", FileMode.Create) do! themeFile.CopyToAsync file do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = $"""Theme {if isNew then "add" else "updat"}ed successfully""" } return! toAdminDashboard next ctx else do! addMessage ctx - { UserMessage.error with + { UserMessage.Error with Message = "Theme exists and overwriting was not requested; nothing saved" } return! toAdminDashboard next ctx | Ok _ -> - do! addMessage ctx { UserMessage.error with Message = "You may not replace the admin theme" } + do! addMessage ctx { UserMessage.Error with Message = "You may not replace the admin theme" } return! toAdminDashboard next ctx | Error message -> - do! addMessage ctx { UserMessage.error with Message = message } + do! addMessage ctx { UserMessage.Error with Message = message } return! toAdminDashboard next ctx else return! RequestErrors.BAD_REQUEST "Bad request" next ctx } @@ -512,11 +512,11 @@ module Theme = let data = ctx.Data match themeId with | "admin" | "default" -> - do! addMessage ctx { UserMessage.error with Message = $"You may not delete the {themeId} theme" } + do! addMessage ctx { UserMessage.Error with Message = $"You may not delete the {themeId} theme" } return! all next ctx | it when WebLogCache.isThemeInUse (ThemeId it) -> do! addMessage ctx - { UserMessage.error with + { UserMessage.Error with Message = $"You may not delete the {themeId} theme, as it is currently in use" } return! all next ctx @@ -525,7 +525,7 @@ module Theme = | true -> let zippedTheme = $"{themeId}-theme.zip" if File.Exists zippedTheme then File.Delete zippedTheme - do! addMessage ctx { UserMessage.success with Message = $"Theme ID {themeId} deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = $"Theme ID {themeId} deleted successfully" } return! all next ctx | false -> return! Error.notFound next ctx } @@ -550,7 +550,7 @@ module WebLog = let! hash = hashForPage "Web Log Settings" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (SettingsModel.fromWebLog ctx.WebLog) + |> addToHash ViewContext.Model (SettingsModel.FromWebLog ctx.WebLog) |> addToHash "pages" ( seq { KeyValuePair.Create("posts", "- First Page of Posts -") @@ -569,11 +569,11 @@ module WebLog = KeyValuePair.Create(string Database, "Database") KeyValuePair.Create(string Disk, "Disk") |] - |> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList) - |> addToHash "rss_model" (EditRssModel.fromRssOptions ctx.WebLog.Rss) + |> addToHash "users" (users |> List.map (DisplayUser.FromUser ctx.WebLog) |> Array.ofList) + |> addToHash "rss_model" (EditRssModel.FromRssOptions ctx.WebLog.Rss) |> addToHash "custom_feeds" ( ctx.WebLog.Rss.CustomFeeds - |> List.map (DisplayCustomFeed.fromFeed (CategoryCache.get ctx)) + |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx)) |> Array.ofList) |> addViewContext ctx let! hash' = TagMapping.withTagMappings ctx hash @@ -592,7 +592,7 @@ module WebLog = match! data.WebLog.FindById ctx.WebLog.Id with | Some webLog -> let oldSlug = webLog.Slug - let webLog = model.update webLog + let webLog = model.Update webLog do! data.WebLog.UpdateSettings webLog // Update cache @@ -604,7 +604,7 @@ module WebLog = let oldDir = Path.Combine (uploadRoot, oldSlug) if Directory.Exists oldDir then Directory.Move (oldDir, Path.Combine (uploadRoot, webLog.Slug)) - do! addMessage ctx { UserMessage.success with Message = "Web log settings saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Web log settings saved successfully" } return! redirectToGet "admin/settings" next ctx | None -> return! Error.notFound next ctx } diff --git a/src/MyWebLog/Handlers/Feed.fs b/src/MyWebLog/Handlers/Feed.fs index 996ae5e..35b668b 100644 --- a/src/MyWebLog/Handlers/Feed.fs +++ b/src/MyWebLog/Handlers/Feed.fs @@ -418,7 +418,7 @@ let saveSettings : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> t let webLog = { webLog with Rss = model.UpdateOptions webLog.Rss } do! data.WebLog.UpdateRssOptions webLog WebLogCache.set webLog - do! addMessage ctx { UserMessage.success with Message = "RSS settings updated successfully" } + do! addMessage ctx { UserMessage.Success with Message = "RSS settings updated successfully" } return! redirectToGet "admin/settings#rss-settings" next ctx | None -> return! Error.notFound next ctx } @@ -433,7 +433,7 @@ let editCustomFeed feedId : HttpHandler = requireAccess WebLogAdmin >=> fun next | Some f -> hashForPage $"""{if feedId = "new" then "Add" else "Edit"} Custom RSS Feed""" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (EditCustomFeedModel.fromFeed f) + |> addToHash ViewContext.Model (EditCustomFeedModel.FromFeed f) |> addToHash "medium_values" [| KeyValuePair.Create("", "– Unspecified –") KeyValuePair.Create(string Podcast, "Podcast") @@ -464,7 +464,7 @@ let saveCustomFeed : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> do! data.WebLog.UpdateRssOptions webLog WebLogCache.set webLog do! addMessage ctx { - UserMessage.success with + UserMessage.Success with Message = $"""Successfully {if model.Id = "new" then "add" else "sav"}ed custom feed""" } return! redirectToGet $"admin/settings/rss/{feed.Id}/edit" next ctx @@ -488,9 +488,9 @@ let deleteCustomFeed feedId : HttpHandler = requireAccess WebLogAdmin >=> fun ne } do! data.WebLog.UpdateRssOptions webLog WebLogCache.set webLog - do! addMessage ctx { UserMessage.success with Message = "Custom feed deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Custom feed deleted successfully" } else - do! addMessage ctx { UserMessage.warning with Message = "Custom feed not found; no action taken" } + do! addMessage ctx { UserMessage.Warning with Message = "Custom feed not found; no action taken" } return! redirectToGet "admin/settings#rss-settings" next ctx | None -> return! Error.notFound next ctx } diff --git a/src/MyWebLog/Handlers/Helpers.fs b/src/MyWebLog/Handlers/Helpers.fs index 43ccc11..07041af 100644 --- a/src/MyWebLog/Handlers/Helpers.fs +++ b/src/MyWebLog/Handlers/Helpers.fs @@ -252,7 +252,7 @@ module Error = else if isHtmx ctx then let messages = [| - { UserMessage.error with + { UserMessage.Error with Message = $"You are not authorized to access the URL {ctx.Request.Path.Value}" } |] @@ -264,7 +264,7 @@ module Error = handleContext (fun ctx -> if isHtmx ctx then let messages = [| - { UserMessage.error with Message = $"The URL {ctx.Request.Path.Value} was not found" } + { UserMessage.Error with Message = $"The URL {ctx.Request.Path.Value} was not found" } |] RequestErrors.notFound (messagesToHeaders messages) earlyReturn ctx else RequestErrors.NOT_FOUND "Not found" earlyReturn ctx) @@ -272,7 +272,7 @@ module Error = let server message : HttpHandler = handleContext (fun ctx -> if isHtmx ctx then - let messages = [| { UserMessage.error with Message = message } |] + let messages = [| { UserMessage.Error with Message = message } |] ServerErrors.internalError (messagesToHeaders messages) earlyReturn ctx else ServerErrors.INTERNAL_ERROR message earlyReturn ctx) @@ -351,14 +351,14 @@ let requireAccess level : HttpHandler = fun next ctx -> task { | Some userLevel when userLevel.HasAccess level -> return! next ctx | Some userLevel -> do! addMessage ctx - { UserMessage.warning with + { UserMessage.Warning with Message = $"The page you tried to access requires {level} privileges" Detail = Some $"Your account only has {userLevel} privileges" } return! Error.notAuthorized next ctx | None -> do! addMessage ctx - { UserMessage.warning with Message = "The page you tried to access required you to be logged on" } + { UserMessage.Warning with Message = "The page you tried to access required you to be logged on" } return! Error.notAuthorized next ctx } diff --git a/src/MyWebLog/Handlers/Page.fs b/src/MyWebLog/Handlers/Page.fs index 099c7b3..9609427 100644 --- a/src/MyWebLog/Handlers/Page.fs +++ b/src/MyWebLog/Handlers/Page.fs @@ -36,7 +36,7 @@ let edit pgId : HttpHandler = requireAccess Author >=> fun next ctx -> task { } match result with | Some (title, page) when canEdit page.AuthorId ctx -> - let model = EditPageModel.fromPage page + let model = EditPageModel.FromPage page let! templates = templatesForTheme ctx "page" return! hashForPage title @@ -56,8 +56,8 @@ let delete pgId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> ta match! ctx.Data.Page.Delete (PageId pgId) ctx.WebLog.Id with | true -> do! PageListCache.update ctx - do! addMessage ctx { UserMessage.success with Message = "Page deleted successfully" } - | false -> do! addMessage ctx { UserMessage.error with Message = "Page not found; nothing deleted" } + do! addMessage ctx { UserMessage.Success with Message = "Page deleted successfully" } + | false -> do! addMessage ctx { UserMessage.Error with Message = "Page not found; nothing deleted" } return! redirectToGet "admin/pages" next ctx } @@ -68,7 +68,7 @@ let editPermalinks pgId : HttpHandler = requireAccess Author >=> fun next ctx -> return! hashForPage "Manage Prior Permalinks" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (ManagePermalinksModel.fromPage pg) + |> addToHash ViewContext.Model (ManagePermalinksModel.FromPage pg) |> adminView "permalinks" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx @@ -83,7 +83,7 @@ let savePermalinks : HttpHandler = requireAccess Author >=> fun next ctx -> task let links = model.Prior |> Array.map Permalink |> List.ofArray match! ctx.Data.Page.UpdatePriorPermalinks pageId ctx.WebLog.Id links with | true -> - do! addMessage ctx { UserMessage.success with Message = "Page permalinks saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Page permalinks saved successfully" } return! redirectToGet $"admin/page/{model.Id}/permalinks" next ctx | false -> return! Error.notFound next ctx | Some _ -> return! Error.notAuthorized next ctx @@ -97,7 +97,7 @@ let editRevisions pgId : HttpHandler = requireAccess Author >=> fun next ctx -> return! hashForPage "Manage Page Revisions" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (ManageRevisionsModel.fromPage ctx.WebLog pg) + |> addToHash ViewContext.Model (ManageRevisionsModel.FromPage ctx.WebLog pg) |> adminView "revisions" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx @@ -109,7 +109,7 @@ let purgeRevisions pgId : HttpHandler = requireAccess Author >=> fun next ctx -> match! data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with | Some pg -> do! data.Page.Update { pg with Revisions = [ List.head pg.Revisions ] } - do! addMessage ctx { UserMessage.success with Message = "Prior revisions purged successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Prior revisions purged successfully" } return! redirectToGet $"admin/page/{pgId}/revisions" next ctx | None -> return! Error.notFound next ctx } @@ -152,7 +152,7 @@ let restoreRevision (pgId, revDate) : HttpHandler = requireAccess Author >=> fun Revisions = { rev with AsOf = Noda.now () } :: (pg.Revisions |> List.filter (fun r -> r.AsOf <> rev.AsOf)) } - do! addMessage ctx { UserMessage.success with Message = "Revision restored successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Revision restored successfully" } return! redirectToGet $"admin/page/{pgId}/revisions" next ctx | Some _, Some _ -> return! Error.notAuthorized next ctx | None, _ @@ -164,7 +164,7 @@ let deleteRevision (pgId, revDate) : HttpHandler = requireAccess Author >=> fun match! findPageRevision pgId revDate ctx with | Some pg, Some rev when canEdit pg.AuthorId ctx -> do! ctx.Data.Page.Update { pg with Revisions = pg.Revisions |> List.filter (fun r -> r.AsOf <> rev.AsOf) } - do! addMessage ctx { UserMessage.success with Message = "Revision deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Revision deleted successfully" } return! adminBareView "" next ctx (makeHash {| content = "" |}) | Some _, Some _ -> return! Error.notAuthorized next ctx | None, _ @@ -191,7 +191,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { let updatedPage = model.UpdatePage page now do! (if model.IsNew then data.Page.Add else data.Page.Update) updatedPage if updateList then do! PageListCache.update ctx - do! addMessage ctx { UserMessage.success with Message = "Page saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Page saved successfully" } return! redirectToGet $"admin/page/{page.Id}/edit" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx diff --git a/src/MyWebLog/Handlers/Post.fs b/src/MyWebLog/Handlers/Post.fs index 161b441..780cac5 100644 --- a/src/MyWebLog/Handlers/Post.fs +++ b/src/MyWebLog/Handlers/Post.fs @@ -47,7 +47,7 @@ let preparePostList webLog posts listType (url: string) pageNbr perPage (data: I posts |> Seq.ofList |> Seq.truncate perPage - |> Seq.map (PostListItem.fromPost webLog) + |> Seq.map (PostListItem.FromPost webLog) |> Array.ofSeq let! olderPost, newerPost = match listType with @@ -232,7 +232,7 @@ let edit postId : HttpHandler = requireAccess Author >=> fun next ctx -> task { match result with | Some (title, post) when canEdit post.AuthorId ctx -> let! templates = templatesForTheme ctx "post" - let model = EditPostModel.fromPost ctx.WebLog post + let model = EditPostModel.FromPost ctx.WebLog post return! hashForPage title |> withAntiCsrf ctx @@ -255,8 +255,8 @@ let edit postId : HttpHandler = requireAccess Author >=> fun next ctx -> task { // POST /admin/post/{id}/delete let delete postId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { match! ctx.Data.Post.Delete (PostId postId) ctx.WebLog.Id with - | true -> do! addMessage ctx { UserMessage.success with Message = "Post deleted successfully" } - | false -> do! addMessage ctx { UserMessage.error with Message = "Post not found; nothing deleted" } + | true -> do! addMessage ctx { UserMessage.Success with Message = "Post deleted successfully" } + | false -> do! addMessage ctx { UserMessage.Error with Message = "Post not found; nothing deleted" } return! redirectToGet "admin/posts" next ctx } @@ -267,7 +267,7 @@ let editPermalinks postId : HttpHandler = requireAccess Author >=> fun next ctx return! hashForPage "Manage Prior Permalinks" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (ManagePermalinksModel.fromPost post) + |> addToHash ViewContext.Model (ManagePermalinksModel.FromPost post) |> adminView "permalinks" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx @@ -282,7 +282,7 @@ let savePermalinks : HttpHandler = requireAccess Author >=> fun next ctx -> task let links = model.Prior |> Array.map Permalink |> List.ofArray match! ctx.Data.Post.UpdatePriorPermalinks postId ctx.WebLog.Id links with | true -> - do! addMessage ctx { UserMessage.success with Message = "Post permalinks saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Post permalinks saved successfully" } return! redirectToGet $"admin/post/{model.Id}/permalinks" next ctx | false -> return! Error.notFound next ctx | Some _ -> return! Error.notAuthorized next ctx @@ -296,7 +296,7 @@ let editRevisions postId : HttpHandler = requireAccess Author >=> fun next ctx - return! hashForPage "Manage Post Revisions" |> withAntiCsrf ctx - |> addToHash ViewContext.Model (ManageRevisionsModel.fromPost ctx.WebLog post) + |> addToHash ViewContext.Model (ManageRevisionsModel.FromPost ctx.WebLog post) |> adminView "revisions" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx @@ -308,7 +308,7 @@ let purgeRevisions postId : HttpHandler = requireAccess Author >=> fun next ctx match! data.Post.FindFullById (PostId postId) ctx.WebLog.Id with | Some post when canEdit post.AuthorId ctx -> do! data.Post.Update { post with Revisions = [ List.head post.Revisions ] } - do! addMessage ctx { UserMessage.success with Message = "Prior revisions purged successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Prior revisions purged successfully" } return! redirectToGet $"admin/post/{postId}/revisions" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx @@ -352,7 +352,7 @@ let restoreRevision (postId, revDate) : HttpHandler = requireAccess Author >=> f Revisions = { rev with AsOf = Noda.now () } :: (post.Revisions |> List.filter (fun r -> r.AsOf <> rev.AsOf)) } - do! addMessage ctx { UserMessage.success with Message = "Revision restored successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Revision restored successfully" } return! redirectToGet $"admin/post/{postId}/revisions" next ctx | Some _, Some _ -> return! Error.notAuthorized next ctx | None, _ @@ -364,7 +364,7 @@ let deleteRevision (postId, revDate) : HttpHandler = requireAccess Author >=> fu match! findPostRevision postId revDate ctx with | Some post, Some rev when canEdit post.AuthorId ctx -> do! ctx.Data.Post.Update { post with Revisions = post.Revisions |> List.filter (fun r -> r.AsOf <> rev.AsOf) } - do! addMessage ctx { UserMessage.success with Message = "Revision deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Revision deleted successfully" } return! adminBareView "" next ctx (makeHash {| content = "" |}) | Some _, Some _ -> return! Error.notAuthorized next ctx | None, _ @@ -408,7 +408,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { |> List.distinct |> List.length = List.length priorCats) then do! CategoryCache.update ctx - do! addMessage ctx { UserMessage.success with Message = "Post saved successfully" } + do! addMessage ctx { UserMessage.Success with Message = "Post saved successfully" } return! redirectToGet $"admin/post/{post.Id}/edit" next ctx | Some _ -> return! Error.notAuthorized next ctx | None -> return! Error.notFound next ctx diff --git a/src/MyWebLog/Handlers/Upload.fs b/src/MyWebLog/Handlers/Upload.fs index 6e1b33a..3493bf6 100644 --- a/src/MyWebLog/Handlers/Upload.fs +++ b/src/MyWebLog/Handlers/Upload.fs @@ -117,7 +117,7 @@ let list : HttpHandler = requireAccess Author >=> fun next ctx -> task { [] let allFiles = dbUploads - |> List.map (DisplayUpload.fromUpload webLog Database) + |> List.map (DisplayUpload.FromUpload webLog Database) |> List.append diskUploads |> List.sortByDescending (fun file -> file.UpdatedOn, file.Path) return! @@ -169,7 +169,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { use stream = new FileStream(Path.Combine(fullPath, fileName), FileMode.Create) do! upload.CopyToAsync stream - do! addMessage ctx { UserMessage.success with Message = $"File uploaded to {form.Destination} successfully" } + do! addMessage ctx { UserMessage.Success with Message = $"File uploaded to {form.Destination} successfully" } return! showUploads next ctx else return! RequestErrors.BAD_REQUEST "Bad request; no file present" next ctx @@ -179,7 +179,7 @@ let save : HttpHandler = requireAccess Author >=> fun next ctx -> task { let deleteFromDb upId : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { match! ctx.Data.Upload.Delete (UploadId upId) ctx.WebLog.Id with | Ok fileName -> - do! addMessage ctx { UserMessage.success with Message = $"{fileName} deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = $"{fileName} deleted successfully" } return! showUploads next ctx | Error _ -> return! Error.notFound next ctx } @@ -202,7 +202,7 @@ let deleteFromDisk urlParts : HttpHandler = requireAccess WebLogAdmin >=> fun ne if File.Exists path then File.Delete path removeEmptyDirectories ctx.WebLog filePath - do! addMessage ctx { UserMessage.success with Message = $"{filePath} deleted successfully" } + do! addMessage ctx { UserMessage.Success with Message = $"{filePath} deleted successfully" } return! showUploads next ctx else return! Error.notFound next ctx } diff --git a/src/MyWebLog/Handlers/User.fs b/src/MyWebLog/Handlers/User.fs index aa518c4..9f19efe 100644 --- a/src/MyWebLog/Handlers/User.fs +++ b/src/MyWebLog/Handlers/User.fs @@ -38,7 +38,7 @@ let logOn returnUrl : HttpHandler = fun next ctx -> | None -> if ctx.Request.Query.ContainsKey "returnUrl" then Some ctx.Request.Query["returnUrl"].[0] else None hashForPage "Log On" |> withAntiCsrf ctx - |> addToHash ViewContext.Model { LogOnModel.empty with ReturnTo = returnTo } + |> addToHash ViewContext.Model { LogOnModel.Empty with ReturnTo = returnTo } |> adminView "log-on" next ctx @@ -66,7 +66,7 @@ let doLogOn : HttpHandler = fun next ctx -> task { AuthenticationProperties(IssuedUtc = DateTimeOffset.UtcNow)) do! data.WebLogUser.SetLastSeen user.Id user.WebLogId do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = "Log on successful" Detail = Some $"Welcome to {ctx.WebLog.Name}!" } @@ -75,14 +75,14 @@ let doLogOn : HttpHandler = fun next ctx -> task { | Some url -> redirectTo false url next ctx | None -> redirectToGet "admin/dashboard" next ctx | Error msg -> - do! addMessage ctx { UserMessage.error with Message = msg } + do! addMessage ctx { UserMessage.Error with Message = msg } return! logOn model.ReturnTo next ctx } // GET /user/log-off let logOff : HttpHandler = fun next ctx -> task { do! ctx.SignOutAsync CookieAuthenticationDefaults.AuthenticationScheme - do! addMessage ctx { UserMessage.info with Message = "Log off successful" } + do! addMessage ctx { UserMessage.Info with Message = "Log off successful" } return! redirectToGet "" next ctx } @@ -100,7 +100,7 @@ let all : HttpHandler = fun next ctx -> task { return! hashForPage "User Administration" |> withAntiCsrf ctx - |> addToHash "users" (users |> List.map (DisplayUser.fromUser ctx.WebLog) |> Array.ofList) + |> addToHash "users" (users |> List.map (DisplayUser.FromUser ctx.WebLog) |> Array.ofList) |> adminBareView "user-list-body" next ctx } @@ -125,7 +125,7 @@ let edit usrId : HttpHandler = fun next ctx -> task { if isNew then someTask { WebLogUser.Empty with Id = userId } else ctx.Data.WebLogUser.FindById userId ctx.WebLog.Id match! tryUser with - | Some user -> return! showEdit (EditUserModel.fromUser user) next ctx + | Some user -> return! showEdit (EditUserModel.FromUser user) next ctx | None -> return! Error.notFound next ctx } @@ -140,13 +140,13 @@ let delete userId : HttpHandler = fun next ctx -> task { match! data.WebLogUser.Delete user.Id user.WebLogId with | Ok _ -> do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = $"User {user.DisplayName} deleted successfully" } return! all next ctx | Error msg -> do! addMessage ctx - { UserMessage.error with + { UserMessage.Error with Message = $"User {user.DisplayName} was not deleted" Detail = Some msg } @@ -168,7 +168,7 @@ let private showMyInfo (model: EditMyInfoModel) (user: WebLogUser) : HttpHandler // GET /admin/my-info let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with - | Some user -> return! showMyInfo (EditMyInfoModel.fromUser user) user next ctx + | Some user -> return! showMyInfo (EditMyInfoModel.FromUser user) user next ctx | None -> return! Error.notFound next ctx } @@ -188,10 +188,10 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task { } do! data.WebLogUser.Update user let pwMsg = if model.NewPassword = "" then "" else " and updated your password" - do! addMessage ctx { UserMessage.success with Message = $"Saved your information{pwMsg} successfully" } + do! addMessage ctx { UserMessage.Success with Message = $"Saved your information{pwMsg} successfully" } return! redirectToGet "admin/my-info" next ctx | Some user -> - do! addMessage ctx { UserMessage.error with Message = "Passwords did not match; no updates made" } + do! addMessage ctx { UserMessage.Error with Message = "Passwords did not match; no updates made" } return! showMyInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user next ctx | None -> return! Error.notFound next ctx } @@ -222,12 +222,12 @@ let save : HttpHandler = requireAccess WebLogAdmin >=> fun next ctx -> task { else { updatedUser with PasswordHash = createPasswordHash updatedUser model.Password } do! (if model.IsNew then data.WebLogUser.Add else data.WebLogUser.Update) toUpdate do! addMessage ctx - { UserMessage.success with + { UserMessage.Success with Message = $"""{if model.IsNew then "Add" else "Updat"}ed user successfully""" } return! all next ctx | Some _ -> - do! addMessage ctx { UserMessage.error with Message = "The passwords did not match; nothing saved" } + do! addMessage ctx { UserMessage.Error with Message = "The passwords did not match; nothing saved" } return! (withHxRetarget $"#user_{model.Id}" >=> showEdit { model with Password = ""; PasswordConfirm = "" }) next ctx