RSS take 2 / page edit

RSS now generates for /feed, and ?format[atom|rss|rss2]; page list shows
updated date/time, page edit page loads
This commit is contained in:
Daniel J. Summers 2016-07-26 08:03:46 -05:00
parent 7538b9ef26
commit 2574501ccd
9 changed files with 84 additions and 54 deletions

View File

@ -3,6 +3,7 @@
open FSharp.Interop.Dynamic open FSharp.Interop.Dynamic
open myWebLog.Entities open myWebLog.Entities
open Rethink open Rethink
open RethinkDb.Driver.Ast
open System.Dynamic open System.Dynamic
let private r = RethinkDb.Driver.RethinkDB.R let private r = RethinkDb.Driver.RethinkDB.R
@ -11,14 +12,18 @@ let private r = RethinkDb.Driver.RethinkDB.R
let private page (webLogId : string) (pageId : string) = let private page (webLogId : string) (pageId : string) =
r.Table(Table.Page) r.Table(Table.Page)
.Get(pageId) .Get(pageId)
.Filter(fun p -> p.["webLogId"].Eq(webLogId)) .Filter(ReqlFunction1(fun p -> upcast p.["webLogId"].Eq(webLogId)))
/// Get a page by its Id /// Get a page by its Id
let tryFindPage conn webLogId pageId : Page option = let tryFindPage conn webLogId pageId =
match (page webLogId pageId) match r.Table(Table.Page)
.Get(pageId)
.RunAtomAsync<Page>(conn) |> await |> box with .RunAtomAsync<Page>(conn) |> await |> box with
| null -> None | null -> None
| page -> Some <| unbox page | page -> let pg : Page = unbox page
match pg.webLogId = webLogId with
| true -> Some pg
| _ -> None
/// Get a page by its Id (excluding revisions) /// Get a page by its Id (excluding revisions)
let tryFindPageWithoutRevisions conn webLogId pageId : Page option = let tryFindPageWithoutRevisions conn webLogId pageId : Page option =

View File

@ -91,7 +91,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="RethinkDb.Driver"> <Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll</HintPath> <HintPath>..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />

View File

@ -6,5 +6,5 @@
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" /> <package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
<package id="FSharp.Interop.Dynamic" version="3.0.0.0" targetFramework="net452" /> <package id="FSharp.Interop.Dynamic" version="3.0.0.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="RethinkDb.Driver" version="2.3.8" targetFramework="net452" /> <package id="RethinkDb.Driver" version="2.3.9" targetFramework="net452" />
</packages> </packages>

View File

@ -22,7 +22,8 @@ type PageModule(conn : IConnection, clock : IClock) as this =
/// List all pages /// List all pages
member this.PageList () = member this.PageList () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = PagesModel(this.Context, this.WebLog, findAllPages conn this.WebLog.id) let model = PagesModel(this.Context, this.WebLog, (findAllPages conn this.WebLog.id
|> List.map (fun p -> PageForDisplay(this.WebLog, p))))
model.pageTitle <- Resources.Pages model.pageTitle <- Resources.Pages
upcast this.View.["admin/page/list", model] upcast this.View.["admin/page/list", model]
@ -42,7 +43,7 @@ type PageModule(conn : IConnection, clock : IClock) as this =
model.pageTitle <- match pageId with model.pageTitle <- match pageId with
| "new" -> Resources.AddNewPage | "new" -> Resources.AddNewPage
| _ -> Resources.EditPage | _ -> Resources.EditPage
upcast this.View.["admin/page/edit"] upcast this.View.["admin/page/edit", model]
| None -> this.NotFound () | None -> this.NotFound ()
/// Save a page /// Save a page

View File

@ -25,6 +25,37 @@ type PostModule(conn : IConnection, clock : IClock) as this =
/// Convert a list of posts to a list of posts for display /// Convert a list of posts to a list of posts for display
let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post)) let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post))
/// Generate an RSS/Atom feed of the latest posts
let generateFeed format : obj =
let posts = findFeedPosts conn this.WebLog.id 10
let feed =
SyndicationFeed(
this.WebLog.name, defaultArg this.WebLog.subtitle null,
Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.urlBase), null,
(match posts |> List.tryHead with
| Some (post, _) -> Instant(post.updatedOn).ToDateTimeOffset ()
| _ -> System.DateTimeOffset(System.DateTime.MinValue)),
posts
|> List.map (fun (post, user) ->
let item =
SyndicationItem(
BaseUri = Uri(sprintf "%s://%s/%s" this.Request.Url.Scheme this.WebLog.urlBase post.permalink),
PublishDate = Instant(post.publishedOn).ToDateTimeOffset (),
LastUpdatedTime = Instant(post.updatedOn).ToDateTimeOffset (),
Title = TextSyndicationContent(post.title),
Content = TextSyndicationContent(post.text, TextSyndicationContentKind.Html))
user
|> Option.iter (fun u -> item.Authors.Add
(SyndicationPerson(u.userName, u.preferredName, defaultArg u.url null)))
post.categories
|> List.iter (fun c -> item.Categories.Add(SyndicationCategory(c.name)))
item))
let stream = new IO.MemoryStream()
Xml.XmlWriter.Create(stream)
|> match format with | "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20
stream.Position <- int64 0
upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format)
do do
this.Get .["/" ] <- fun _ -> this.HomePage () this.Get .["/" ] <- fun _ -> this.HomePage ()
this.Get .["/{permalink*}" ] <- fun parms -> this.CatchAll (downcast parms) this.Get .["/{permalink*}" ] <- fun parms -> this.CatchAll (downcast parms)
@ -33,7 +64,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
this.Get .["/category/{slug}/page/{page:int}"] <- fun parms -> this.CategorizedPosts (downcast parms) this.Get .["/category/{slug}/page/{page:int}"] <- fun parms -> this.CategorizedPosts (downcast parms)
this.Get .["/tag/{tag}" ] <- fun parms -> this.TaggedPosts (downcast parms) this.Get .["/tag/{tag}" ] <- fun parms -> this.TaggedPosts (downcast parms)
this.Get .["/tag/{tag}/page/{page:int}" ] <- fun parms -> this.TaggedPosts (downcast parms) this.Get .["/tag/{tag}/page/{page:int}" ] <- fun parms -> this.TaggedPosts (downcast parms)
this.Get .["/feed" ] <- fun parms -> this.GenerateFeed (downcast parms) this.Get .["/feed" ] <- fun _ -> this.Feed ()
this.Get .["/posts/list" ] <- fun _ -> this.PostList 1 this.Get .["/posts/list" ] <- fun _ -> this.PostList 1
this.Get .["/posts/list/page/{page:int}" ] <- fun parms -> this.PostList (getPage <| downcast parms) this.Get .["/posts/list/page/{page:int}" ] <- fun parms -> this.PostList (getPage <| downcast parms)
this.Get .["/post/{postId}/edit" ] <- fun parms -> this.EditPost (downcast parms) this.Get .["/post/{postId}/edit" ] <- fun parms -> this.EditPost (downcast parms)
@ -137,38 +168,14 @@ type PostModule(conn : IConnection, clock : IClock) as this =
this.ThemedView "index" model this.ThemedView "index" model
/// Generate an RSS feed /// Generate an RSS feed
member this.GenerateFeed (parameters : DynamicDictionary) = member this.Feed () =
let format = match parameters.ContainsKey "format" with // FIXME: format not coming through on query string let query = this.Request.Query :?> DynamicDictionary
| true -> parameters.["format"].ToString () match query.ContainsKey "format" with
| _ -> "rss" | true -> match query.["format"].ToString () with
let posts = findFeedPosts conn this.WebLog.id 10 | x when x = "atom" || x = "rss" -> generateFeed x
let feed = | x when x = "rss2" -> generateFeed "rss"
SyndicationFeed( | _ -> this.Redirect "/feed" (MyWebLogModel(this.Context, this.WebLog))
this.WebLog.name, defaultArg this.WebLog.subtitle null, | _ -> generateFeed "rss"
Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.urlBase), null,
(match posts |> List.tryHead with
| Some (post, _) -> Instant(post.updatedOn).ToDateTimeOffset ()
| _ -> System.DateTimeOffset(System.DateTime.MinValue)),
posts
|> List.map (fun (post, user) ->
let item =
SyndicationItem(
BaseUri = Uri(sprintf "%s://%s/%s" this.Request.Url.Scheme this.WebLog.urlBase post.permalink),
PublishDate = Instant(post.publishedOn).ToDateTimeOffset (),
LastUpdatedTime = Instant(post.updatedOn).ToDateTimeOffset (),
Title = TextSyndicationContent(post.title),
Content = TextSyndicationContent(post.text, TextSyndicationContentKind.Html))
user
|> Option.iter (fun u -> item.Authors.Add
(SyndicationPerson(u.userName, u.preferredName, defaultArg u.url null)))
post.categories
|> List.iter (fun c -> item.Categories.Add(SyndicationCategory(c.name)))
item))
let stream = new IO.MemoryStream()
Xml.XmlWriter.Create(stream)
|> match format with | "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20
stream.Position <- int64 0
upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format)
// ---- Administer posts ---- // ---- Administer posts ----

View File

@ -228,11 +228,23 @@ type PageModel(ctx, webLog, page) =
member this.page : Page = page member this.page : Page = page
/// Wrapper for a page with additional properties
type PageForDisplay(webLog, page) =
/// The page
member this.page : Page = page
/// The time zone of the web log
member this.timeZone = webLog.timeZone
/// The date the page was last updated
member this.updatedDate = FormatDateTime.longDate this.timeZone page.updatedOn
/// The time the page was last updated
member this.updatedTime = FormatDateTime.time this.timeZone page.updatedOn
/// Model for page list display /// Model for page list display
type PagesModel(ctx, webLog, pages) = type PagesModel(ctx, webLog, pages) =
inherit MyWebLogModel(ctx, webLog) inherit MyWebLogModel(ctx, webLog)
/// The pages /// The pages
member this.pages : Page list = pages member this.pages : PageForDisplay list = pages
/// Form used to edit a page /// Form used to edit a page

View File

@ -75,6 +75,10 @@
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\CSharpFormat.dll</HintPath> <HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\CSharpFormat.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Dynamitey">
<HintPath>..\packages\Dynamitey.1.0.2.0\lib\net40\Dynamitey.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.CodeFormat"> <Reference Include="FSharp.CodeFormat">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.CodeFormat.dll</HintPath> <HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.CodeFormat.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -91,6 +95,10 @@
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Formatting.Common.dll</HintPath> <HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Formatting.Common.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="FSharp.Interop.Dynamic">
<HintPath>..\packages\FSharp.Interop.Dynamic.3.0.0.0\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FSharp.Literate"> <Reference Include="FSharp.Literate">
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Literate.dll</HintPath> <HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Literate.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -137,7 +145,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="RethinkDb.Driver"> <Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll</HintPath> <HintPath>..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Suave"> <Reference Include="Suave">

View File

@ -2,9 +2,11 @@
<packages> <packages>
<package id="Common.Logging" version="3.3.1" targetFramework="net452" /> <package id="Common.Logging" version="3.3.1" targetFramework="net452" />
<package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" /> <package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
<package id="Dynamitey" version="1.0.2.0" targetFramework="net452" />
<package id="FSharp.Compiler.Service" version="2.0.0.6" targetFramework="net452" /> <package id="FSharp.Compiler.Service" version="2.0.0.6" targetFramework="net452" />
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" /> <package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
<package id="FSharp.Formatting" version="2.14.4" targetFramework="net452" /> <package id="FSharp.Formatting" version="2.14.4" targetFramework="net452" />
<package id="FSharp.Interop.Dynamic" version="3.0.0.0" targetFramework="net452" />
<package id="FSharpVSPowerTools.Core" version="2.3.0" targetFramework="net452" /> <package id="FSharpVSPowerTools.Core" version="2.3.0" targetFramework="net452" />
<package id="Nancy" version="1.4.3" targetFramework="net452" /> <package id="Nancy" version="1.4.3" targetFramework="net452" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" /> <package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
@ -12,6 +14,6 @@
<package id="Nancy.Session.RethinkDB" version="0.8.6" targetFramework="net452" /> <package id="Nancy.Session.RethinkDB" version="0.8.6" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="NodaTime" version="1.3.2" targetFramework="net452" /> <package id="NodaTime" version="1.3.2" targetFramework="net452" />
<package id="RethinkDb.Driver" version="2.3.8" targetFramework="net452" /> <package id="RethinkDb.Driver" version="2.3.9" targetFramework="net452" />
<package id="Suave" version="1.1.3" targetFramework="net452" /> <package id="Suave" version="1.1.3" targetFramework="net452" />
</packages> </packages>

View File

@ -13,17 +13,12 @@
@Each.pages @Each.pages
<tr> <tr>
<td> <td>
@Current.title<br /> @Current.page.title<br />
<a href="/@Current.permalink">@Translate.View</a> &nbsp; <a href="/@Current.page.permalink">@Translate.View</a> &nbsp;
<a href="/page/@Current.id/edit">@Translate.Edit</a> &nbsp; <a href="/page/@Current.page.id/edit">@Translate.Edit</a> &nbsp;
<a href="javascript:void(0)" onclick="deletePage('@Current.id', '@Current.title')">@Translate.Delete</a> <a href="javascript:void(0)" onclick="deletePage('@Current.page.id', '@Current.title')">@Translate.Delete</a>
</td>
<td>
<!-- // TODO: make the formatting stuff work in a loop
=theDate.format('MMM D, YYYY')
br
#{__("at")} #{theDate.format('h:mma')} -->
</td> </td>
<td>@Current.updatedDate<br />@Translate.at @Current.updatedTime</td>
</tr> </tr>
@EndEach @EndEach
</table> </table>