From fa20122f200d774eeb788ed7cbaac2444c1ff170 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 24 Apr 2022 23:21:22 -0400 Subject: [PATCH] Add meta item - Use meta item for category/author lookups - Add Liquid filter to get values from meta item lists - Implement log on "returnUrl" parameter --- src/MyWebLog.Data/Data.fs | 8 ++--- src/MyWebLog.Domain/SupportTypes.fs | 11 ++++++ src/MyWebLog.Domain/ViewModels.fs | 24 ++++++++----- src/MyWebLog/Handlers.fs | 40 +++++++++++++++------- src/MyWebLog/Program.fs | 18 +++++++--- src/MyWebLog/themes/admin/log-on.liquid | 3 ++ src/MyWebLog/themes/admin/post-list.liquid | 2 +- src/MyWebLog/themes/default/index.liquid | 22 +++++++++++- 8 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/MyWebLog.Data/Data.fs b/src/MyWebLog.Data/Data.fs index d7d2746..efdb6e6 100644 --- a/src/MyWebLog.Data/Data.fs +++ b/src/MyWebLog.Data/Data.fs @@ -204,14 +204,14 @@ module Category = } /// Get a category ID -> name dictionary for the given category IDs - let findNames (catIds : CategoryId list) (webLogId : WebLogId) conn = backgroundTask { + let findNames (webLogId : WebLogId) conn (catIds : CategoryId list) = backgroundTask { let! cats = rethink { withTable Table.Category getAll (catIds |> List.map (fun it -> it :> obj)) filter "webLogId" webLogId result; withRetryDefault conn } - return cats |> List.map (fun c -> CategoryId.toString c.id, c.name) |> dict + return cats |> List.map (fun c -> { name = CategoryId.toString c.id; value = c.name}) } /// Update a category @@ -504,12 +504,12 @@ module WebLogUser = |> tryFirst /// Get a user ID -> name dictionary for the given user IDs - let findNames (userIds : WebLogUserId list) (webLogId : WebLogId) conn = backgroundTask { + let findNames (webLogId : WebLogId) conn (userIds : WebLogUserId list) = backgroundTask { let! users = rethink { withTable Table.WebLogUser getAll (userIds |> List.map (fun it -> it :> obj)) filter "webLogId" webLogId result; withRetryDefault conn } - return users |> List.map (fun u -> WebLogUserId.toString u.id, WebLogUser.displayName u) |> dict + return users |> List.map (fun u -> { name = WebLogUserId.toString u.id; value = WebLogUser.displayName u }) } diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs index 6476c3b..f36f65a 100644 --- a/src/MyWebLog.Domain/SupportTypes.fs +++ b/src/MyWebLog.Domain/SupportTypes.fs @@ -88,6 +88,17 @@ module MarkupText = | text -> invalidOp $"Cannot derive type of text ({text})" +/// An item of metadata +[] +type MetaItem = + { /// The name of the metadata value + name : string + + /// The metadata value + value : string + } + + /// A revision of a page or post [] type Revision = diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs index f318ad8..cee7e04 100644 --- a/src/MyWebLog.Domain/ViewModels.fs +++ b/src/MyWebLog.Domain/ViewModels.fs @@ -209,7 +209,14 @@ type LogOnModel = /// The user's password password : string + + /// Where the user should be redirected once they have logged on + returnTo : string option } + + /// An empty log on model + static member empty = + { emailAddress = ""; password = ""; returnTo = None } /// View model for posts in a list @@ -221,9 +228,6 @@ type PostListItem = /// The ID of the user who authored the post authorId : string - /// The name of the user who authored the post - authorName : string - /// The status of the post status : string @@ -243,25 +247,24 @@ type PostListItem = text : string /// The IDs of the categories for this post - categoryIds : string[] + categoryIds : string list /// Tags for the post - tags : string[] + tags : string list } /// Create a post list item from a post static member fromPost (post : Post) = { id = PostId.toString post.id authorId = WebLogUserId.toString post.authorId - authorName = "" status = PostStatus.toString post.status title = post.title permalink = Permalink.toString post.permalink publishedOn = Option.toNullable post.publishedOn updatedOn = post.updatedOn text = post.text - categoryIds = post.categoryIds |> List.map CategoryId.toString |> Array.ofList - tags = Array.ofList post.tags + categoryIds = post.categoryIds |> List.map CategoryId.toString + tags = post.tags } @@ -270,8 +273,11 @@ type PostDisplay = { /// The posts to be displayed posts : PostListItem[] + /// Author ID -> name lookup + authors : MetaItem list + /// Category ID -> name lookup - categories : IDictionary + categories : MetaItem list /// A subtitle for the page subtitle : string option diff --git a/src/MyWebLog/Handlers.fs b/src/MyWebLog/Handlers.fs index 80a9b57..c8d5276 100644 --- a/src/MyWebLog/Handlers.fs +++ b/src/MyWebLog/Handlers.fs @@ -424,19 +424,25 @@ module Post = /// Convert a list of posts into items ready to be displayed let private preparePostList (webLog : WebLog) (posts : Post list) pageNbr perPage conn = task { let! authors = - Data.WebLogUser.findNames (posts |> List.map (fun p -> p.authorId) |> List.distinct) webLog.id conn + posts + |> List.map (fun p -> p.authorId) + |> List.distinct + |> Data.WebLogUser.findNames webLog.id conn let! cats = - Data.Category.findNames (posts |> List.map (fun c -> c.categoryIds) |> List.concat |> List.distinct) - webLog.id conn + posts + |> List.map (fun c -> c.categoryIds) + |> List.concat + |> List.distinct + |> Data.Category.findNames webLog.id conn let postItems = posts |> Seq.ofList |> Seq.truncate perPage |> Seq.map PostListItem.fromPost - |> Seq.map (fun pi -> { pi with authorName = authors[pi.authorId] }) |> Array.ofSeq let model = { posts = postItems + authors = authors categories = cats subtitle = None hasNewer = pageNbr <> 1 @@ -606,9 +612,20 @@ module User = Convert.ToBase64String (alg.GetBytes 64) // GET /user/log-on - let logOn : HttpHandler = fun next ctx -> task { + let logOn returnUrl : HttpHandler = fun next ctx -> task { + let returnTo = + match returnUrl with + | Some _ -> returnUrl + | None -> + match ctx.Request.Query.ContainsKey "returnUrl" with + | true -> Some ctx.Request.Query["returnUrl"].[0] + | false -> None return! - Hash.FromAnonymousObject {| page_title = "Log On"; csrf = (csrfToken ctx) |} + Hash.FromAnonymousObject {| + model = { LogOnModel.empty with returnTo = returnTo } + page_title = "Log On" + csrf = csrfToken ctx + |} |> viewForTheme "admin" "log-on" next ctx } @@ -629,14 +646,11 @@ module User = do! ctx.SignInAsync (identity.AuthenticationType, ClaimsPrincipal identity, AuthenticationProperties (IssuedUtc = DateTimeOffset.UtcNow)) do! addMessage ctx - { UserMessage.success with - message = "Logged on successfully" - detail = Some $"Welcome to {webLog.name}!" - } - return! redirectToGet "/admin" next ctx + { UserMessage.success with message = $"Logged on successfully | Welcome to {webLog.name}!" } + return! redirectToGet (match model.returnTo with Some url -> url | None -> "/admin") next ctx | _ -> do! addMessage ctx { UserMessage.error with message = "Log on attempt unsuccessful" } - return! logOn next ctx + return! logOn model.returnTo next ctx } // GET /user/log-off @@ -696,7 +710,7 @@ let endpoints = [ ] subRoute "/user" [ GET [ - route "/log-on" User.logOn + route "/log-on" (User.logOn None) route "/log-off" User.logOff ] POST [ diff --git a/src/MyWebLog/Program.fs b/src/MyWebLog/Program.fs index 2a46f4f..9ad58a3 100644 --- a/src/MyWebLog/Program.fs +++ b/src/MyWebLog/Program.fs @@ -58,6 +58,14 @@ module DotLiquidBespoke = "" } |> Seq.iter result.WriteLine + + /// A filter to retrieve the value of a meta item from a list + // (shorter than `{% assign item = list | where: "name", [name] | first %}{{ item.value }}`) + type ValueFilter () = + static member Value (_ : Context, items : MetaItem list, name : string) = + match items |> List.tryFind (fun it -> it.name = name) with + | Some item -> item.value + | None -> $"-- {name} not found --" /// Create the default information for a new web log @@ -192,16 +200,18 @@ let main args = // Set up DotLiquid Template.RegisterFilter typeof + Template.RegisterFilter typeof Template.RegisterTag "user_links" [ // Domain types - typeof; typeof + typeof; typeof; typeof // View models typeof; typeof; typeof; typeof - typeof; typeof; typeof; typeof - typeof; typeof + typeof; typeof; typeof; typeof + typeof; typeof; typeof // Framework types - typeof; typeof; typeof + typeof; typeof; typeof; typeof + typeof ] |> List.iter (fun it -> Template.RegisterSafeType (it, [| "*" |])) diff --git a/src/MyWebLog/themes/admin/log-on.liquid b/src/MyWebLog/themes/admin/log-on.liquid index 49df1a7..d506042 100644 --- a/src/MyWebLog/themes/admin/log-on.liquid +++ b/src/MyWebLog/themes/admin/log-on.liquid @@ -2,6 +2,9 @@
+ {% if model.return_to %} + + {% endif %}
diff --git a/src/MyWebLog/themes/admin/post-list.liquid b/src/MyWebLog/themes/admin/post-list.liquid index b3056b9..b22ae1d 100644 --- a/src/MyWebLog/themes/admin/post-list.liquid +++ b/src/MyWebLog/themes/admin/post-list.liquid @@ -31,7 +31,7 @@ Delete - {{ post.author_name }} + {{ model.authors | value: post.author_id }} {{ post.status }} {{ post.tags | join: ", " }} diff --git a/src/MyWebLog/themes/default/index.liquid b/src/MyWebLog/themes/default/index.liquid index c3c2fa1..4540ab1 100644 --- a/src/MyWebLog/themes/default/index.liquid +++ b/src/MyWebLog/themes/default/index.liquid @@ -14,10 +14,30 @@

Published on {{ post.published_on | date: "MMMM d, yyyy" }} at {{ post.published_on | date: "h:mmtt" | downcase }} + by {{ model.authors | value: post.author_id }}

{{ post.text }} + {%- assign category_count = post.category_ids | size -%} + {%- assign tag_count = post.tags | size -%} + {% if category_count > 0 or tag_count > 0 %} +
+

+ {%- if category_count > 0 -%} + {%- for cat in post.category_ids -%} + {%- assign cat_names = model.categories | value: cat | split: "," | concat: cat_names -%} + {%- endfor -%} + Categorized under: {{ cat_names | reverse | join: ", " }}
+ {%- assign cat_names = "" -%} + {% endif -%} + {%- if tag_count > 0 %} + Tagged: {{ post.tags | join: ", " }} + {% endif -%} +

+
+ {% endif %} +
{% endfor %} - \ No newline at end of file +