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 @@