From ac8fa084d1fd390d40de19e8b5a03ab3610ad859 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 26 Jul 2016 23:17:13 -0500 Subject: [PATCH] PascalCase record members Contrary to some examples, the official design guidelines for F# state that these should be PascalCase rather than camelCase. Also, while the projects are still "myWebLog", the namespaces and DLLs are now "MyWebLog". (never intended it to be the other way, really...) --- src/Settings.FSharpLint | 5 + src/myWebLog.Data/AssemblyInfo.fs | 4 +- src/myWebLog.Data/Category.fs | 63 +-- src/myWebLog.Data/DataConfig.fs | 90 ++-- src/myWebLog.Data/Entities.fs | 425 +++++++++--------- src/myWebLog.Data/Page.fs | 42 +- src/myWebLog.Data/Post.fs | 119 +++-- src/myWebLog.Data/Rethink.fs | 2 +- src/myWebLog.Data/SetUp.fs | 44 +- src/myWebLog.Data/Table.fs | 2 +- src/myWebLog.Data/User.fs | 8 +- src/myWebLog.Data/WebLog.fs | 52 +-- src/myWebLog.Data/myWebLog.Data.fsproj | 4 +- src/myWebLog.Resources/Resources.Designer.cs | 4 +- .../myWebLog.Resources.csproj | 4 +- src/myWebLog.Web/AdminModule.fs | 10 +- src/myWebLog.Web/App.fs | 47 +- src/myWebLog.Web/AssemblyInfo.fs | 4 +- src/myWebLog.Web/CategoryModule.fs | 70 ++- src/myWebLog.Web/Keys.fs | 2 +- src/myWebLog.Web/ModuleExtensions.fs | 13 +- src/myWebLog.Web/PageModule.fs | 75 ++-- src/myWebLog.Web/PostModule.fs | 218 +++++---- src/myWebLog.Web/UserModule.fs | 49 +- src/myWebLog.Web/ViewModels.fs | 301 ++++++------- src/myWebLog.Web/myWebLog.Web.fsproj | 2 +- src/myWebLog/Program.cs | 4 +- src/myWebLog/Properties/AssemblyInfo.cs | 4 +- src/myWebLog/myWebLog.csproj | 5 +- src/myWebLog/views/admin/admin-layout.html | 16 +- src/myWebLog/views/admin/category/edit.html | 24 +- src/myWebLog/views/admin/category/list.html | 14 +- src/myWebLog/views/admin/dashboard.html | 6 +- src/myWebLog/views/admin/message.html | 18 - src/myWebLog/views/admin/page/edit.html | 22 +- src/myWebLog/views/admin/page/list.html | 12 +- src/myWebLog/views/admin/post/edit.html | 40 +- src/myWebLog/views/admin/post/list.html | 28 +- src/myWebLog/views/admin/user/logon.html | 8 +- src/myWebLog/views/themes/default/footer.html | 2 +- .../views/themes/default/index-content.html | 28 +- src/myWebLog/views/themes/default/layout.html | 20 +- .../views/themes/default/page-content.html | 4 +- .../views/themes/default/single-content.html | 30 +- 44 files changed, 950 insertions(+), 994 deletions(-) create mode 100644 src/Settings.FSharpLint delete mode 100644 src/myWebLog/views/admin/message.html diff --git a/src/Settings.FSharpLint b/src/Settings.FSharpLint new file mode 100644 index 0000000..d8e7530 --- /dev/null +++ b/src/Settings.FSharpLint @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/myWebLog.Data/AssemblyInfo.fs b/src/myWebLog.Data/AssemblyInfo.fs index 701d1ed..e5d523c 100644 --- a/src/myWebLog.Data/AssemblyInfo.fs +++ b/src/myWebLog.Data/AssemblyInfo.fs @@ -4,11 +4,11 @@ open System.Reflection open System.Runtime.CompilerServices open System.Runtime.InteropServices -[] +[] [] [] [] -[] +[] [] [] [] diff --git a/src/myWebLog.Data/Category.fs b/src/myWebLog.Data/Category.fs index d670029..f498fe8 100644 --- a/src/myWebLog.Data/Category.fs +++ b/src/myWebLog.Data/Category.fs @@ -1,8 +1,9 @@ -module myWebLog.Data.Category +module MyWebLog.Data.Category open FSharp.Interop.Dynamic -open myWebLog.Entities +open MyWebLog.Entities open Rethink +open RethinkDb.Driver.Ast open System.Dynamic let private r = RethinkDb.Driver.RethinkDB.R @@ -11,18 +12,18 @@ let private r = RethinkDb.Driver.RethinkDB.R let private category (webLogId : string) (catId : string) = r.Table(Table.Category) .Get(catId) - .Filter(fun c -> c.["webLogId"].Eq(webLogId)) + .Filter(fun c -> c.["WebLogId"].Eq(webLogId)) /// Sort categories by their name, with their children sorted below them, including an indent level let sortCategories categories = let rec getChildren (cat : Category) indent = seq { yield cat, indent - for child in categories |> List.filter (fun c -> c.parentId = Some cat.id) do + for child in categories |> List.filter (fun c -> c.ParentId = Some cat.Id) do yield! getChildren child (indent + 1) } categories - |> List.filter (fun c -> c.parentId.IsNone) + |> List.filter (fun c -> c.ParentId.IsNone) |> List.map (fun c -> getChildren c 0) |> Seq.collect id |> Seq.toList @@ -30,8 +31,8 @@ let sortCategories categories = /// Get all categories for a web log let getAllCategories conn (webLogId : string) = r.Table(Table.Category) - .GetAll(webLogId).OptArg("index", "webLogId") - .OrderBy("name") + .GetAll(webLogId).OptArg("index", "WebLogId") + .OrderBy("Name") .RunListAsync(conn) |> await |> Seq.toList @@ -46,28 +47,28 @@ let tryFindCategory conn webLogId catId : Category option = /// Save a category let saveCategory conn webLogId (cat : Category) = - match cat.id with - | "new" -> let newCat = { cat with id = string <| System.Guid.NewGuid() - webLogId = webLogId } + match cat.Id with + | "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid() + WebLogId = webLogId } r.Table(Table.Category) .Insert(newCat) .RunResultAsync(conn) |> await |> ignore - newCat.id + newCat.Id | _ -> let upd8 = ExpandoObject() - upd8?name <- cat.name - upd8?slug <- cat.slug - upd8?description <- cat.description - upd8?parentId <- cat.parentId - (category webLogId cat.id) + upd8?Name <- cat.Name + upd8?Slug <- cat.Slug + upd8?Description <- cat.Description + upd8?ParentId <- cat.ParentId + (category webLogId cat.Id) .Update(upd8) .RunResultAsync(conn) |> await |> ignore - cat.id + cat.Id /// Remove a category from a given parent let removeCategoryFromParent conn webLogId parentId catId = match tryFindCategory conn webLogId parentId with | Some parent -> let upd8 = ExpandoObject() - upd8?children <- parent.children + upd8?Children <- parent.Children |> List.filter (fun childId -> childId <> catId) (category webLogId parentId) .Update(upd8) @@ -78,7 +79,7 @@ let removeCategoryFromParent conn webLogId parentId catId = let addCategoryToParent conn webLogId parentId catId = match tryFindCategory conn webLogId parentId with | Some parent -> let upd8 = ExpandoObject() - upd8?children <- catId :: parent.children + upd8?Children <- catId :: parent.Children (category webLogId parentId) .Update(upd8) .RunResultAsync(conn) |> await |> ignore @@ -87,40 +88,40 @@ let addCategoryToParent conn webLogId parentId catId = /// Delete a category let deleteCategory conn cat = // Remove the category from its parent - match cat.parentId with - | Some parentId -> removeCategoryFromParent conn cat.webLogId parentId cat.id + match cat.ParentId with + | Some parentId -> removeCategoryFromParent conn cat.WebLogId parentId cat.Id | None -> () // Move this category's children to its parent let newParent = ExpandoObject() - newParent?parentId <- cat.parentId - cat.children - |> List.iter (fun childId -> (category cat.webLogId childId) + newParent?ParentId <- cat.ParentId + cat.Children + |> List.iter (fun childId -> (category cat.WebLogId childId) .Update(newParent) .RunResultAsync(conn) |> await |> ignore) // Remove the category from posts where it is assigned r.Table(Table.Post) - .GetAll(cat.webLogId).OptArg("index", "webLogId") - .Filter(fun p -> p.["categoryIds"].Contains(cat.id)) + .GetAll(cat.WebLogId).OptArg("index", "WebLogId") + .Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(cat.Id))) .RunCursorAsync(conn) |> await |> Seq.toList |> List.iter (fun post -> let newCats = ExpandoObject() - newCats?categoryIds <- post.categoryIds - |> List.filter (fun c -> c <> cat.id) + newCats?CategoryIds <- post.CategoryIds + |> List.filter (fun c -> c <> cat.Id) r.Table(Table.Post) - .Get(post.id) + .Get(post.Id) .Update(newCats) .RunResultAsync(conn) |> await |> ignore) // Now, delete the category r.Table(Table.Category) - .Get(cat.id) + .Get(cat.Id) .Delete() .RunResultAsync(conn) |> await |> ignore /// Get a category by its slug let tryFindCategoryBySlug conn (webLogId : string) (slug : string) = r.Table(Table.Category) - .GetAll(r.Array(webLogId, slug)).OptArg("index", "slug") + .GetAll(r.Array(webLogId, slug)).OptArg("index", "Slug") .RunCursorAsync(conn) |> await |> Seq.tryHead diff --git a/src/myWebLog.Data/DataConfig.fs b/src/myWebLog.Data/DataConfig.fs index e4709ff..49325de 100644 --- a/src/myWebLog.Data/DataConfig.fs +++ b/src/myWebLog.Data/DataConfig.fs @@ -1,47 +1,59 @@ -namespace myWebLog.Data +namespace MyWebLog.Data open RethinkDb.Driver open RethinkDb.Driver.Net open Newtonsoft.Json /// Data configuration -type DataConfig = { - /// The hostname for the RethinkDB server - hostname : string - /// The port for the RethinkDB server - port : int - /// The authorization key to use when connecting to the server - authKey : string - /// How long an attempt to connect to the server should wait before giving up - timeout : int - /// The name of the default database to use on the connection - database : string - /// A connection to the RethinkDB server using the configuration in this object - conn : IConnection - } +type DataConfig = + { /// The hostname for the RethinkDB server + [] + Hostname : string + /// The port for the RethinkDB server + [] + Port : int + /// The authorization key to use when connecting to the server + [] + AuthKey : string + /// How long an attempt to connect to the server should wait before giving up + [] + Timeout : int + /// The name of the default database to use on the connection + [] + Database : string + /// A connection to the RethinkDB server using the configuration in this object + [] + Conn : IConnection } with /// Create a data configuration from JSON - static member fromJson json = - let mutable cfg = JsonConvert.DeserializeObject json - cfg <- match cfg.hostname with - | null -> { cfg with hostname = RethinkDBConstants.DefaultHostname } - | _ -> cfg - cfg <- match cfg.port with - | 0 -> { cfg with port = RethinkDBConstants.DefaultPort } - | _ -> cfg - cfg <- match cfg.authKey with - | null -> { cfg with authKey = RethinkDBConstants.DefaultAuthkey } - | _ -> cfg - cfg <- match cfg.timeout with - | 0 -> { cfg with timeout = RethinkDBConstants.DefaultTimeout } - | _ -> cfg - cfg <- match cfg.database with - | null -> { cfg with database = RethinkDBConstants.DefaultDbName } - | _ -> cfg - { cfg with conn = RethinkDB.R.Connection() - .Hostname(cfg.hostname) - .Port(cfg.port) - .AuthKey(cfg.authKey) - .Db(cfg.database) - .Timeout(cfg.timeout) - .Connect() } + static member FromJson json = + let ensureHostname cfg = match cfg.Hostname with + | null -> { cfg with Hostname = RethinkDBConstants.DefaultHostname } + | _ -> cfg + let ensurePort cfg = match cfg.Port with + | 0 -> { cfg with Port = RethinkDBConstants.DefaultPort } + | _ -> cfg + let ensureAuthKey cfg = match cfg.AuthKey with + | null -> { cfg with AuthKey = RethinkDBConstants.DefaultAuthkey } + | _ -> cfg + let ensureTimeout cfg = match cfg.Timeout with + | 0 -> { cfg with Timeout = RethinkDBConstants.DefaultTimeout } + | _ -> cfg + let ensureDatabase cfg = match cfg.Database with + | null -> { cfg with Database = RethinkDBConstants.DefaultDbName } + | _ -> cfg + let connect cfg = { cfg with Conn = RethinkDB.R.Connection() + .Hostname(cfg.Hostname) + .Port(cfg.Port) + .AuthKey(cfg.AuthKey) + .Db(cfg.Database) + .Timeout(cfg.Timeout) + .Connect() } + JsonConvert.DeserializeObject json + |> ensureHostname + |> ensurePort + |> ensureAuthKey + |> ensureTimeout + |> ensureDatabase + |> connect + diff --git a/src/myWebLog.Data/Entities.fs b/src/myWebLog.Data/Entities.fs index bde0a9b..df767ad 100644 --- a/src/myWebLog.Data/Entities.fs +++ b/src/myWebLog.Data/Entities.fs @@ -1,4 +1,4 @@ -namespace myWebLog.Entities +namespace MyWebLog.Entities open Newtonsoft.Json @@ -37,261 +37,244 @@ module CommentStatus = // ---- Entities ---- /// A revision of a post or page -type Revision = { - /// The instant which this revision was saved - asOf : int64 - /// The source language - sourceType : string - /// The text - text : string - } +type Revision = + { /// The instant which this revision was saved + AsOf : int64 + /// The source language + SourceType : string + /// The text + Text : string } with /// An empty revision - static member empty = - { asOf = int64 0 - sourceType = RevisionSource.HTML - text = "" } + static member Empty = + { AsOf = int64 0 + SourceType = RevisionSource.HTML + Text = "" } /// A page with static content -type Page = { - /// The Id - id : string - /// The Id of the web log to which this page belongs - webLogId : string - /// The Id of the author of this page - authorId : string - /// The title of the page - title : string - /// The link at which this page is displayed - permalink : string - /// The instant this page was published - publishedOn : int64 - /// The instant this page was last updated - updatedOn : int64 - /// Whether this page shows as part of the web log's navigation - showInPageList : bool - /// The current text of the page - text : string - /// Revisions of this page - revisions : Revision list - } +type Page = + { /// The Id + Id : string + /// The Id of the web log to which this page belongs + WebLogId : string + /// The Id of the author of this page + AuthorId : string + /// The title of the page + Title : string + /// The link at which this page is displayed + Permalink : string + /// The instant this page was published + PublishedOn : int64 + /// The instant this page was last updated + UpdatedOn : int64 + /// Whether this page shows as part of the web log's navigation + ShowInPageList : bool + /// The current text of the page + Text : string + /// Revisions of this page + Revisions : Revision list } with - static member empty = - { id = "" - webLogId = "" - authorId = "" - title = "" - permalink = "" - publishedOn = int64 0 - updatedOn = int64 0 - showInPageList = false - text = "" - revisions = List.empty + static member Empty = + { Id = "" + WebLogId = "" + AuthorId = "" + Title = "" + Permalink = "" + PublishedOn = int64 0 + UpdatedOn = int64 0 + ShowInPageList = false + Text = "" + Revisions = List.empty } /// An entry in the list of pages displayed as part of the web log (derived via query) -type PageListEntry = { - permalink : string - title : string - } +type PageListEntry = + { Permalink : string + Title : string } /// A web log -type WebLog = { - /// The Id - id : string - /// The name - name : string - /// The subtitle - subtitle : string option - /// The default page ("posts" or a page Id) - defaultPage : string - /// The path of the theme (within /views/themes) - themePath : string - /// The URL base - urlBase : string - /// The time zone in which dates/times should be displayed - timeZone : string - /// A list of pages to be rendered as part of the site navigation - [] - pageList : PageListEntry list - } +type WebLog = + { /// The Id + Id : string + /// The name + Name : string + /// The subtitle + Subtitle : string option + /// The default page ("posts" or a page Id) + DefaultPage : string + /// The path of the theme (within /views/themes) + ThemePath : string + /// The URL base + UrlBase : string + /// The time zone in which dates/times should be displayed + TimeZone : string + /// A list of pages to be rendered as part of the site navigation (not stored) + PageList : PageListEntry list } with /// An empty web log - static member empty = - { id = "" - name = "" - subtitle = None - defaultPage = "" - themePath = "default" - urlBase = "" - timeZone = "America/New_York" - pageList = List.empty - } + static member Empty = + { Id = "" + Name = "" + Subtitle = None + DefaultPage = "" + ThemePath = "default" + UrlBase = "" + TimeZone = "America/New_York" + PageList = List.empty } /// An authorization between a user and a web log -type Authorization = { - /// The Id of the web log to which this authorization grants access - webLogId : string - /// The level of access granted by this authorization - level : string - } +type Authorization = + { /// The Id of the web log to which this authorization grants access + WebLogId : string + /// The level of access granted by this authorization + Level : string } /// A user of myWebLog -type User = { - /// The Id - id : string - /// The user name (e-mail address) - userName : string - /// The first name - firstName : string - /// The last name - lastName : string - /// The user's preferred name - preferredName : string - /// The hash of the user's password - passwordHash : string - /// The URL of the user's personal site - url : string option - /// The user's authorizations - authorizations : Authorization list - } +type User = + { /// The Id + Id : string + /// The user name (e-mail address) + UserName : string + /// The first name + FirstName : string + /// The last name + LastName : string + /// The user's preferred name + PreferredName : string + /// The hash of the user's password + PasswordHash : string + /// The URL of the user's personal site + Url : string option + /// The user's authorizations + Authorizations : Authorization list } with /// An empty user - static member empty = - { id = "" - userName = "" - firstName = "" - lastName = "" - preferredName = "" - passwordHash = "" - url = None - authorizations = List.empty - } + static member Empty = + { Id = "" + UserName = "" + FirstName = "" + LastName = "" + PreferredName = "" + PasswordHash = "" + Url = None + Authorizations = List.empty } /// Claims for this user [] - member this.claims = this.authorizations - |> List.map (fun auth -> sprintf "%s|%s" auth.webLogId auth.level) + member this.Claims = this.Authorizations + |> List.map (fun auth -> sprintf "%s|%s" auth.WebLogId auth.Level) /// A category to which posts may be assigned -type Category = { - /// The Id - id : string - /// The Id of the web log to which this category belongs - webLogId : string - /// The displayed name - name : string - /// The slug (used in category URLs) - slug : string - /// A longer description of the category - description : string option - /// The parent Id of this category (if a subcategory) - parentId : string option - /// The categories for which this category is the parent - children : string list - } +type Category = + { /// The Id + Id : string + /// The Id of the web log to which this category belongs + WebLogId : string + /// The displayed name + Name : string + /// The slug (used in category URLs) + Slug : string + /// A longer description of the category + Description : string option + /// The parent Id of this category (if a subcategory) + ParentId : string option + /// The categories for which this category is the parent + Children : string list } with /// An empty category static member empty = - { id = "new" - webLogId = "" - name = "" - slug = "" - description = None - parentId = None - children = List.empty - } + { Id = "new" + WebLogId = "" + Name = "" + Slug = "" + Description = None + ParentId = None + Children = List.empty } /// A comment (applies to a post) -type Comment = { - /// The Id - id : string - /// The Id of the post to which this comment applies - postId : string - /// The Id of the comment to which this comment is a reply - inReplyToId : string option - /// The name of the commentor - name : string - /// The e-mail address of the commentor - email : string - /// The URL of the commentor's personal website - url : string option - /// The status of the comment - status : string - /// The instant the comment was posted - postedOn : int64 - /// The text of the comment - text : string - } +type Comment = + { /// The Id + Id : string + /// The Id of the post to which this comment applies + PostId : string + /// The Id of the comment to which this comment is a reply + InReplyToId : string option + /// The name of the commentor + Name : string + /// The e-mail address of the commentor + Email : string + /// The URL of the commentor's personal website + Url : string option + /// The status of the comment + Status : string + /// The instant the comment was posted + PostedOn : int64 + /// The text of the comment + Text : string } with - static member empty = - { id = "" - postId = "" - inReplyToId = None - name = "" - email = "" - url = None - status = CommentStatus.Pending - postedOn = int64 0 - text = "" - } + static member Empty = + { Id = "" + PostId = "" + InReplyToId = None + Name = "" + Email = "" + Url = None + Status = CommentStatus.Pending + PostedOn = int64 0 + Text = "" } /// A post -type Post = { - /// The Id - id : string - /// The Id of the web log to which this post belongs - webLogId : string - /// The Id of the author of this post - authorId : string - /// The status - status : string - /// The title - title : string - /// The link at which the post resides - permalink : string - /// The instant on which the post was originally published - publishedOn : int64 - /// The instant on which the post was last updated - updatedOn : int64 - /// The text of the post - text : string - /// The Ids of the categories to which this is assigned - categoryIds : string list - /// The tags for the post - tags : string list - /// The permalinks at which this post may have once resided - priorPermalinks : string list - /// Revisions of this post - revisions : Revision list - /// The categories to which this is assigned - [] - categories : Category list - /// The comments - [] - comments : Comment list - } +type Post = + { /// The Id + Id : string + /// The Id of the web log to which this post belongs + WebLogId : string + /// The Id of the author of this post + AuthorId : string + /// The status + Status : string + /// The title + Title : string + /// The link at which the post resides + Permalink : string + /// The instant on which the post was originally published + PublishedOn : int64 + /// The instant on which the post was last updated + UpdatedOn : int64 + /// The text of the post + Text : string + /// The Ids of the categories to which this is assigned + CategoryIds : string list + /// The tags for the post + Tags : string list + /// The permalinks at which this post may have once resided + PriorPermalinks : string list + /// Revisions of this post + Revisions : Revision list + /// The categories to which this is assigned (not stored in database) + Categories : Category list + /// The comments (not stored in database) + Comments : Comment list } with - static member empty = - { id = "new" - webLogId = "" - authorId = "" - status = PostStatus.Draft - title = "" - permalink = "" - publishedOn = int64 0 - updatedOn = int64 0 - text = "" - categoryIds = List.empty - tags = List.empty - priorPermalinks = List.empty - revisions = List.empty - categories = List.empty - comments = List.empty - } + static member Empty = + { Id = "new" + WebLogId = "" + AuthorId = "" + Status = PostStatus.Draft + Title = "" + Permalink = "" + PublishedOn = int64 0 + UpdatedOn = int64 0 + Text = "" + CategoryIds = List.empty + Tags = List.empty + PriorPermalinks = List.empty + Revisions = List.empty + Categories = List.empty + Comments = List.empty } diff --git a/src/myWebLog.Data/Page.fs b/src/myWebLog.Data/Page.fs index 6fdb455..9f2d299 100644 --- a/src/myWebLog.Data/Page.fs +++ b/src/myWebLog.Data/Page.fs @@ -1,7 +1,7 @@ -module myWebLog.Data.Page +module MyWebLog.Data.Page open FSharp.Interop.Dynamic -open myWebLog.Entities +open MyWebLog.Entities open Rethink open RethinkDb.Driver.Ast open System.Dynamic @@ -12,7 +12,7 @@ let private r = RethinkDb.Driver.RethinkDB.R let private page (webLogId : string) (pageId : string) = r.Table(Table.Page) .Get(pageId) - .Filter(ReqlFunction1(fun p -> upcast p.["webLogId"].Eq(webLogId))) + .Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId))) /// Get a page by its Id let tryFindPage conn webLogId pageId = @@ -21,14 +21,14 @@ let tryFindPage conn webLogId pageId = .RunAtomAsync(conn) |> await |> box with | null -> None | page -> let pg : Page = unbox page - match pg.webLogId = webLogId with + match pg.WebLogId = webLogId with | true -> Some pg | _ -> None /// Get a page by its Id (excluding revisions) let tryFindPageWithoutRevisions conn webLogId pageId : Page option = match (page webLogId pageId) - .Without("revisions") + .Without("Revisions") .RunAtomAsync(conn) |> await |> box with | null -> None | page -> Some <| unbox page @@ -36,8 +36,8 @@ let tryFindPageWithoutRevisions conn webLogId pageId : Page option = /// Find a page by its permalink let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = r.Table(Table.Page) - .GetAll(r.Array(webLogId, permalink)).OptArg("index", "permalink") - .Without("revisions") + .GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") + .Without("Revisions") .RunCursorAsync(conn) |> await |> Seq.tryHead @@ -45,32 +45,32 @@ let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = /// Get a list of all pages (excludes page text and revisions) let findAllPages conn (webLogId : string) = r.Table(Table.Page) - .GetAll(webLogId).OptArg("index", "webLogId") - .OrderBy("title") - .Without("text", "revisions") + .GetAll(webLogId).OptArg("index", "WebLogId") + .OrderBy("Title") + .Without("Text", "Revisions") .RunListAsync(conn) |> await |> Seq.toList /// Save a page let savePage conn (pg : Page) = - match pg.id with - | "new" -> let newPage = { pg with id = string <| System.Guid.NewGuid() } + match pg.Id with + | "new" -> let newPage = { pg with Id = string <| System.Guid.NewGuid() } r.Table(Table.Page) .Insert(page) .RunResultAsync(conn) |> await |> ignore - newPage.id + newPage.Id | _ -> let upd8 = ExpandoObject() - upd8?title <- pg.title - upd8?permalink <- pg.permalink - upd8?publishedOn <- pg.publishedOn - upd8?updatedOn <- pg.updatedOn - upd8?text <- pg.text - upd8?revisions <- pg.revisions - (page pg.webLogId pg.id) + upd8?Title <- pg.Title + upd8?Permalink <- pg.Permalink + upd8?PublishedOn <- pg.PublishedOn + upd8?UpdatedOn <- pg.UpdatedOn + upd8?Text <- pg.Text + upd8?Revisions <- pg.Revisions + (page pg.WebLogId pg.Id) .Update(upd8) .RunResultAsync(conn) |> await |> ignore - pg.id + pg.Id /// Delete a page let deletePage conn webLogId pageId = diff --git a/src/myWebLog.Data/Post.fs b/src/myWebLog.Data/Post.fs index 8cf7a34..5e916ce 100644 --- a/src/myWebLog.Data/Post.fs +++ b/src/myWebLog.Data/Post.fs @@ -1,7 +1,7 @@ -module myWebLog.Data.Post +module MyWebLog.Data.Post open FSharp.Interop.Dynamic -open myWebLog.Entities +open MyWebLog.Entities open Rethink open RethinkDb.Driver.Ast open System.Dynamic @@ -11,22 +11,20 @@ let private r = RethinkDb.Driver.RethinkDB.R /// Shorthand to select all published posts for a web log let private publishedPosts (webLogId : string)= r.Table(Table.Post) - .GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "webLogAndStatus") + .GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus") /// Shorthand to sort posts by published date, slice for the given page, and return a list let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) = filter - .OrderBy(r.Desc("publishedOn")) + .OrderBy(r.Desc("PublishedOn")) .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) .RunListAsync(conn) |> await |> Seq.toList /// Shorthand to get a newer or older post -// TODO: older posts need to sort by published on DESC -//let private adjacentPost conn post (theFilter : ReqlExpr -> ReqlExpr) (sort :ReqlExpr) : Post option = -let private adjacentPost conn post (theFilter : ReqlExpr -> obj) (sort : obj) : Post option = - (publishedPosts post.webLogId) +let private adjacentPost conn post (theFilter : ReqlExpr -> obj) (sort : obj) = + (publishedPosts post.WebLogId) .Filter(theFilter) .OrderBy(sort) .Limit(1) @@ -48,45 +46,45 @@ let findPageOfPublishedPosts conn webLogId pageNbr nbrPerPage = /// Get a page of published posts assigned to a given category let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage = (publishedPosts webLogId) - .Filter(ReqlFunction1(fun p -> upcast p.["categoryIds"].Contains(categoryId))) + .Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(categoryId))) |> toPostList conn pageNbr nbrPerPage /// Get a page of published posts tagged with a given tag let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage = (publishedPosts webLogId) - .Filter(ReqlFunction1(fun p -> upcast p.["tags"].Contains(tag))) + .Filter(ReqlFunction1(fun p -> upcast p.["Tags"].Contains(tag))) |> toPostList conn pageNbr nbrPerPage /// Try to get the next newest post from the given post -let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn)) +let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn)) /// Try to get the next newest post assigned to the given category let tryFindNewerCategorizedPost conn (categoryId : string) post = - newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn) - .And(p.["categoryIds"].Contains(categoryId))) + newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn) + .And(p.["CategoryIds"].Contains(categoryId))) /// Try to get the next newest tagged post from the given tagged post let tryFindNewerTaggedPost conn (tag : string) post = - newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn).And(p.["tags"].Contains(tag))) + newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn).And(p.["Tags"].Contains(tag))) /// Try to get the next oldest post from the given post -let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn)) +let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn)) /// Try to get the next oldest post assigned to the given category let tryFindOlderCategorizedPost conn (categoryId : string) post = - olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn) - .And(p.["categoryIds"].Contains(categoryId))) + olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn) + .And(p.["CategoryIds"].Contains(categoryId))) /// Try to get the next oldest tagged post from the given tagged post let tryFindOlderTaggedPost conn (tag : string) post = - olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn).And(p.["tags"].Contains(tag))) + olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn).And(p.["Tags"].Contains(tag))) /// Get a page of all posts in all statuses let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage = // FIXME: sort unpublished posts by their last updated date r.Table(Table.Post) - .GetAll(webLogId).OptArg("index", "webLogId") - .OrderBy(r.Desc("publishedOn")) + .GetAll(webLogId).OptArg("index", "WebLogId") + .OrderBy(r.Desc("PublishedOn")) .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) .RunListAsync(conn) |> await @@ -96,7 +94,7 @@ let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage = let tryFindPost conn webLogId postId : Post option = match r.Table(Table.Post) .Get(postId) - .Filter(fun p -> p.["webLogId"].Eq(webLogId)) + .Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId))) .RunAtomAsync(conn) |> box with | null -> None @@ -106,27 +104,22 @@ let tryFindPost conn webLogId postId : Post option = // TODO: see if we can make .Merge work for page list even though the attribute is ignored // (needs to be ignored for serialization, but included for deserialization) let tryFindPostByPermalink conn webLogId permalink = - match r.Table(Table.Post) - .GetAll(r.Array(webLogId, permalink)).OptArg("index", "permalink") - .Filter(fun p -> p.["status"].Eq(PostStatus.Published)) - .Without("revisions") - .RunCursorAsync(conn) - |> await - |> Seq.tryHead with - | Some p -> Some { p with categories = r.Table(Table.Category) - .GetAll(p.categoryIds |> List.toArray) - .Without("children") - .OrderBy("name") - .RunListAsync(conn) - |> await - |> Seq.toList - comments = r.Table(Table.Comment) - .GetAll(p.id).OptArg("index", "postId") - .OrderBy("postedOn") - .RunListAsync(conn) - |> await - |> Seq.toList } - | None -> None + r.Table(Table.Post) + .GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") + .Filter(fun p -> p.["Status"].Eq(PostStatus.Published)) + .Without("Revisions") + .Merge(fun p -> r.HashMap("Categories", r.Table(Table.Category) + .GetAll(p.["CategoryIds"]) + .Without("Children") + .OrderBy("Name") + .CoerceTo("array"))) + .Merge(fun p -> r.HashMap("Comments", r.Table(Table.Comment) + .GetAll(p.["Id"]).OptArg("index", "PostId") + .OrderBy("PostedOn") + .CoerceTo("array"))) + .RunCursorAsync(conn) + |> await + |> Seq.tryHead /// Try to find a post by its prior permalink let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = @@ -140,34 +133,34 @@ let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = /// Get a set of posts for RSS let findFeedPosts conn webLogId nbr : (Post * User option) list = - findPageOfPublishedPosts conn webLogId 1 nbr - |> List.map (fun post -> { post with categories = r.Table(Table.Category) - .GetAll(post.categoryIds |> List.toArray) - .OrderBy("name") - .Pluck("id", "name") - .RunListAsync(conn) - |> await - |> Seq.toList }, - (match r.Table(Table.User) - .Get(post.authorId) - .RunAtomAsync(conn) - |> await - |> box with - | null -> None - | user -> Some <| unbox user)) + (publishedPosts webLogId) + .Merge(fun post -> r.HashMap("Categories", r.Table(Table.Category) + .GetAll(post.["CategoryIds"]) + .OrderBy("Name") + .Pluck("Id", "Name") + .CoerceTo("array"))) + |> toPostList conn 1 nbr + |> List.map (fun post -> post, match r.Table(Table.User) + .Get(post.AuthorId) + .RunAtomAsync(conn) + |> await + |> box with + | null -> None + | user -> Some <| unbox user) /// Save a post let savePost conn post = - match post.id with - | "new" -> let newPost = { post with id = string <| System.Guid.NewGuid() } + match post.Id with + | "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() } r.Table(Table.Post) .Insert(newPost) .RunResultAsync(conn) |> ignore - newPost.id + newPost.Id | _ -> r.Table(Table.Post) - .Get(post.id) - .Replace(post) + .Get(post.Id) + .Replace( { post with Categories = List.empty + Comments = List.empty } ) .RunResultAsync(conn) |> ignore - post.id + post.Id diff --git a/src/myWebLog.Data/Rethink.fs b/src/myWebLog.Data/Rethink.fs index 1abb517..2d92d39 100644 --- a/src/myWebLog.Data/Rethink.fs +++ b/src/myWebLog.Data/Rethink.fs @@ -1,4 +1,4 @@ -module myWebLog.Data.Rethink +module MyWebLog.Data.Rethink open RethinkDb.Driver.Ast open RethinkDb.Driver.Net diff --git a/src/myWebLog.Data/SetUp.fs b/src/myWebLog.Data/SetUp.fs index f48c8d1..041f9cd 100644 --- a/src/myWebLog.Data/SetUp.fs +++ b/src/myWebLog.Data/SetUp.fs @@ -1,4 +1,4 @@ -module myWebLog.Data.SetUp +module MyWebLog.Data.SetUp open Rethink open RethinkDb.Driver.Ast @@ -12,17 +12,17 @@ let private logStepDone () = Console.Out.WriteLine (" done.") /// Ensure the myWebLog database exists let checkDatabase (cfg : DataConfig) = logStep "|> Checking database" - let dbs = r.DbList().RunListAsync(cfg.conn) |> await - match dbs.Contains cfg.database with + let dbs = r.DbList().RunListAsync(cfg.Conn) |> await + match dbs.Contains cfg.Database with | true -> () - | _ -> logStepStart (sprintf " %s database not found - creating" cfg.database) - r.DbCreate(cfg.database).RunResultAsync(cfg.conn) |> await |> ignore + | _ -> logStepStart (sprintf " %s database not found - creating" cfg.Database) + r.DbCreate(cfg.Database).RunResultAsync(cfg.Conn) |> await |> ignore logStepDone () /// Ensure all required tables exist let checkTables cfg = logStep "|> Checking tables" - let tables = r.Db(cfg.database).TableList().RunListAsync(cfg.conn) |> await + let tables = r.Db(cfg.Database).TableList().RunListAsync(cfg.Conn) |> await [ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ] |> List.map (fun tbl -> match tables.Contains tbl with | true -> None @@ -30,27 +30,27 @@ let checkTables cfg = |> List.filter (fun create -> create.IsSome) |> List.map (fun create -> create.Value) |> List.iter (fun (tbl, create) -> logStepStart (sprintf " Creating table %s" tbl) - create.RunResultAsync(cfg.conn) |> await |> ignore + create.RunResultAsync(cfg.Conn) |> await |> ignore logStepDone ()) /// Shorthand to get the table -let tbl cfg table = r.Db(cfg.database).Table(table) +let tbl cfg table = r.Db(cfg.Database).Table(table) /// Create the given index let createIndex cfg table (index : string * (ReqlExpr -> obj) option) = let idxName, idxFunc = index logStepStart (sprintf """ Creating index "%s" on table %s""" idxName table) match idxFunc with - | Some f -> (tbl cfg table).IndexCreate(idxName, f).RunResultAsync(cfg.conn) - | None -> (tbl cfg table).IndexCreate(idxName ).RunResultAsync(cfg.conn) + | Some f -> (tbl cfg table).IndexCreate(idxName, f).RunResultAsync(cfg.Conn) + | None -> (tbl cfg table).IndexCreate(idxName ).RunResultAsync(cfg.Conn) |> await |> ignore - (tbl cfg table).IndexWait(idxName).RunAtomAsync(cfg.conn) |> await |> ignore + (tbl cfg table).IndexWait(idxName).RunAtomAsync(cfg.Conn) |> await |> ignore logStepDone () /// Ensure that the given indexes exist, and create them if required let ensureIndexes cfg (indexes : (string * (string * (ReqlExpr -> obj) option) list) list) = let ensureForTable tabl = - let idx = (tbl cfg (fst tabl)).IndexList().RunListAsync(cfg.conn) |> await + let idx = (tbl cfg (fst tabl)).IndexList().RunListAsync(cfg.Conn) |> await snd tabl |> List.iter (fun index -> match idx.Contains (fst index) with | true -> () @@ -68,21 +68,21 @@ let webLogField (name : string) : (ReqlExpr -> obj) option = /// Ensure all the required indexes exist let checkIndexes cfg = logStep "|> Checking indexes" - [ Table.Category, [ "webLogId", None - "slug", webLogField "slug" + [ Table.Category, [ "WebLogId", None + "Slug", webLogField "Slug" ] - Table.Comment, [ "postId", None + Table.Comment, [ "PostId", None ] - Table.Page, [ "webLogId", None - "permalink", webLogField "permalink" + Table.Page, [ "WebLogId", None + "Permalink", webLogField "Permalink" ] - Table.Post, [ "webLogId", None - "webLogAndStatus", webLogField "status" - "permalink", webLogField "permalink" + Table.Post, [ "WebLogId", None + "WebLogAndStatus", webLogField "Status" + "Permalink", webLogField "Permalink" ] - Table.User, [ "userName", None + Table.User, [ "UserName", None ] - Table.WebLog, [ "urlBase", None + Table.WebLog, [ "UrlBase", None ] ] |> ensureIndexes cfg diff --git a/src/myWebLog.Data/Table.fs b/src/myWebLog.Data/Table.fs index 8aec6ec..7191881 100644 --- a/src/myWebLog.Data/Table.fs +++ b/src/myWebLog.Data/Table.fs @@ -1,4 +1,4 @@ -module myWebLog.Data.Table +module MyWebLog.Data.Table /// The Category table let Category = "Category" diff --git a/src/myWebLog.Data/User.fs b/src/myWebLog.Data/User.fs index d1fa356..59ce636 100644 --- a/src/myWebLog.Data/User.fs +++ b/src/myWebLog.Data/User.fs @@ -1,6 +1,6 @@ -module myWebLog.Data.User +module MyWebLog.Data.User -open myWebLog.Entities +open MyWebLog.Entities open Rethink let private r = RethinkDb.Driver.RethinkDB.R @@ -11,8 +11,8 @@ let private r = RethinkDb.Driver.RethinkDB.R // http://rethinkdb.com/docs/secondary-indexes/java/ for more information. let tryUserLogOn conn (email : string) (passwordHash : string) = r.Table(Table.User) - .GetAll(email).OptArg("index", "userName") - .Filter(fun u -> u.["passwordHash"].Eq(passwordHash)) + .GetAll(email).OptArg("index", "UserName") + .Filter(fun u -> u.["PasswordHash"].Eq(passwordHash)) .RunCursorAsync(conn) |> await |> Seq.tryHead diff --git a/src/myWebLog.Data/WebLog.fs b/src/myWebLog.Data/WebLog.fs index 0fd4ada..e2f77d5 100644 --- a/src/myWebLog.Data/WebLog.fs +++ b/src/myWebLog.Data/WebLog.fs @@ -1,43 +1,39 @@ -module myWebLog.Data.WebLog +module MyWebLog.Data.WebLog -open myWebLog.Entities +open MyWebLog.Entities open Rethink +open RethinkDb.Driver.Ast let private r = RethinkDb.Driver.RethinkDB.R /// Counts of items displayed on the admin dashboard -type DashboardCounts = { - /// The number of pages for the web log - pages : int - /// The number of pages for the web log - posts : int - /// The number of categories for the web log - categories : int - } +type DashboardCounts = + { /// The number of pages for the web log + Pages : int + /// The number of pages for the web log + Posts : int + /// The number of categories for the web log + Categories : int } /// Detemine the web log by the URL base -// TODO: see if we can make .Merge work for page list even though the attribute is ignored -// (needs to be ignored for serialization, but included for deserialization) let tryFindWebLogByUrlBase conn (urlBase : string) = - let webLog = r.Table(Table.WebLog) - .GetAll(urlBase).OptArg("index", "urlBase") - .RunCursorAsync(conn) - |> await - |> Seq.tryHead - match webLog with - | Some w -> Some { w with pageList = r.Table(Table.Page) - .GetAll(w.id).OptArg("index", "webLogId") - .Filter(fun pg -> pg.["showInPageList"].Eq(true)) - .OrderBy("title") - .Pluck("title", "permalink") - .RunListAsync(conn) |> await |> Seq.toList } - | None -> None + r.Table(Table.WebLog) + .GetAll(urlBase).OptArg("index", "urlBase") + .Merge(fun w -> r.HashMap("PageList", r.Table(Table.Page) + .GetAll(w.["Id"]).OptArg("index", "WebLogId") + .Filter(ReqlFunction1(fun pg -> upcast pg.["ShowInPageList"].Eq(true))) + .OrderBy("Title") + .Pluck("Title", "Permalink") + .CoerceTo("array"))) + .RunCursorAsync(conn) + |> await + |> Seq.tryHead /// Get counts for the admin dashboard let findDashboardCounts conn (webLogId : string) = - r.Expr( r.HashMap("pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "webLogId").Count())) - .Merge(r.HashMap("posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "webLogId").Count())) - .Merge(r.HashMap("categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "webLogId").Count())) + r.Expr( r.HashMap("Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) + .Merge(r.HashMap("Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) + .Merge(r.HashMap("Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count())) .RunAtomAsync(conn) |> await \ No newline at end of file diff --git a/src/myWebLog.Data/myWebLog.Data.fsproj b/src/myWebLog.Data/myWebLog.Data.fsproj index 4e52d36..9182b36 100644 --- a/src/myWebLog.Data/myWebLog.Data.fsproj +++ b/src/myWebLog.Data/myWebLog.Data.fsproj @@ -8,7 +8,7 @@ 1fba0b84-b09e-4b16-b9b6-5730dea27192 Library myWebLog.Data - myWebLog.Data + MyWebLog.Data v4.5.2 4.4.0.0 true @@ -22,7 +22,7 @@ bin\Debug\ DEBUG;TRACE 3 - bin\Debug\myWebLog.Data.xml + bin\Debug\MyWebLog.Data.xml pdbonly diff --git a/src/myWebLog.Resources/Resources.Designer.cs b/src/myWebLog.Resources/Resources.Designer.cs index aef5363..1d165e9 100644 --- a/src/myWebLog.Resources/Resources.Designer.cs +++ b/src/myWebLog.Resources/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace myWebLog { +namespace MyWebLog { using System; @@ -39,7 +39,7 @@ namespace myWebLog { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("myWebLog.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MyWebLog.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/myWebLog.Resources/myWebLog.Resources.csproj b/src/myWebLog.Resources/myWebLog.Resources.csproj index d9951b1..0d309fb 100644 --- a/src/myWebLog.Resources/myWebLog.Resources.csproj +++ b/src/myWebLog.Resources/myWebLog.Resources.csproj @@ -7,8 +7,8 @@ {A12EA8DA-88BC-4447-90CB-A0E2DCC37523} Library Properties - myWebLog - myWebLog.Resources + MyWebLog + MyWebLog.Resources v4.5.2 512 diff --git a/src/myWebLog.Web/AdminModule.fs b/src/myWebLog.Web/AdminModule.fs index 2fd3695..c89efa7 100644 --- a/src/myWebLog.Web/AdminModule.fs +++ b/src/myWebLog.Web/AdminModule.fs @@ -1,7 +1,7 @@ -namespace myWebLog +namespace MyWebLog -open myWebLog.Data.WebLog -open myWebLog.Entities +open MyWebLog.Data.WebLog +open MyWebLog.Entities open Nancy open RethinkDb.Driver.Net @@ -15,6 +15,6 @@ type AdminModule(conn : IConnection) as this = /// Admin dashboard member this.Dashboard () = this.RequiresAccessLevel AuthorizationLevel.Administrator - let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts conn this.WebLog.id) - model.pageTitle <- Resources.Dashboard + let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts conn this.WebLog.Id) + model.PageTitle <- Resources.Dashboard upcast this.View.["admin/dashboard", model] diff --git a/src/myWebLog.Web/App.fs b/src/myWebLog.Web/App.fs index b021a56..ddd5248 100644 --- a/src/myWebLog.Web/App.fs +++ b/src/myWebLog.Web/App.fs @@ -1,10 +1,10 @@ -module myWebLog.App +module MyWebLog.App -open myWebLog -open myWebLog.Data -open myWebLog.Data.SetUp -open myWebLog.Data.WebLog -open myWebLog.Entities +open MyWebLog +open MyWebLog.Data +open MyWebLog.Data.SetUp +open MyWebLog.Data.WebLog +open MyWebLog.Entities open Nancy open Nancy.Authentication.Forms open Nancy.Bootstrapper @@ -25,7 +25,7 @@ open System.IO open System.Text.RegularExpressions /// Set up a database connection -let cfg = try DataConfig.fromJson (System.IO.File.ReadAllText "data-config.json") +let cfg = try DataConfig.FromJson (System.IO.File.ReadAllText "data-config.json") with ex -> raise <| ApplicationException(Resources.ErrDataConfig, ex) do @@ -37,7 +37,7 @@ type TranslateTokenViewEngineMatcher() = interface ISuperSimpleViewEngineMatcher with member this.Invoke (content, model, host) = regex.Replace(content, fun m -> let key = m.Groups.["TranslationKey"].Value - match myWebLog.Resources.ResourceManager.GetString key with + match MyWebLog.Resources.ResourceManager.GetString key with | null -> key | xlat -> xlat) @@ -54,8 +54,8 @@ type MyWebLogUserMapper(container : TinyIoCContainer) = interface IUserMapper with member this.GetUserFromIdentifier (identifier, context) = - match context.Request.PersistableSession.GetOrDefault(Keys.User, User.empty) with - | user when user.id = string identifier -> upcast MyWebLogUser(user.preferredName, user.claims) + match context.Request.PersistableSession.GetOrDefault(Keys.User, User.Empty) with + | user when user.Id = string identifier -> upcast MyWebLogUser(user.PreferredName, user.Claims) | _ -> null @@ -87,7 +87,7 @@ type MyWebLogBootstrapper() = // Data configuration (both config and the connection; Nancy modules just need the connection) container.Register(cfg) |> ignore - container.Register(cfg.conn) + container.Register(cfg.Conn) |> ignore // NodaTime container.Register(SystemClock.Instance) @@ -109,8 +109,8 @@ type MyWebLogBootstrapper() = // CSRF Csrf.Enable pipelines // Sessions - let sessions = RethinkDbSessionConfiguration(cfg.conn) - sessions.Database <- cfg.database + let sessions = RethinkDbSessionConfiguration(cfg.Conn) + sessions.Database <- cfg.Database PersistableSessions.Enable (pipelines, sessions) () @@ -128,17 +128,18 @@ let version = type RequestEnvironment() = interface IRequestStartup with member this.Initialize (pipelines, context) = - pipelines.BeforeRequest.AddItemToStartOfPipeline - (fun ctx -> ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks - match tryFindWebLogByUrlBase cfg.conn ctx.Request.Url.HostName with - | Some webLog -> ctx.Items.[Keys.WebLog] <- webLog - | None -> ApplicationException - (sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured) - |> raise - ctx.Items.[Keys.Version] <- version - null) + let establishEnv (ctx : NancyContext) = + ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks + match tryFindWebLogByUrlBase cfg.Conn ctx.Request.Url.HostName with + | Some webLog -> ctx.Items.[Keys.WebLog] <- webLog + | None -> // TODO: redirect to domain set up page + ApplicationException (sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured) + |> raise + ctx.Items.[Keys.Version] <- version + null + pipelines.BeforeRequest.AddItemToStartOfPipeline establishEnv let app = OwinApp.ofMidFunc "/" (NancyMiddleware.UseNancy (NancyOptions())) -let run () = startWebServer defaultConfig app // webPart +let Run () = startWebServer defaultConfig app // webPart diff --git a/src/myWebLog.Web/AssemblyInfo.fs b/src/myWebLog.Web/AssemblyInfo.fs index ea73017..24c3209 100644 --- a/src/myWebLog.Web/AssemblyInfo.fs +++ b/src/myWebLog.Web/AssemblyInfo.fs @@ -4,11 +4,11 @@ open System.Reflection open System.Runtime.CompilerServices open System.Runtime.InteropServices -[] +[] [] [] [] -[] +[] [] [] [] diff --git a/src/myWebLog.Web/CategoryModule.fs b/src/myWebLog.Web/CategoryModule.fs index d72dfa8..a3352d6 100644 --- a/src/myWebLog.Web/CategoryModule.fs +++ b/src/myWebLog.Web/CategoryModule.fs @@ -1,7 +1,7 @@ -namespace myWebLog +namespace MyWebLog -open myWebLog.Data.Category -open myWebLog.Entities +open MyWebLog.Data.Category +open MyWebLog.Entities open Nancy open Nancy.ModelBinding open Nancy.Security @@ -21,24 +21,21 @@ type CategoryModule(conn : IConnection) as this = member this.CategoryList () = this.RequiresAccessLevel AuthorizationLevel.Administrator let model = CategoryListModel(this.Context, this.WebLog, - (getAllCategories conn this.WebLog.id - |> List.map (fun cat -> IndentedCategory.create cat (fun _ -> false)))) + (getAllCategories conn this.WebLog.Id + |> List.map (fun cat -> IndentedCategory.Create cat (fun _ -> false)))) upcast this.View.["/admin/category/list", model] /// Edit a category member this.EditCategory (parameters : DynamicDictionary) = this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId : string = downcast parameters.["id"] + let catId = parameters.["id"].ToString () match (match catId with | "new" -> Some Category.empty - | _ -> tryFindCategory conn this.WebLog.id catId) with + | _ -> tryFindCategory conn this.WebLog.Id catId) with | Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat) - let cats = getAllCategories conn this.WebLog.id - |> List.map (fun cat -> IndentedCategory.create cat - (fun c -> c = defaultArg (fst cat).parentId "")) - model.categories <- getAllCategories conn this.WebLog.id - |> List.map (fun cat -> IndentedCategory.create cat - (fun c -> c = defaultArg (fst cat).parentId "")) + model.Categories <- getAllCategories conn this.WebLog.Id + |> List.map (fun cat -> IndentedCategory.Create cat + (fun c -> c = defaultArg (fst cat).ParentId "")) upcast this.View.["admin/category/edit", model] | None -> this.NotFound () @@ -46,32 +43,32 @@ type CategoryModule(conn : IConnection) as this = member this.SaveCategory (parameters : DynamicDictionary) = this.ValidateCsrfToken () this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId : string = downcast parameters.["id"] + let catId = parameters.["id"].ToString () let form = this.Bind () let oldCat = match catId with | "new" -> Some Category.empty - | _ -> tryFindCategory conn this.WebLog.id catId + | _ -> tryFindCategory conn this.WebLog.Id catId match oldCat with - | Some old -> let cat = { old with name = form.name - slug = form.slug - description = match form.description with | "" -> None | d -> Some d - parentId = match form.parentId with | "" -> None | p -> Some p } - let newCatId = saveCategory conn this.WebLog.id cat - match old.parentId = cat.parentId with + | Some old -> let cat = { old with Name = form.Name + Slug = form.Slug + Description = match form.Description with "" -> None | d -> Some d + ParentId = match form.ParentId with "" -> None | p -> Some p } + let newCatId = saveCategory conn this.WebLog.Id cat + match old.ParentId = cat.ParentId with | true -> () - | _ -> match old.parentId with - | Some parentId -> removeCategoryFromParent conn this.WebLog.id parentId newCatId + | _ -> match old.ParentId with + | Some parentId -> removeCategoryFromParent conn this.WebLog.Id parentId newCatId | None -> () - match cat.parentId with - | Some parentId -> addCategoryToParent conn this.WebLog.id parentId newCatId + match cat.ParentId with + | Some parentId -> addCategoryToParent conn this.WebLog.Id parentId newCatId | None -> () let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = System.String.Format - (Resources.MsgCategoryEditSuccess, - (match catId with | "new" -> Resources.Added | _ -> Resources.Updated)) - details = None } - |> model.addMessage + { UserMessage.Empty with + Level = Level.Info + Message = System.String.Format + (Resources.MsgCategoryEditSuccess, + (match catId with | "new" -> Resources.Added | _ -> Resources.Updated)) } + |> model.AddMessage this.Redirect (sprintf "/category/%s/edit" newCatId) model | None -> this.NotFound () @@ -79,13 +76,12 @@ type CategoryModule(conn : IConnection) as this = member this.DeleteCategory (parameters : DynamicDictionary) = this.ValidateCsrfToken () this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId : string = downcast parameters.["id"] - match tryFindCategory conn this.WebLog.id catId with + let catId = parameters.["id"].ToString () + match tryFindCategory conn this.WebLog.Id catId with | Some cat -> deleteCategory conn cat let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = System.String.Format(Resources.MsgCategoryDeleted, cat.name) - details = None } - |> model.addMessage + { UserMessage.Empty with Level = Level.Info + Message = System.String.Format(Resources.MsgCategoryDeleted, cat.Name) } + |> model.AddMessage this.Redirect "/categories" model | None -> this.NotFound () diff --git a/src/myWebLog.Web/Keys.fs b/src/myWebLog.Web/Keys.fs index 282a3b7..18bc4e6 100644 --- a/src/myWebLog.Web/Keys.fs +++ b/src/myWebLog.Web/Keys.fs @@ -1,4 +1,4 @@ -module myWebLog.Keys +module MyWebLog.Keys let Messages = "messages" diff --git a/src/myWebLog.Web/ModuleExtensions.fs b/src/myWebLog.Web/ModuleExtensions.fs index e5deb9c..5b412b8 100644 --- a/src/myWebLog.Web/ModuleExtensions.fs +++ b/src/myWebLog.Web/ModuleExtensions.fs @@ -1,8 +1,7 @@ [] -module myWebLog.ModuleExtensions +module MyWebLog.ModuleExtensions -open myWebLog -open myWebLog.Entities +open MyWebLog.Entities open Nancy open Nancy.Security @@ -14,19 +13,19 @@ type NancyModule with /// Display a view using the theme specified for the web log member this.ThemedView view (model : MyWebLogModel) : obj = - upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.themePath view), model] + upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.ThemePath view), model] /// Return a 404 member this.NotFound () : obj = upcast HttpStatusCode.NotFound /// Redirect a request, storing messages in the session if they exist member this.Redirect url (model : MyWebLogModel) : obj = - match List.length model.messages with + match List.length model.Messages with | 0 -> () - | _ -> this.Session.[Keys.Messages] <- model.messages + | _ -> this.Session.[Keys.Messages] <- model.Messages upcast this.Response.AsRedirect(url).WithStatusCode HttpStatusCode.TemporaryRedirect /// Require a specific level of access for the current web log member this.RequiresAccessLevel level = this.RequiresAuthentication() - this.RequiresClaims [| sprintf "%s|%s" this.WebLog.id level |] + this.RequiresClaims [| sprintf "%s|%s" this.WebLog.Id level |] diff --git a/src/myWebLog.Web/PageModule.fs b/src/myWebLog.Web/PageModule.fs index 9992b36..40fba18 100644 --- a/src/myWebLog.Web/PageModule.fs +++ b/src/myWebLog.Web/PageModule.fs @@ -1,8 +1,8 @@ -namespace myWebLog +namespace MyWebLog open FSharp.Markdown -open myWebLog.Data.Page -open myWebLog.Entities +open MyWebLog.Data.Page +open MyWebLog.Entities open Nancy open Nancy.ModelBinding open Nancy.Security @@ -22,9 +22,9 @@ type PageModule(conn : IConnection, clock : IClock) as this = /// List all pages member this.PageList () = 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] /// Edit a page @@ -32,17 +32,15 @@ type PageModule(conn : IConnection, clock : IClock) as this = this.RequiresAccessLevel AuthorizationLevel.Administrator let pageId = parameters.["id"].ToString () match (match pageId with - | "new" -> Some Page.empty - | _ -> tryFindPage conn this.WebLog.id pageId) with - | Some page -> let rev = match page.revisions - |> List.sortByDescending (fun r -> r.asOf) + | "new" -> Some Page.Empty + | _ -> tryFindPage conn this.WebLog.Id pageId) with + | Some page -> let rev = match page.Revisions + |> List.sortByDescending (fun r -> r.AsOf) |> List.tryHead with | Some r -> r - | None -> Revision.empty + | None -> Revision.Empty let model = EditPageModel(this.Context, this.WebLog, page, rev) - model.pageTitle <- match pageId with - | "new" -> Resources.AddNewPage - | _ -> Resources.EditPage + model.PageTitle <- match pageId with "new" -> Resources.AddNewPage | _ -> Resources.EditPage upcast this.View.["admin/page/edit", model] | None -> this.NotFound () @@ -54,30 +52,28 @@ type PageModule(conn : IConnection, clock : IClock) as this = let form = this.Bind () let now = clock.Now.Ticks match (match pageId with - | "new" -> Some Page.empty - | _ -> tryFindPage conn this.WebLog.id pageId) with - | Some p -> let page = match pageId with - | "new" -> { p with webLogId = this.WebLog.id } - | _ -> p + | "new" -> Some Page.Empty + | _ -> tryFindPage conn this.WebLog.Id pageId) with + | Some p -> let page = match pageId with "new" -> { p with WebLogId = this.WebLog.Id } | _ -> p let pId = { p with - title = form.title - permalink = form.permalink - publishedOn = match pageId with | "new" -> now | _ -> page.publishedOn - updatedOn = now - text = match form.source with - | RevisionSource.Markdown -> Markdown.TransformHtml form.text - | _ -> form.text - revisions = { asOf = now - sourceType = form.source - text = form.text } :: page.revisions } + Title = form.Title + Permalink = form.Permalink + PublishedOn = match pageId with "new" -> now | _ -> page.PublishedOn + UpdatedOn = now + Text = match form.Source with + | RevisionSource.Markdown -> Markdown.TransformHtml form.Text + | _ -> form.Text + Revisions = { AsOf = now + SourceType = form.Source + Text = form.Text } :: page.Revisions } |> savePage conn let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = System.String.Format - (Resources.MsgPageEditSuccess, - (match pageId with | "new" -> Resources.Added | _ -> Resources.Updated)) - details = None } - |> model.addMessage + { UserMessage.Empty with + Level = Level.Info + Message = System.String.Format + (Resources.MsgPageEditSuccess, + (match pageId with | "new" -> Resources.Added | _ -> Resources.Updated)) } + |> model.AddMessage this.Redirect (sprintf "/page/%s/edit" pId) model | None -> this.NotFound () @@ -86,12 +82,11 @@ type PageModule(conn : IConnection, clock : IClock) as this = this.ValidateCsrfToken () this.RequiresAccessLevel AuthorizationLevel.Administrator let pageId = parameters.["id"].ToString () - match tryFindPageWithoutRevisions conn this.WebLog.id pageId with - | Some page -> deletePage conn page.webLogId page.id + match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with + | Some page -> deletePage conn page.WebLogId page.Id let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = Resources.MsgPageDeleted - details = None } - |> model.addMessage + { UserMessage.Empty with Level = Level.Info + Message = Resources.MsgPageDeleted } + |> model.AddMessage this.Redirect "/pages" model | None -> this.NotFound () diff --git a/src/myWebLog.Web/PostModule.fs b/src/myWebLog.Web/PostModule.fs index 162158a..a994fc2 100644 --- a/src/myWebLog.Web/PostModule.fs +++ b/src/myWebLog.Web/PostModule.fs @@ -1,10 +1,10 @@ -namespace myWebLog +namespace MyWebLog open FSharp.Markdown -open myWebLog.Data.Category -open myWebLog.Data.Page -open myWebLog.Data.Post -open myWebLog.Entities +open MyWebLog.Data.Category +open MyWebLog.Data.Page +open MyWebLog.Data.Post +open MyWebLog.Entities open Nancy open Nancy.ModelBinding open Nancy.Security @@ -20,39 +20,39 @@ type PostModule(conn : IConnection, clock : IClock) as this = /// Get the page number from the dictionary let getPage (parameters : DynamicDictionary) = - match parameters.ContainsKey "page" with | true -> System.Int32.Parse (parameters.["page"].ToString ()) | _ -> 1 + match parameters.ContainsKey "page" with true -> System.Int32.Parse (parameters.["page"].ToString ()) | _ -> 1 /// Convert a list of posts to a list of posts for display 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 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, + 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 () + | 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)) + 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))) + (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 + |> match format with "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20 stream.Position <- int64 0 upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format) @@ -75,77 +75,77 @@ type PostModule(conn : IConnection, clock : IClock) as this = /// Display a page of published posts member this.PublishedPostsPage pageNbr = let model = PostsModel(this.Context, this.WebLog) - model.pageNbr <- pageNbr - model.posts <- findPageOfPublishedPosts conn this.WebLog.id pageNbr 10 |> forDisplay - model.hasNewer <- match pageNbr with + model.PageNbr <- pageNbr + model.Posts <- findPageOfPublishedPosts conn this.WebLog.Id pageNbr 10 |> forDisplay + model.HasNewer <- match pageNbr with | 1 -> false - | _ -> match List.isEmpty model.posts with + | _ -> match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindNewerPost conn (List.last model.posts).post - model.hasOlder <- match List.isEmpty model.posts with + | _ -> Option.isSome <| tryFindNewerPost conn (List.last model.Posts).Post + model.HasOlder <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindOlderPost conn (List.head model.posts).post - model.urlPrefix <- "/posts" - model.pageTitle <- match pageNbr with + | _ -> Option.isSome <| tryFindOlderPost conn (List.head model.Posts).Post + model.UrlPrefix <- "/posts" + model.PageTitle <- match pageNbr with | 1 -> "" | _ -> sprintf "%s%i" Resources.PageHash pageNbr this.ThemedView "index" model /// Display either the newest posts or the configured home page member this.HomePage () = - match this.WebLog.defaultPage with + match this.WebLog.DefaultPage with | "posts" -> this.PublishedPostsPage 1 - | pageId -> match tryFindPageWithoutRevisions conn this.WebLog.id pageId with + | pageId -> match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with | Some page -> let model = PageModel(this.Context, this.WebLog, page) - model.pageTitle <- page.title + model.PageTitle <- page.Title this.ThemedView "page" model | None -> this.NotFound () /// Derive a post or page from the URL, or redirect from a prior URL to the current one member this.CatchAll (parameters : DynamicDictionary) = let url = parameters.["permalink"].ToString () - match tryFindPostByPermalink conn this.WebLog.id url with + match tryFindPostByPermalink conn this.WebLog.Id url with | Some post -> // Hopefully the most common result; the permalink is a permalink! let model = PostModel(this.Context, this.WebLog, post) - model.newerPost <- tryFindNewerPost conn post - model.olderPost <- tryFindOlderPost conn post - model.pageTitle <- post.title + model.NewerPost <- tryFindNewerPost conn post + model.OlderPost <- tryFindOlderPost conn post + model.PageTitle <- post.Title this.ThemedView "single" model | None -> // Maybe it's a page permalink instead... - match tryFindPageByPermalink conn this.WebLog.id url with + match tryFindPageByPermalink conn this.WebLog.Id url with | Some page -> // ...and it is! let model = PageModel(this.Context, this.WebLog, page) - model.pageTitle <- page.title + model.PageTitle <- page.Title this.ThemedView "page" model | None -> // Maybe it's an old permalink for a post - match tryFindPostByPriorPermalink conn this.WebLog.id url with + match tryFindPostByPriorPermalink conn this.WebLog.Id url with | Some post -> // Redirect them to the proper permalink - upcast this.Response.AsRedirect(sprintf "/%s" post.permalink) + upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink) .WithStatusCode HttpStatusCode.MovedPermanently | None -> this.NotFound () /// Display categorized posts member this.CategorizedPosts (parameters : DynamicDictionary) = let slug = parameters.["slug"].ToString () - match tryFindCategoryBySlug conn this.WebLog.id slug with + match tryFindCategoryBySlug conn this.WebLog.Id slug with | Some cat -> let pageNbr = getPage parameters let model = PostsModel(this.Context, this.WebLog) - model.pageNbr <- pageNbr - model.posts <- findPageOfCategorizedPosts conn this.WebLog.id cat.id pageNbr 10 |> forDisplay - model.hasNewer <- match List.isEmpty model.posts with + model.PageNbr <- pageNbr + model.Posts <- findPageOfCategorizedPosts conn this.WebLog.Id cat.Id pageNbr 10 |> forDisplay + model.HasNewer <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.id - (List.head model.posts).post - model.hasOlder <- match List.isEmpty model.posts with + | _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.Id + (List.head model.Posts).Post + model.HasOlder <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.id - (List.last model.posts).post - model.urlPrefix <- sprintf "/category/%s" slug - model.pageTitle <- sprintf "\"%s\" Category%s" cat.name + | _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.Id + (List.last model.Posts).Post + model.UrlPrefix <- sprintf "/category/%s" slug + model.PageTitle <- sprintf "\"%s\" Category%s" cat.Name (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n) - model.subtitle <- Some <| match cat.description with + model.Subtitle <- Some <| match cat.Description with | Some desc -> desc - | None -> sprintf "Posts in the \"%s\" category" cat.name + | None -> sprintf "Posts in the \"%s\" category" cat.Name this.ThemedView "index" model | None -> this.NotFound () @@ -154,17 +154,17 @@ type PostModule(conn : IConnection, clock : IClock) as this = let tag = parameters.["tag"].ToString () let pageNbr = getPage parameters let model = PostsModel(this.Context, this.WebLog) - model.pageNbr <- pageNbr - model.posts <- findPageOfTaggedPosts conn this.WebLog.id tag pageNbr 10 |> forDisplay - model.hasNewer <- match List.isEmpty model.posts with + model.PageNbr <- pageNbr + model.Posts <- findPageOfTaggedPosts conn this.WebLog.Id tag pageNbr 10 |> forDisplay + model.HasNewer <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindNewerTaggedPost conn tag (List.head model.posts).post - model.hasOlder <- match List.isEmpty model.posts with + | _ -> Option.isSome <| tryFindNewerTaggedPost conn tag (List.head model.Posts).Post + model.HasOlder <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindOlderTaggedPost conn tag (List.last model.posts).post - model.urlPrefix <- sprintf "/tag/%s" tag - model.pageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n) - model.subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag + | _ -> Option.isSome <| tryFindOlderTaggedPost conn tag (List.last model.Posts).Post + model.UrlPrefix <- sprintf "/tag/%s" tag + model.PageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n) + model.Subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag this.ThemedView "index" model /// Generate an RSS feed @@ -183,35 +183,33 @@ type PostModule(conn : IConnection, clock : IClock) as this = member this.PostList pageNbr = this.RequiresAccessLevel AuthorizationLevel.Administrator let model = PostsModel(this.Context, this.WebLog) - model.pageNbr <- pageNbr - model.posts <- findPageOfAllPosts conn this.WebLog.id pageNbr 25 |> forDisplay - model.hasNewer <- pageNbr > 1 - model.hasOlder <- List.length model.posts > 24 - model.urlPrefix <- "/posts/list" - model.pageTitle <- Resources.Posts + model.PageNbr <- pageNbr + model.Posts <- findPageOfAllPosts conn this.WebLog.Id pageNbr 25 |> forDisplay + model.HasNewer <- pageNbr > 1 + model.HasOlder <- List.length model.Posts > 24 + model.UrlPrefix <- "/posts/list" + model.PageTitle <- Resources.Posts upcast this.View.["admin/post/list", model] /// Edit a post member this.EditPost (parameters : DynamicDictionary) = this.RequiresAccessLevel AuthorizationLevel.Administrator - let postId : string = downcast parameters.["postId"] + let postId = parameters.["postId"].ToString () match (match postId with - | "new" -> Some Post.empty - | _ -> tryFindPost conn this.WebLog.id postId) with - | Some post -> let rev = match post.revisions - |> List.sortByDescending (fun r -> r.asOf) + | "new" -> Some Post.Empty + | _ -> tryFindPost conn this.WebLog.Id postId) with + | Some post -> let rev = match post.Revisions + |> List.sortByDescending (fun r -> r.AsOf) |> List.tryHead with | Some r -> r - | None -> Revision.empty + | None -> Revision.Empty let model = EditPostModel(this.Context, this.WebLog, post, rev) - model.categories <- getAllCategories conn this.WebLog.id - |> List.map (fun cat -> string (fst cat).id, + model.Categories <- getAllCategories conn this.WebLog.Id + |> List.map (fun cat -> string (fst cat).Id, sprintf "%s%s" (String.replicate (snd cat) "     ") - (fst cat).name) - model.pageTitle <- match post.id with - | "new" -> Resources.AddNewPost - | _ -> Resources.EditPost + (fst cat).Name) + model.PageTitle <- match post.Id with "new" -> Resources.AddNewPost | _ -> Resources.EditPost upcast this.View.["admin/post/edit"] | None -> this.NotFound () @@ -219,45 +217,45 @@ type PostModule(conn : IConnection, clock : IClock) as this = member this.SavePost (parameters : DynamicDictionary) = this.RequiresAccessLevel AuthorizationLevel.Administrator this.ValidateCsrfToken () - let postId : string = downcast parameters.["postId"] - let form = this.Bind() - let now = clock.Now.Ticks + let postId = parameters.["postId"].ToString () + let form = this.Bind() + let now = clock.Now.Ticks match (match postId with - | "new" -> Some Post.empty - | _ -> tryFindPost conn this.WebLog.id postId) with - | Some p -> let justPublished = p.publishedOn = int64 0 && form.publishNow + | "new" -> Some Post.Empty + | _ -> tryFindPost conn this.WebLog.Id postId) with + | Some p -> let justPublished = p.PublishedOn = int64 0 && form.PublishNow let post = match postId with | "new" -> { p with - webLogId = this.WebLog.id - authorId = (this.Request.PersistableSession.GetOrDefault - (Keys.User, User.empty)).id } - | _ -> p + WebLogId = this.WebLog.Id + AuthorId = (this.Request.PersistableSession.GetOrDefault + (Keys.User, User.Empty)).Id } + | _ -> p let pId = { post with - status = match form.publishNow with + Status = match form.PublishNow with | true -> PostStatus.Published - | _ -> PostStatus.Draft - title = form.title - permalink = form.permalink - publishedOn = match justPublished with | true -> now | _ -> int64 0 - updatedOn = now - text = match form.source with - | RevisionSource.Markdown -> Markdown.TransformHtml form.text - | _ -> form.text - categoryIds = Array.toList form.categories - tags = form.tags.Split ',' + | _ -> PostStatus.Draft + Title = form.Title + Permalink = form.Permalink + PublishedOn = match justPublished with true -> now | _ -> int64 0 + UpdatedOn = now + Text = match form.Source with + | RevisionSource.Markdown -> Markdown.TransformHtml form.Text + | _ -> form.Text + CategoryIds = Array.toList form.Categories + Tags = form.Tags.Split ',' |> Seq.map (fun t -> t.Trim().ToLowerInvariant()) |> Seq.toList - revisions = { asOf = now - sourceType = form.source - text = form.text } :: post.revisions } + Revisions = { AsOf = now + SourceType = form.Source + Text = form.Text } :: post.Revisions } |> savePost conn let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = System.String.Format - (Resources.MsgPostEditSuccess, - (match postId with | "new" -> Resources.Added | _ -> Resources.Updated), - (match justPublished with | true -> Resources.AndPublished | _ -> "")) - details = None } - |> model.addMessage + { UserMessage.Empty with + Level = Level.Info + Message = System.String.Format + (Resources.MsgPostEditSuccess, + (match postId with | "new" -> Resources.Added | _ -> Resources.Updated), + (match justPublished with | true -> Resources.AndPublished | _ -> "")) } + |> model.AddMessage this.Redirect (sprintf "/post/%s/edit" pId) model | None -> this.NotFound () diff --git a/src/myWebLog.Web/UserModule.fs b/src/myWebLog.Web/UserModule.fs index 5ef73ed..7e6fb76 100644 --- a/src/myWebLog.Web/UserModule.fs +++ b/src/myWebLog.Web/UserModule.fs @@ -1,7 +1,7 @@ -namespace myWebLog +namespace MyWebLog -open myWebLog.Data.User -open myWebLog.Entities +open MyWebLog.Data.User +open MyWebLog.Entities open Nancy open Nancy.Authentication.Forms open Nancy.Cryptography @@ -21,15 +21,16 @@ type UserModule(conn : IConnection) as this = |> Seq.fold (fun acc byt -> sprintf "%s%s" acc (byt.ToString "x2")) "" do - this.Get .["/logon" ] <- fun parms -> this.ShowLogOn (downcast parms) + this.Get .["/logon" ] <- fun _ -> this.ShowLogOn () this.Post.["/logon" ] <- fun parms -> this.DoLogOn (downcast parms) - this.Get .["/logoff"] <- fun parms -> this.LogOff () + this.Get .["/logoff"] <- fun _ -> this.LogOff () /// Show the log on page - member this.ShowLogOn (parameters : DynamicDictionary) = + member this.ShowLogOn () = let model = LogOnModel(this.Context, this.WebLog) - model.form.returnUrl <- match parameters.ContainsKey "returnUrl" with - | true -> parameters.["returnUrl"].ToString () + let query = this.Request.Query :?> DynamicDictionary + model.Form.ReturnUrl <- match query.ContainsKey "returnUrl" with + | true -> query.["returnUrl"].ToString () | _ -> "" upcast this.View.["admin/user/logon", model] @@ -38,30 +39,28 @@ type UserModule(conn : IConnection) as this = this.ValidateCsrfToken () let form = this.Bind () let model = MyWebLogModel(this.Context, this.WebLog) - match tryUserLogOn conn form.email (pbkdf2 form.password) with + match tryUserLogOn conn form.Email (pbkdf2 form.Password) with | Some user -> this.Session.[Keys.User] <- user - { level = Level.Info - message = Resources.MsgLogOnSuccess - details = None } - |> model.addMessage + { UserMessage.Empty with Level = Level.Info + Message = Resources.MsgLogOnSuccess } + |> model.AddMessage this.Redirect "" model |> ignore // Save the messages in the session before the Nancy redirect // TODO: investigate if addMessage should update the session when it's called - upcast this.LoginAndRedirect (System.Guid.Parse user.id, - fallbackRedirectUrl = defaultArg (Option.ofObj(form.returnUrl)) "/") - | None -> { level = Level.Error - message = Resources.ErrBadLogOnAttempt - details = None } - |> model.addMessage - this.Redirect (sprintf "/user/logon?returnUrl=%s" form.returnUrl) model + upcast this.LoginAndRedirect (System.Guid.Parse user.Id, + fallbackRedirectUrl = defaultArg (Option.ofObj form.ReturnUrl) "/") + | None -> { UserMessage.Empty with Level = Level.Error + Message = Resources.ErrBadLogOnAttempt } + |> model.AddMessage + this.Redirect (sprintf "/user/logon?returnUrl=%s" form.ReturnUrl) model /// Log a user off member this.LogOff () = - let user = this.Request.PersistableSession.GetOrDefault (Keys.User, User.empty) + // FIXME: why are we getting the user here if we don't do anything with it? + let user = this.Request.PersistableSession.GetOrDefault (Keys.User, User.Empty) this.Session.DeleteAll () let model = MyWebLogModel(this.Context, this.WebLog) - { level = Level.Info - message = Resources.MsgLogOffSuccess - details = None } - |> model.addMessage + { UserMessage.Empty with Level = Level.Info + Message = Resources.MsgLogOffSuccess } + |> model.AddMessage this.Redirect "" model |> ignore upcast this.LogoutAndRedirect "/" diff --git a/src/myWebLog.Web/ViewModels.fs b/src/myWebLog.Web/ViewModels.fs index 3210117..093bb7b 100644 --- a/src/myWebLog.Web/ViewModels.fs +++ b/src/myWebLog.Web/ViewModels.fs @@ -1,7 +1,7 @@ -namespace myWebLog +namespace MyWebLog -open myWebLog.Data.WebLog -open myWebLog.Entities +open MyWebLog.Data.WebLog +open MyWebLog.Entities open Nancy open Nancy.Session.Persistable open Newtonsoft.Json @@ -21,25 +21,23 @@ module Level = /// A message for the user -type UserMessage = { - /// The level of the message (use Level module constants) - level : string - /// The text of the message - message : string - /// Further details regarding the message - details : string option - } +type UserMessage = + { /// The level of the message (use Level module constants) + Level : string + /// The text of the message + Message : string + /// Further details regarding the message + Details : string option } with /// An empty message - static member empty = { - level = Level.Info - message = "" - details = None - } + static member Empty = + { Level = Level.Info + Message = "" + Details = None } /// Display version [] - member this.toDisplay = + member this.ToDisplay = let classAndLabel = dict [ Level.Error, ("danger", Resources.Error) @@ -48,23 +46,23 @@ with ] seq { yield "
" - match snd classAndLabel.[this.level] with + match snd classAndLabel.[this.Level] with | "" -> () | lbl -> yield lbl.ToUpper () yield " » " - yield this.message + yield this.Message yield "" - match this.details with + match this.Details with | Some d -> yield "
" yield d | None -> () yield "
" } - |> Seq.reduce (fun acc x -> acc + x) + |> Seq.reduce (+) /// Helpers to format local date/time using NodaTime @@ -94,58 +92,58 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) = /// Get the messages from the session let getMessages () = - let msg = ctx.Request.PersistableSession.GetOrDefault(Keys.Messages, List.empty) + let msg = ctx.Request.PersistableSession.GetOrDefault(Keys.Messages, []) match List.length msg with | 0 -> () | _ -> ctx.Request.Session.Delete Keys.Messages msg /// The web log for this request - member this.webLog = webLog + member this.WebLog = webLog /// The subtitle for the webLog (SSVE can't do IsSome that deep) - member this.webLogSubtitle = defaultArg this.webLog.subtitle "" + member this.WebLogSubtitle = defaultArg this.WebLog.Subtitle "" /// User messages - member val messages = getMessages () with get, set + member val Messages = getMessages () with get, set /// The currently logged in user - member this.user = ctx.Request.PersistableSession.GetOrDefault(Keys.User, User.empty) + member this.User = ctx.Request.PersistableSession.GetOrDefault(Keys.User, User.Empty) /// The title of the page - member val pageTitle = "" with get, set + member val PageTitle = "" with get, set /// The name and version of the application - member this.generator = sprintf "myWebLog %s" (ctx.Items.[Keys.Version].ToString ()) + member this.Generator = sprintf "myWebLog %s" (ctx.Items.[Keys.Version].ToString ()) /// The request start time - member this.requestStart = ctx.Items.[Keys.RequestStart] :?> int64 + member this.RequestStart = ctx.Items.[Keys.RequestStart] :?> int64 /// Is a user authenticated for this request? - member this.isAuthenticated = "" <> this.user.id + member this.IsAuthenticated = "" <> this.User.Id /// Add a message to the output - member this.addMessage message = this.messages <- message :: this.messages + member this.AddMessage message = this.Messages <- message :: this.Messages /// Display a long date - member this.displayLongDate ticks = FormatDateTime.longDate this.webLog.timeZone ticks + member this.DisplayLongDate ticks = FormatDateTime.longDate this.WebLog.TimeZone ticks /// Display a short date - member this.displayShortDate ticks = FormatDateTime.shortDate this.webLog.timeZone ticks + member this.DisplayShortDate ticks = FormatDateTime.shortDate this.WebLog.TimeZone ticks /// Display the time - member this.displayTime ticks = FormatDateTime.time this.webLog.timeZone ticks + member this.DisplayTime ticks = FormatDateTime.time this.WebLog.TimeZone ticks /// The page title with the web log name appended - member this.displayPageTitle = - match this.pageTitle with - | "" -> match this.webLog.subtitle with - | Some st -> sprintf "%s | %s" this.webLog.name st - | None -> this.webLog.name - | pt -> sprintf "%s | %s" pt this.webLog.name + member this.DisplayPageTitle = + match this.PageTitle with + | "" -> match this.WebLog.Subtitle with + | Some st -> sprintf "%s | %s" this.WebLog.Name st + | None -> this.WebLog.Name + | pt -> sprintf "%s | %s" pt this.WebLog.Name /// An image with the version and load time in the tool tip - member this.footerLogo = + member this.FooterLogo = seq { yield "\"myWebLog\"" } - |> Seq.reduce (fun acc x -> acc + x) + |> Seq.reduce (+) // ---- Admin models ---- @@ -154,68 +152,67 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) = type DashboardModel(ctx, webLog, counts : DashboardCounts) = inherit MyWebLogModel(ctx, webLog) /// The number of posts for the current web log - member val posts = counts.posts with get, set + member val Posts = counts.Posts with get, set /// The number of pages for the current web log - member val pages = counts.pages with get, set + member val Pages = counts.Pages with get, set /// The number of categories for the current web log - member val categories = counts.categories with get, set + member val Categories = counts.Categories with get, set // ---- Category models ---- -type IndentedCategory = { - category : Category - indent : int - selected : bool - } +type IndentedCategory = + { Category : Category + Indent : int + Selected : bool } with /// Create an indented category - static member create (cat : Category * int) (isSelected : string -> bool) = - { category = fst cat - indent = snd cat - selected = isSelected (fst cat).id } + static member Create (cat : Category * int) (isSelected : string -> bool) = + { Category = fst cat + Indent = snd cat + Selected = isSelected (fst cat).Id } /// Display name for a category on the list page, complete with indents - member this.listName = sprintf "%s%s" (String.replicate this.indent " »   ") this.category.name + member this.ListName = sprintf "%s%s" (String.replicate this.Indent " »   ") this.Category.Name /// Display for this category as an option within a select box - member this.option = + member this.Option = seq { - yield sprintf "" } |> String.concat "" /// Does the category have a description? - member this.hasDescription = this.category.description.IsSome + member this.HasDescription = this.Category.Description.IsSome /// Model for the list of categories type CategoryListModel(ctx, webLog, categories) = inherit MyWebLogModel(ctx, webLog) /// The categories - member this.categories : IndentedCategory list = categories + member this.Categories : IndentedCategory list = categories /// Form for editing a category type CategoryForm(category : Category) = new() = CategoryForm(Category.empty) /// The name of the category - member val name = category.name with get, set + member val Name = category.Name with get, set /// The slug of the category (used in category URLs) - member val slug = category.slug with get, set + member val Slug = category.Slug with get, set /// The description of the category - member val description = defaultArg category.description "" with get, set + member val Description = defaultArg category.Description "" with get, set /// The parent category for this one - member val parentId = defaultArg category.parentId "" with get, set + member val ParentId = defaultArg category.ParentId "" with get, set /// Model for editing a category type CategoryEditModel(ctx, webLog, category) = inherit MyWebLogModel(ctx, webLog) /// The form with the category information - member val form = CategoryForm(category) with get, set + member val Form = CategoryForm(category) with get, set /// The categories - member val categories : IndentedCategory list = List.empty with get, set + member val Categories : IndentedCategory list = [] with get, set // ---- Page models ---- @@ -223,54 +220,53 @@ type CategoryEditModel(ctx, webLog, category) = /// Model for page display type PageModel(ctx, webLog, page) = inherit MyWebLogModel(ctx, webLog) - /// The page to be displayed - 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 + member this.Page : Page = page /// The time zone of the web log - member this.timeZone = webLog.timeZone + member this.TimeZone = webLog.TimeZone /// The date the page was last updated - member this.updatedDate = FormatDateTime.longDate this.timeZone page.updatedOn + 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 + member this.UpdatedTime = FormatDateTime.time this.TimeZone page.UpdatedOn /// Model for page list display type PagesModel(ctx, webLog, pages) = inherit MyWebLogModel(ctx, webLog) /// The pages - member this.pages : PageForDisplay list = pages + member this.Pages : PageForDisplay list = pages /// Form used to edit a page type EditPageForm() = /// The title of the page - member val title = "" with get, set + member val Title = "" with get, set /// The link for the page - member val permalink = "" with get, set + member val Permalink = "" with get, set /// The source type of the revision - member val source = "" with get, set + member val Source = "" with get, set /// The text of the revision - member val text = "" with get, set + member val Text = "" with get, set /// Whether to show the page in the web log's page list - member val showInPageList = false with get, set + member val ShowInPageList = false with get, set /// Fill the form with applicable values from a page - member this.forPage (page : Page) = - this.title <- page.title - this.permalink <- page.permalink - this.showInPageList <- page.showInPageList + member this.ForPage (page : Page) = + this.Title <- page.Title + this.Permalink <- page.Permalink + this.ShowInPageList <- page.ShowInPageList this /// Fill the form with applicable values from a revision - member this.forRevision rev = - this.source <- rev.sourceType - this.text <- rev.text + member this.ForRevision rev = + this.Source <- rev.SourceType + this.Text <- rev.Text this @@ -278,21 +274,21 @@ type EditPageForm() = type EditPageModel(ctx, webLog, page, revision) = inherit MyWebLogModel(ctx, webLog) /// The page edit form - member val form = EditPageForm().forPage(page).forRevision(revision) + member val Form = EditPageForm().ForPage(page).ForRevision(revision) /// The page itself - member this.page = page + member this.Page = page /// The page's published date - member this.publishedDate = this.displayLongDate page.publishedOn + member this.PublishedDate = this.DisplayLongDate page.PublishedOn /// The page's published time - member this.publishedTime = this.displayTime page.publishedOn + member this.PublishedTime = this.DisplayTime page.PublishedOn /// The page's last updated date - member this.lastUpdatedDate = this.displayLongDate page.updatedOn + member this.LastUpdatedDate = this.DisplayLongDate page.UpdatedOn /// The page's last updated time - member this.lastUpdatedTime = this.displayTime page.updatedOn + member this.LastUpdatedTime = this.DisplayTime page.UpdatedOn /// Is this a new page? - member this.isNew = "new" = page.id + member this.IsNew = "new" = page.Id /// Generate a checked attribute if this page shows in the page list - member this.pageListChecked = match page.showInPageList with | true -> "checked=\"checked\"" | _ -> "" + member this.PageListChecked = match page.ShowInPageList with true -> "checked=\"checked\"" | _ -> "" // ---- Post models ---- @@ -301,102 +297,103 @@ type EditPageModel(ctx, webLog, page, revision) = type PostModel(ctx, webLog, post) = inherit MyWebLogModel(ctx, webLog) /// The post being displayed - member this.post : Post = post + member this.Post : Post = post /// The next newer post - member val newerPost = Option.None with get, set + member val NewerPost = Option.None with get, set /// The next older post - member val olderPost = Option.None with get, set + member val OlderPost = Option.None with get, set /// The date the post was published - member this.publishedDate = this.displayLongDate this.post.publishedOn + member this.PublishedDate = this.DisplayLongDate this.Post.PublishedOn /// The time the post was published - member this.publishedTime = this.displayTime this.post.publishedOn + member this.PublishedTime = this.DisplayTime this.Post.PublishedOn /// Does the post have tags? - member this.hasTags = List.length post.tags > 0 + member this.HasTags = not (List.isEmpty post.Tags) /// Get the tags sorted - member this.tags = post.tags + member this.Tags = post.Tags |> List.sort |> List.map (fun tag -> tag, tag.Replace(' ', '+')) /// Does this post have a newer post? - member this.hasNewer = this.newerPost.IsSome + member this.HasNewer = this.NewerPost.IsSome /// Does this post have an older post? - member this.hasOlder = this.olderPost.IsSome + member this.HasOlder = this.OlderPost.IsSome + /// Wrapper for a post with additional properties type PostForDisplay(webLog : WebLog, post : Post) = /// Turn tags into a pipe-delimited string of tags let pipedTags tags = tags |> List.reduce (fun acc x -> sprintf "%s | %s" acc x) /// The actual post - member this.post = post + member this.Post = post /// The time zone for the web log to which this post belongs - member this.timeZone = webLog.timeZone + member this.TimeZone = webLog.TimeZone /// The date the post was published - member this.publishedDate = FormatDateTime.longDate this.timeZone this.post.publishedOn + member this.PublishedDate = FormatDateTime.longDate this.TimeZone this.Post.PublishedOn /// The time the post was published - member this.publishedTime = FormatDateTime.time this.timeZone this.post.publishedOn + member this.PublishedTime = FormatDateTime.time this.TimeZone this.Post.PublishedOn /// Tags - member this.tags = - match List.length this.post.tags with - | 0 -> "" - | 1 | 2 | 3 | 4 | 5 -> this.post.tags |> pipedTags - | count -> sprintf "%s %s" (this.post.tags |> List.take 3 |> pipedTags) - (System.String.Format(Resources.andXMore, count - 3)) + member this.Tags = + match List.length this.Post.Tags with + | 0 -> "" + | 1 | 2 | 3 | 4 | 5 -> this.Post.Tags |> pipedTags + | count -> sprintf "%s %s" (this.Post.Tags |> List.take 3 |> pipedTags) + (System.String.Format(Resources.andXMore, count - 3)) /// Model for all page-of-posts pages type PostsModel(ctx, webLog) = inherit MyWebLogModel(ctx, webLog) /// The subtitle for the page - member val subtitle = Option.None with get, set + member val Subtitle = Option.None with get, set /// The posts to display - member val posts = List.empty with get, set + member val Posts : PostForDisplay list = [] with get, set /// The page number of the post list - member val pageNbr = 0 with get, set + member val PageNbr = 0 with get, set /// Whether there is a newer page of posts for the list - member val hasNewer = false with get, set + member val HasNewer = false with get, set /// Whether there is an older page of posts for the list - member val hasOlder = true with get, set + member val HasOlder = true with get, set /// The prefix for the next/prior links - member val urlPrefix = "" with get, set + member val UrlPrefix = "" with get, set /// The link for the next newer page of posts - member this.newerLink = - match this.urlPrefix = "/posts" && this.pageNbr = 2 && this.webLog.defaultPage = "posts" with + member this.NewerLink = + match this.UrlPrefix = "/posts" && this.PageNbr = 2 && this.WebLog.DefaultPage = "posts" with | true -> "/" - | _ -> sprintf "%s/page/%i" this.urlPrefix (this.pageNbr - 1) + | _ -> sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr - 1) /// The link for the prior (older) page of posts - member this.olderLink = sprintf "%s/page/%i" this.urlPrefix (this.pageNbr + 1) + member this.OlderLink = sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr + 1) /// Form for editing a post type EditPostForm() = /// The title of the post - member val title = "" with get, set + member val Title = "" with get, set /// The permalink for the post - member val permalink = "" with get, set + member val Permalink = "" with get, set /// The source type for this revision - member val source = "" with get, set + member val Source = "" with get, set /// The text - member val text = "" with get, set + member val Text = "" with get, set /// Tags for the post - member val tags = "" with get, set + member val Tags = "" with get, set /// The selected category Ids for the post - member val categories = Array.empty with get, set + member val Categories : string[] = [||] with get, set /// Whether the post should be published - member val publishNow = true with get, set + member val PublishNow = true with get, set /// Fill the form with applicable values from a post - member this.forPost post = - this.title <- post.title - this.permalink <- post.permalink - this.tags <- List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.tags - this.categories <- List.toArray post.categoryIds + member this.ForPost post = + this.Title <- post.Title + this.Permalink <- post.Permalink + this.Tags <- List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.Tags + this.Categories <- List.toArray post.CategoryIds this /// Fill the form with applicable values from a revision - member this.forRevision rev = - this.source <- rev.sourceType - this.text <- rev.text + member this.ForRevision rev = + this.Source <- rev.SourceType + this.Text <- rev.Text this /// View model for the edit post page @@ -404,17 +401,17 @@ type EditPostModel(ctx, webLog, post, revision) = inherit MyWebLogModel(ctx, webLog) /// The form - member val form = EditPostForm().forPost(post).forRevision(revision) with get, set + member val Form = EditPostForm().ForPost(post).ForRevision(revision) with get, set /// The post being edited - member val post = post with get, set + member val Post = post with get, set /// The categories to which the post may be assigned - member val categories = List.empty with get, set + member val Categories : (string * string) list = [] with get, set /// Whether the post is currently published - member this.isPublished = PostStatus.Published = this.post.status + member this.IsPublished = PostStatus.Published = this.Post.Status /// The published date - member this.publishedDate = this.displayLongDate this.post.publishedOn + member this.PublishedDate = this.DisplayLongDate this.Post.PublishedOn /// The published time - member this.publishedTime = this.displayTime this.post.publishedOn + member this.PublishedTime = this.DisplayTime this.Post.PublishedOn // ---- User models ---- @@ -422,15 +419,15 @@ type EditPostModel(ctx, webLog, post, revision) = /// Form for the log on page type LogOnForm() = /// The URL to which the user will be directed upon successful log on - member val returnUrl = "" with get, set + member val ReturnUrl = "" with get, set /// The e-mail address - member val email = "" with get, set + member val Email = "" with get, set /// The user's passwor - member val password = "" with get, set + member val Password = "" with get, set /// Model to support the user log on page type LogOnModel(ctx, webLog) = inherit MyWebLogModel(ctx, webLog) /// The log on form - member val form = LogOnForm() with get, set + member val Form = LogOnForm() with get, set diff --git a/src/myWebLog.Web/myWebLog.Web.fsproj b/src/myWebLog.Web/myWebLog.Web.fsproj index 14dea9a..fc9cc04 100644 --- a/src/myWebLog.Web/myWebLog.Web.fsproj +++ b/src/myWebLog.Web/myWebLog.Web.fsproj @@ -8,7 +8,7 @@ e6ee110a-27a6-4a19-b0cb-d24f48f71b53 Library myWebLog.Web - myWebLog.Web + MyWebLog.Web v4.5.2 4.4.0.0 true diff --git a/src/myWebLog/Program.cs b/src/myWebLog/Program.cs index 608dfc0..01ea1cf 100644 --- a/src/myWebLog/Program.cs +++ b/src/myWebLog/Program.cs @@ -1,10 +1,10 @@ -namespace myWebLog +namespace MyWebLog { class Program { static void Main(string[] args) { - App.run(); + App.Run(); } } } diff --git a/src/myWebLog/Properties/AssemblyInfo.cs b/src/myWebLog/Properties/AssemblyInfo.cs index 34eb3d8..c3a308b 100644 --- a/src/myWebLog/Properties/AssemblyInfo.cs +++ b/src/myWebLog/Properties/AssemblyInfo.cs @@ -1,11 +1,11 @@ using System.Reflection; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("myWebLog")] +[assembly: AssemblyTitle("MyWebLog")] [assembly: AssemblyDescription("A lightweight blogging platform built on Suave, Nancy, and RethinkDB")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("myWebLog")] +[assembly: AssemblyProduct("MyWebLog")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/myWebLog/myWebLog.csproj b/src/myWebLog/myWebLog.csproj index cb19a3f..97773dc 100644 --- a/src/myWebLog/myWebLog.csproj +++ b/src/myWebLog/myWebLog.csproj @@ -7,8 +7,8 @@ {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB} Exe Properties - myWebLog - myWebLog + MyWebLog + MyWebLog v4.5.2 512 true @@ -51,7 +51,6 @@ Always - Always diff --git a/src/myWebLog/views/admin/admin-layout.html b/src/myWebLog/views/admin/admin-layout.html index 8d96785..d281591 100644 --- a/src/myWebLog/views/admin/admin-layout.html +++ b/src/myWebLog/views/admin/admin-layout.html @@ -3,7 +3,7 @@ - @Model.pageTitle | @Translate.Admin | @Model.webLog.name + @Model.PageTitle | @Translate.Admin | @Model.WebLog.Name @@ -14,17 +14,17 @@
- @Each.messages - @Current.toDisplay + @Each.Messages + @Current.ToDisplay @EndEach @Section['Content'];
-
@Model.generator
+
@Model.Generator
diff --git a/src/myWebLog/views/admin/category/edit.html b/src/myWebLog/views/admin/category/edit.html index dd74069..279dfd8 100644 --- a/src/myWebLog/views/admin/category/edit.html +++ b/src/myWebLog/views/admin/category/edit.html @@ -1,34 +1,34 @@ @Master['admin/admin-layout'] @Section['Content'] -
+ @AntiForgeryToken
- - + +
- - + +
- - + +
- - - @Each.categories - @Current.option + @Each.Categories + @Current.Option @EndEach
@@ -46,7 +46,7 @@ @Section['Scripts'] @EndSection diff --git a/src/myWebLog/views/admin/category/list.html b/src/myWebLog/views/admin/category/list.html index 48dffa0..d49849f 100644 --- a/src/myWebLog/views/admin/category/list.html +++ b/src/myWebLog/views/admin/category/list.html @@ -11,20 +11,20 @@ @Translate.Category @Translate.Description - @Each.categories + @Each.Categories - @Translate.Edit   - + @Translate.Edit   + @Translate.Delete - @Current.listName + @Current.ListName - @If.hasDescription - @Current.category.description.Value + @If.HasDescription + @Current.Category.Description.Value @EndIf - @IfNot.hasDescription + @IfNot.HasDescription   @EndIf diff --git a/src/myWebLog/views/admin/dashboard.html b/src/myWebLog/views/admin/dashboard.html index 9528ebe..3781a5d 100644 --- a/src/myWebLog/views/admin/dashboard.html +++ b/src/myWebLog/views/admin/dashboard.html @@ -3,7 +3,7 @@ @Section['Content']
-

@Translate.Posts  @Model.posts

+

@Translate.Posts  @Model.Posts

@Translate.ListAll     @@ -11,7 +11,7 @@

-

@Translate.Pages  @Model.pages

+

@Translate.Pages  @Model.Pages

@Translate.ListAll     @@ -21,7 +21,7 @@

-

@Translate.Categories  @Model.categories

+

@Translate.Categories  @Model.Categories

@Translate.ListAll     diff --git a/src/myWebLog/views/admin/message.html b/src/myWebLog/views/admin/message.html deleted file mode 100644 index d697c89..0000000 --- a/src/myWebLog/views/admin/message.html +++ /dev/null @@ -1,18 +0,0 @@ -if session && 0 < (session.messages || []).length - while 0 < session.messages.length - - var message = session.messages.shift() -

\ No newline at end of file diff --git a/src/myWebLog/views/admin/page/edit.html b/src/myWebLog/views/admin/page/edit.html index 55bee85..a87fe3c 100644 --- a/src/myWebLog/views/admin/page/edit.html +++ b/src/myWebLog/views/admin/page/edit.html @@ -1,22 +1,22 @@ @Master['admin/admin-layout'] @Section['Content'] - + @AntiForgeryToken
- - + +
- - + +

@Translate.startingWith http://@Model.webLog.urlBase/

- +
@@ -26,16 +26,16 @@ @IfNot.isNew
-

@Model.publishedDate
@Model.publishedTime

+

@Model.PublishedDate
@Model.PublishedTime

-

@Model.lastUpdatedDate
@Model.lastUpdatedTime

+

@Model.LastUpdatedDate
@Model.LastUpdatedTime

@EndIf
- -   + +  
@@ -51,7 +51,7 @@ @EndSection diff --git a/src/myWebLog/views/admin/page/list.html b/src/myWebLog/views/admin/page/list.html index b20085f..7e5f759 100644 --- a/src/myWebLog/views/admin/page/list.html +++ b/src/myWebLog/views/admin/page/list.html @@ -10,15 +10,15 @@ @Translate.Title @Translate.LastUpdated - @Each.pages + @Each.Pages - @Current.page.title
- @Translate.View   - @Translate.Edit   - @Translate.Delete + @Current.Page.Title
+ @Translate.View   + @Translate.Edit   + @Translate.Delete - @Current.updatedDate
@Translate.at @Current.updatedTime + @Current.UpdatedDate
@Translate.at @Current.UpdatedTime @EndEach diff --git a/src/myWebLog/views/admin/post/edit.html b/src/myWebLog/views/admin/post/edit.html index fbba390..ec00751 100644 --- a/src/myWebLog/views/admin/post/edit.html +++ b/src/myWebLog/views/admin/post/edit.html @@ -1,27 +1,27 @@ @Master['admin/admin-layout'] @Section['Content'] - + @AntiForgeryToken
- - + +
- - + +

@Translate.startingWith http://@Model.webLog.urlBase/

- +
- - + +
@@ -32,12 +32,12 @@
-

@Model.post.status

+

@Model.Post.Status

- @If.isPublished + @If.IsPublished
-

@Model.publishedDate
@Model.publishedTime

+

@Model.PublishedDate
@Model.PublishedTime

@EndIf
@@ -48,7 +48,7 @@
- @Each.categories + @Each.Categories - +   - +
@EndEach
- @If.isPublished - + @If.IsPublished + @EndIf - @IfNot.isPublished + @IfNot.IsPublished
- -   + +  
@EndIf

@@ -89,7 +89,7 @@ @EndSection diff --git a/src/myWebLog/views/admin/post/list.html b/src/myWebLog/views/admin/post/list.html index ba42476..ee9cf74 100644 --- a/src/myWebLog/views/admin/post/list.html +++ b/src/myWebLog/views/admin/post/list.html @@ -16,34 +16,34 @@ @Translate.Status @Translate.Tags - @Each.posts + @Each.Posts - @Current.publishedDate
- @Translate.at @Current.publishedTime + @Current.PublishedDate
+ @Translate.at @Current.PublishedTime - @Current.post.title
- @Translate.View  |  - @Translate.Edit  |  - @Translate.Delete + @Current.Post.Title
+ @Translate.View  |  + @Translate.Edit  |  + @Translate.Delete - @Current.post.status - @Current.tags + @Current.Post.Status + @Current.Tags @EndEach

- @If.hasNewer -

«  @Translate.NewerPosts

+ @If.HasNewer +

«  @Translate.NewerPosts

@EndIf
- @If.hasOlder -

@Translate.OlderPosts  »

+ @If.HasOlder +

@Translate.OlderPosts  »

@EndIf
-@EndSection \ No newline at end of file +@EndSection diff --git a/src/myWebLog/views/admin/user/logon.html b/src/myWebLog/views/admin/user/logon.html index ae0fbfe..70432d4 100644 --- a/src/myWebLog/views/admin/user/logon.html +++ b/src/myWebLog/views/admin/user/logon.html @@ -3,12 +3,12 @@ @Section['Content'] @AntiForgeryToken - +
- +
@@ -17,7 +17,7 @@
- +
@@ -35,7 +35,7 @@ @Section['Scripts'] @EndSection diff --git a/src/myWebLog/views/themes/default/footer.html b/src/myWebLog/views/themes/default/footer.html index 6d32297..54ef6c1 100644 --- a/src/myWebLog/views/themes/default/footer.html +++ b/src/myWebLog/views/themes/default/footer.html @@ -3,7 +3,7 @@
- @Model.footerLogo + @Model.FooterLogo
diff --git a/src/myWebLog/views/themes/default/index-content.html b/src/myWebLog/views/themes/default/index-content.html index 5d2f476..d53e069 100644 --- a/src/myWebLog/views/themes/default/index-content.html +++ b/src/myWebLog/views/themes/default/index-content.html @@ -1,24 +1,24 @@ -@Each.messages - @Current.toDisplay +@Each.Messages + @Current.ToDisplay @EndEach -@If.subTitle.IsSome +@If.SubTitle.IsSome

- @Model.subTitle + @Model.SubTitle

@EndIf -@Each.posts +@Each.Posts

- @Current.post.title + @Current.Post.Title

- @Current.publishedDate   - @Current.publishedTime + @Current.PublishedDate   + @Current.PublishedTime

- @Current.post.text + @Current.Post.Text

@@ -26,16 +26,16 @@ @EndEach
- @If.hasNewer + @If.HasNewer

- @Translate.NewerPosts + @Translate.NewerPosts

@EndIf
- @If.hasOlder + @If.HasOlder

- @Translate.OlderPosts + @Translate.OlderPosts

@EndIf
diff --git a/src/myWebLog/views/themes/default/layout.html b/src/myWebLog/views/themes/default/layout.html index 17ea0f5..f2d274b 100644 --- a/src/myWebLog/views/themes/default/layout.html +++ b/src/myWebLog/views/themes/default/layout.html @@ -3,13 +3,13 @@ - - @Model.displayPageTitle + + @Model.DisplayPageTitle - - + + @Section['Head']; @@ -17,20 +17,20 @@