WIP on DotLiquid support
This commit is contained in:
62
src/MyWebLog.FS.Old/Features/Admin/AdminController.fs
Normal file
62
src/MyWebLog.FS.Old/Features/Admin/AdminController.fs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace MyWebLog.Features.Admin
|
||||
|
||||
open Microsoft.AspNetCore.Authorization
|
||||
open Microsoft.AspNetCore.Mvc
|
||||
open Microsoft.AspNetCore.Mvc.Rendering
|
||||
open MyWebLog
|
||||
open MyWebLog.Features.Shared
|
||||
open RethinkDb.Driver.Net
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// Controller for admin-specific displays and routes
|
||||
[<Route "/admin">]
|
||||
[<Authorize>]
|
||||
type AdminController () =
|
||||
inherit MyWebLogController ()
|
||||
|
||||
[<HttpGet "">]
|
||||
member this.Index () = task {
|
||||
let getCount (f : WebLogId -> IConnection -> Task<int>) = f this.WebLog.id this.Db
|
||||
let! posts = Data.Post.countByStatus Published |> getCount
|
||||
let! drafts = Data.Post.countByStatus Draft |> getCount
|
||||
let! pages = Data.Page.countAll |> getCount
|
||||
let! listed = Data.Page.countListed |> getCount
|
||||
let! cats = Data.Category.countAll |> getCount
|
||||
let! topCats = Data.Category.countTopLevel |> getCount
|
||||
return this.View (DashboardModel (
|
||||
this.WebLog,
|
||||
Posts = posts,
|
||||
Drafts = drafts,
|
||||
Pages = pages,
|
||||
ListedPages = listed,
|
||||
Categories = cats,
|
||||
TopLevelCategories = topCats
|
||||
))
|
||||
}
|
||||
|
||||
[<HttpGet "settings">]
|
||||
member this.Settings() = task {
|
||||
let! allPages = Data.Page.findAll this.WebLog.id this.Db
|
||||
return this.View (SettingsModel (
|
||||
this.WebLog,
|
||||
DefaultPages =
|
||||
(Seq.singleton (SelectListItem ("- {Resources.FirstPageOfPosts} -", "posts"))
|
||||
|> Seq.append (allPages |> Seq.map (fun p -> SelectListItem (p.title, PageId.toString p.id))))
|
||||
))
|
||||
}
|
||||
|
||||
[<HttpPost "settings">]
|
||||
member this.SaveSettings (model : SettingsModel) = task {
|
||||
match! Data.WebLog.findByHost this.WebLog.urlBase this.Db with
|
||||
| Some webLog ->
|
||||
let updated = model.UpdateSettings webLog
|
||||
do! Data.WebLog.updateSettings updated this.Db
|
||||
|
||||
// Update cache
|
||||
WebLogCache.set (WebLogCache.hostToDb this.HttpContext) updated
|
||||
|
||||
// TODO: confirmation message
|
||||
|
||||
return this.RedirectToAction (nameof this.Index);
|
||||
| None -> return this.NotFound ()
|
||||
}
|
||||
76
src/MyWebLog.FS.Old/Features/Admin/AdminTypes.fs
Normal file
76
src/MyWebLog.FS.Old/Features/Admin/AdminTypes.fs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace MyWebLog.Features.Admin
|
||||
|
||||
open MyWebLog
|
||||
open MyWebLog.Features.Shared
|
||||
|
||||
/// The model used to display the dashboard
|
||||
type DashboardModel (webLog) =
|
||||
inherit MyWebLogModel (webLog)
|
||||
|
||||
/// The number of published posts
|
||||
member val Posts = 0 with get, set
|
||||
|
||||
/// The number of post drafts
|
||||
member val Drafts = 0 with get, set
|
||||
|
||||
/// The number of pages
|
||||
member val Pages = 0 with get, set
|
||||
|
||||
/// The number of pages in the page list
|
||||
member val ListedPages = 0 with get, set
|
||||
|
||||
/// The number of categories
|
||||
member val Categories = 0 with get, set
|
||||
|
||||
/// The top-level categories
|
||||
member val TopLevelCategories = 0 with get, set
|
||||
|
||||
|
||||
open Microsoft.AspNetCore.Mvc.Rendering
|
||||
open System.ComponentModel.DataAnnotations
|
||||
|
||||
/// View model for editing web log settings
|
||||
type SettingsModel (webLog) =
|
||||
inherit MyWebLogModel (webLog)
|
||||
|
||||
/// Default constructor
|
||||
[<System.Obsolete "Only used for model binding; use the WebLogDetails constructor">]
|
||||
new() = SettingsModel WebLog.empty
|
||||
|
||||
/// The name of the web log
|
||||
[<Required (AllowEmptyStrings = false)>]
|
||||
[<Display ( ResourceType = typeof<Resources>, Name = "Name")>]
|
||||
member val Name = webLog.name with get, set
|
||||
|
||||
/// The subtitle of the web log
|
||||
[<Display(ResourceType = typeof<Resources>, Name = "Subtitle")>]
|
||||
member val Subtitle = (defaultArg webLog.subtitle "") with get, set
|
||||
|
||||
/// The default page
|
||||
[<Required>]
|
||||
[<Display(ResourceType = typeof<Resources>, Name = "DefaultPage")>]
|
||||
member val DefaultPage = webLog.defaultPage with get, set
|
||||
|
||||
/// How many posts should appear on index pages
|
||||
[<Required>]
|
||||
[<Display(ResourceType = typeof<Resources>, Name = "PostsPerPage")>]
|
||||
[<Range(0, 50)>]
|
||||
member val PostsPerPage = webLog.postsPerPage with get, set
|
||||
|
||||
/// The time zone in which dates/times should be displayed
|
||||
[<Required>]
|
||||
[<Display(ResourceType = typeof<Resources>, Name = "TimeZone")>]
|
||||
member val TimeZone = webLog.timeZone with get, set
|
||||
|
||||
/// Possible values for the default page
|
||||
member val DefaultPages = Seq.empty<SelectListItem> with get, set
|
||||
|
||||
/// Update the settings object from the data in this form
|
||||
member this.UpdateSettings (settings : WebLog) =
|
||||
{ settings with
|
||||
name = this.Name
|
||||
subtitle = (match this.Subtitle with "" -> None | sub -> Some sub)
|
||||
defaultPage = this.DefaultPage
|
||||
postsPerPage = this.PostsPerPage
|
||||
timeZone = this.TimeZone
|
||||
}
|
||||
61
src/MyWebLog.FS.Old/Features/Admin/Index.cshtml
Normal file
61
src/MyWebLog.FS.Old/Features/Admin/Index.cshtml
Normal file
@@ -0,0 +1,61 @@
|
||||
@model DashboardModel
|
||||
@{
|
||||
Layout = "_AdminLayout";
|
||||
ViewBag.Title = Resources.Dashboard;
|
||||
}
|
||||
<article class="container pt-3">
|
||||
<div class="row">
|
||||
<section class="col-lg-5 offset-lg-1 col-xl-4 offset-xl-2 pb-3">
|
||||
<div class="card">
|
||||
<header class="card-header text-white bg-primary">@Resources.Posts</header>
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle text-muted pb-3">
|
||||
@Resources.Published <span class="badge rounded-pill bg-secondary">@Model.Posts</span>
|
||||
@Resources.Drafts <span class="badge rounded-pill bg-secondary">@Model.Drafts</span>
|
||||
</h6>
|
||||
<a asp-action="All" asp-controller="Post" class="btn btn-secondary me-2">@Resources.ViewAll</a>
|
||||
<a asp-action="Edit" asp-controller="Post" asp-route-id="new" class="btn btn-primary">
|
||||
@Resources.WriteANewPost
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="col-lg-5 col-xl-4 pb-3">
|
||||
<div class="card">
|
||||
<header class="card-header text-white bg-primary">@Resources.Pages</header>
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle text-muted pb-3">
|
||||
@Resources.All <span class="badge rounded-pill bg-secondary">@Model.Pages</span>
|
||||
@Resources.ShownInPageList <span class="badge rounded-pill bg-secondary">@Model.ListedPages</span>
|
||||
</h6>
|
||||
<a asp-action="All" asp-controller="Page" class="btn btn-secondary me-2">@Resources.ViewAll</a>
|
||||
<a asp-action="Edit" asp-controller="Page" asp-route-id="new" class="btn btn-primary">
|
||||
@Resources.CreateANewPage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="row">
|
||||
<section class="col-lg-5 offset-lg-1 col-xl-4 offset-xl-2 pb-3">
|
||||
<div class="card">
|
||||
<header class="card-header text-white bg-secondary">@Resources.Categories</header>
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle text-muted pb-3">
|
||||
@Resources.All <span class="badge rounded-pill bg-secondary">@Model.Categories</span>
|
||||
@Resources.TopLevel <span class="badge rounded-pill bg-secondary">@Model.TopLevelCategories</span>
|
||||
</h6>
|
||||
<a asp-action="All" asp-controller="Category" class="btn btn-secondary me-2">@Resources.ViewAll</a>
|
||||
<a asp-action="Edit" asp-controller="Category" asp-route-id="new" class="btn btn-secondary">
|
||||
@Resources.AddANewCategory
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="row pb-3">
|
||||
<div class="col text-end">
|
||||
<a asp-action="Settings" class="btn btn-secondary">@Resources.ModifySettings</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
15
src/MyWebLog.FS.Old/Features/Pages/PageTypes.fs
Normal file
15
src/MyWebLog.FS.Old/Features/Pages/PageTypes.fs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace MyWebLog.Features.Pages
|
||||
|
||||
open MyWebLog
|
||||
open MyWebLog.Features.Shared
|
||||
|
||||
/// The model used to render a single page
|
||||
type SinglePageModel (page : Page, webLog) =
|
||||
inherit MyWebLogModel (webLog)
|
||||
|
||||
/// The page to be rendered
|
||||
member _.Page with get () = page
|
||||
|
||||
/// Is this the home page?
|
||||
member _.IsHome with get() = PageId.toString page.id = webLog.defaultPage
|
||||
|
||||
65
src/MyWebLog.FS.Old/Features/Posts/PostController.fs
Normal file
65
src/MyWebLog.FS.Old/Features/Posts/PostController.fs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace MyWebLog.Features.Posts
|
||||
|
||||
open Microsoft.AspNetCore.Authorization
|
||||
open Microsoft.AspNetCore.Mvc
|
||||
open MyWebLog
|
||||
open MyWebLog.Features.Pages
|
||||
open MyWebLog.Features.Shared
|
||||
open System
|
||||
open System.Threading.Tasks
|
||||
|
||||
/// Handle post-related requests
|
||||
[<Route "/post">]
|
||||
[<Authorize>]
|
||||
type PostController () =
|
||||
inherit MyWebLogController ()
|
||||
|
||||
[<HttpGet "~/">]
|
||||
[<AllowAnonymous>]
|
||||
member this.Index () = task {
|
||||
match this.WebLog.defaultPage with
|
||||
| "posts" -> return! this.PageOfPosts 1
|
||||
| pageId ->
|
||||
match! Data.Page.findById (PageId pageId) this.WebLog.id this.Db with
|
||||
| Some page ->
|
||||
return this.ThemedView (defaultArg page.template "SinglePage", SinglePageModel (page, this.WebLog))
|
||||
| None -> return this.NotFound ()
|
||||
}
|
||||
|
||||
[<HttpGet "~/page/{pageNbr:int}">]
|
||||
[<AllowAnonymous>]
|
||||
member this.PageOfPosts (pageNbr : int) = task {
|
||||
let! posts = Data.Post.findPageOfPublishedPosts this.WebLog.id pageNbr this.WebLog.postsPerPage this.Db
|
||||
return this.ThemedView ("Index", MultiplePostModel (posts, this.WebLog))
|
||||
}
|
||||
|
||||
[<HttpGet "~/{*link}">]
|
||||
member this.CatchAll (link : string) = task {
|
||||
let permalink = Permalink link
|
||||
match! Data.Post.findByPermalink permalink this.WebLog.id this.Db with
|
||||
| Some post -> return this.NotFound ()
|
||||
// TODO: return via single-post action
|
||||
| None ->
|
||||
match! Data.Page.findByPermalink permalink this.WebLog.id this.Db with
|
||||
| Some page ->
|
||||
return this.ThemedView (defaultArg page.template "SinglePage", SinglePageModel (page, this.WebLog))
|
||||
| None ->
|
||||
|
||||
// TOOD: search prior permalinks for posts and pages
|
||||
|
||||
// We tried, we really tried...
|
||||
Console.Write($"Returning 404 for permalink |{permalink}|");
|
||||
return this.NotFound ()
|
||||
}
|
||||
|
||||
[<HttpGet "all">]
|
||||
member this.All () = task {
|
||||
do! Task.CompletedTask;
|
||||
NotImplementedException () |> raise
|
||||
}
|
||||
|
||||
[<HttpGet "{id}/edit">]
|
||||
member this.Edit(postId : string) = task {
|
||||
do! Task.CompletedTask;
|
||||
NotImplementedException () |> raise
|
||||
}
|
||||
11
src/MyWebLog.FS.Old/Features/Posts/PostTypes.fs
Normal file
11
src/MyWebLog.FS.Old/Features/Posts/PostTypes.fs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace MyWebLog.Features.Posts
|
||||
|
||||
open MyWebLog
|
||||
open MyWebLog.Features.Shared
|
||||
|
||||
/// The model used to render multiple posts
|
||||
type MultiplePostModel (posts : Post seq, webLog) =
|
||||
inherit MyWebLogModel (webLog)
|
||||
|
||||
/// The posts to be rendered
|
||||
member _.Posts with get () = posts
|
||||
45
src/MyWebLog.FS.Old/Features/Shared/SharedTypes.fs
Normal file
45
src/MyWebLog.FS.Old/Features/Shared/SharedTypes.fs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace MyWebLog.Features.Shared
|
||||
|
||||
open Microsoft.AspNetCore.Mvc
|
||||
open Microsoft.Extensions.DependencyInjection
|
||||
open MyWebLog
|
||||
open RethinkDb.Driver.Net
|
||||
open System.Security.Claims
|
||||
|
||||
/// Base class for myWebLog controllers
|
||||
type MyWebLogController () =
|
||||
inherit Controller ()
|
||||
|
||||
/// The data context to use to fulfil this request
|
||||
member this.Db with get () = this.HttpContext.RequestServices.GetRequiredService<IConnection> ()
|
||||
|
||||
/// The details for the current web log
|
||||
member this.WebLog with get () = WebLogCache.getByCtx this.HttpContext
|
||||
|
||||
/// The ID of the currently authenticated user
|
||||
member this.UserId with get () =
|
||||
this.User.Claims
|
||||
|> Seq.tryFind (fun c -> c.Type = ClaimTypes.NameIdentifier)
|
||||
|> Option.map (fun c -> c.Value)
|
||||
|> Option.defaultValue ""
|
||||
|
||||
/// Retern a themed view
|
||||
member this.ThemedView (template : string, model : obj) : IActionResult =
|
||||
// TODO: get actual version
|
||||
this.ViewData["Version"] <- "2"
|
||||
this.View (template, model)
|
||||
|
||||
/// Return a 404 response
|
||||
member _.NotFound () : IActionResult =
|
||||
base.NotFound ()
|
||||
|
||||
/// Redirect to an action in this controller
|
||||
member _.RedirectToAction action : IActionResult =
|
||||
base.RedirectToAction action
|
||||
|
||||
|
||||
/// Base model class for myWebLog views
|
||||
type MyWebLogModel (webLog : WebLog) =
|
||||
|
||||
/// The details for the web log
|
||||
member _.WebLog with get () = webLog
|
||||
Reference in New Issue
Block a user