V2 #1
@ -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<Category list> {
|
||||
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<WebLogUser list> {
|
||||
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 })
|
||||
}
|
||||
|
@ -88,6 +88,17 @@ module MarkupText =
|
||||
| text -> invalidOp $"Cannot derive type of text ({text})"
|
||||
|
||||
|
||||
/// An item of metadata
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type MetaItem =
|
||||
{ /// The name of the metadata value
|
||||
name : string
|
||||
|
||||
/// The metadata value
|
||||
value : string
|
||||
}
|
||||
|
||||
|
||||
/// A revision of a page or post
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type Revision =
|
||||
|
@ -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<string, string>
|
||||
categories : MetaItem list
|
||||
|
||||
/// A subtitle for the page
|
||||
subtitle : string option
|
||||
|
@ -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 [
|
||||
|
@ -58,6 +58,14 @@ module DotLiquidBespoke =
|
||||
"</ul>"
|
||||
}
|
||||
|> 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<DotLiquidBespoke.NavLinkFilter>
|
||||
Template.RegisterFilter typeof<DotLiquidBespoke.ValueFilter>
|
||||
Template.RegisterTag<DotLiquidBespoke.UserLinksTag> "user_links"
|
||||
|
||||
[ // Domain types
|
||||
typeof<Page>; typeof<WebLog>
|
||||
typeof<MetaItem>; typeof<Page>; typeof<WebLog>
|
||||
// View models
|
||||
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
|
||||
typeof<EditPageModel>; typeof<EditPostModel>; typeof<PostDisplay>; typeof<PostListItem>
|
||||
typeof<SettingsModel>; typeof<UserMessage>
|
||||
typeof<EditPageModel>; typeof<EditPostModel>; typeof<LogOnModel>; typeof<PostDisplay>
|
||||
typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage>
|
||||
// Framework types
|
||||
typeof<AntiforgeryTokenSet>; typeof<string option>; typeof<KeyValuePair>
|
||||
typeof<AntiforgeryTokenSet>; typeof<KeyValuePair>; typeof<MetaItem list>; typeof<string list>
|
||||
typeof<string option>
|
||||
]
|
||||
|> List.iter (fun it -> Template.RegisterSafeType (it, [| "*" |]))
|
||||
|
||||
|
@ -2,6 +2,9 @@
|
||||
<article class="py-3">
|
||||
<form action="/user/log-on" method="post">
|
||||
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
||||
{% if model.return_to %}
|
||||
<input type="hidden" name="returnTo" value="{{ model.return_to.value }}">
|
||||
{% endif %}
|
||||
<div class="container">
|
||||
<div class="row pb-3">
|
||||
<div class="col col-md-6 col-lg-4 offset-lg-2">
|
||||
|
@ -31,7 +31,7 @@
|
||||
<a href="#" class="text-danger">Delete</a>
|
||||
</small>
|
||||
</td>
|
||||
<td>{{ post.author_name }}</td>
|
||||
<td>{{ model.authors | value: post.author_id }}</td>
|
||||
<td>{{ post.status }}</td>
|
||||
<td>{{ post.tags | join: ", " }}</td>
|
||||
</tr>
|
||||
|
@ -14,10 +14,30 @@
|
||||
<p>
|
||||
Published on {{ post.published_on | date: "MMMM d, yyyy" }}
|
||||
at {{ post.published_on | date: "h:mmtt" | downcase }}
|
||||
by {{ model.authors | value: post.author_id }}
|
||||
</p>
|
||||
{{ post.text }}
|
||||
{%- assign category_count = post.category_ids | size -%}
|
||||
{%- assign tag_count = post.tags | size -%}
|
||||
{% if category_count > 0 or tag_count > 0 %}
|
||||
<footer>
|
||||
<p>
|
||||
{%- 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: ", " }}<br>
|
||||
{%- assign cat_names = "" -%}
|
||||
{% endif -%}
|
||||
{%- if tag_count > 0 %}
|
||||
Tagged: {{ post.tags | join: ", " }}
|
||||
{% endif -%}
|
||||
</p>
|
||||
</footer>
|
||||
{% endif %}
|
||||
<hr>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
|
Loading…
Reference in New Issue
Block a user