From b86ba7b6f681164e7bd90e44770269dcb3e718de Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Thu, 7 Jul 2016 22:57:14 -0500 Subject: [PATCH] Interim commit Nowhere close to executable (though everything builds) --- .gitattributes | 63 +++++ src/myWebLog.Data/AssemblyInfo.fs | 21 ++ src/myWebLog.Data/DataConfig.fs | 9 + src/myWebLog.Data/Entities.fs | 265 ++++++++++++++++++ src/myWebLog.Data/Rethink.fs | 23 ++ src/myWebLog.Data/SetUp.fs | 154 ++++++++++ src/myWebLog.Data/Table.fs | 19 ++ src/myWebLog.Data/WebLog.fs | 20 ++ src/myWebLog.Data/myWebLog.Data.fsproj | 94 +++++++ src/myWebLog.Data/packages.config | 7 + .../Properties/AssemblyInfo.cs | 17 ++ src/myWebLog.Resources/Resources.Designer.cs | 72 +++++ src/myWebLog.Resources/Resources.resx | 123 ++++++++ .../myWebLog.Resources.csproj | 64 +++++ src/myWebLog.Web/App.fs | 127 +++++++++ src/myWebLog.Web/AssemblyInfo.fs | 21 ++ src/myWebLog.Web/Keys.fs | 3 + src/myWebLog.Web/myWebLog.Web.fsproj | 123 ++++++++ src/myWebLog.Web/packages.config | 13 + src/myWebLog.sln | 40 +++ src/myWebLog/App.config | 6 + src/myWebLog/Program.cs | 10 + src/myWebLog/Properties/AssemblyInfo.cs | 15 + src/myWebLog/myWebLog.csproj | 80 ++++++ src/myWebLog/views/default/index-content.html | 38 +++ src/myWebLog/views/default/index.html | 5 + src/myWebLog/views/default/layout.html | 46 +++ 27 files changed, 1478 insertions(+) create mode 100644 .gitattributes create mode 100644 src/myWebLog.Data/AssemblyInfo.fs create mode 100644 src/myWebLog.Data/DataConfig.fs create mode 100644 src/myWebLog.Data/Entities.fs create mode 100644 src/myWebLog.Data/Rethink.fs create mode 100644 src/myWebLog.Data/SetUp.fs create mode 100644 src/myWebLog.Data/Table.fs create mode 100644 src/myWebLog.Data/WebLog.fs create mode 100644 src/myWebLog.Data/myWebLog.Data.fsproj create mode 100644 src/myWebLog.Data/packages.config create mode 100644 src/myWebLog.Resources/Properties/AssemblyInfo.cs create mode 100644 src/myWebLog.Resources/Resources.Designer.cs create mode 100644 src/myWebLog.Resources/Resources.resx create mode 100644 src/myWebLog.Resources/myWebLog.Resources.csproj create mode 100644 src/myWebLog.Web/App.fs create mode 100644 src/myWebLog.Web/AssemblyInfo.fs create mode 100644 src/myWebLog.Web/Keys.fs create mode 100644 src/myWebLog.Web/myWebLog.Web.fsproj create mode 100644 src/myWebLog.Web/packages.config create mode 100644 src/myWebLog.sln create mode 100644 src/myWebLog/App.config create mode 100644 src/myWebLog/Program.cs create mode 100644 src/myWebLog/Properties/AssemblyInfo.cs create mode 100644 src/myWebLog/myWebLog.csproj create mode 100644 src/myWebLog/views/default/index-content.html create mode 100644 src/myWebLog/views/default/index.html create mode 100644 src/myWebLog/views/default/layout.html diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/src/myWebLog.Data/AssemblyInfo.fs b/src/myWebLog.Data/AssemblyInfo.fs new file mode 100644 index 0000000..701d1ed --- /dev/null +++ b/src/myWebLog.Data/AssemblyInfo.fs @@ -0,0 +1,21 @@ +namespace myWebLog.Data.AssemblyInfo + +open System.Reflection +open System.Runtime.CompilerServices +open System.Runtime.InteropServices + +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] + +do + () \ No newline at end of file diff --git a/src/myWebLog.Data/DataConfig.fs b/src/myWebLog.Data/DataConfig.fs new file mode 100644 index 0000000..f3d3738 --- /dev/null +++ b/src/myWebLog.Data/DataConfig.fs @@ -0,0 +1,9 @@ +namespace myWebLog.Data + +open RethinkDb.Driver.Net + +type DataConfig = { + database : string + conn : IConnection + } + diff --git a/src/myWebLog.Data/Entities.fs b/src/myWebLog.Data/Entities.fs new file mode 100644 index 0000000..990007f --- /dev/null +++ b/src/myWebLog.Data/Entities.fs @@ -0,0 +1,265 @@ +namespace myWebLog.Entities + +open Newtonsoft.Json + +// ---- Constants ---- + +/// Constants to use for revision source language +module RevisionSource = + let Markdown = "markdown" + let HTML = "html" + +/// Constants to use for authorization levels +module AuthorizationLevel = + let Administrator = "Administrator" + let User = "User" + +/// Constants to use for post statuses +module PostStatus = + let Draft = "Draft" + let Published = "Published" + +// ---- 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 + } + + +/// 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 + lastUpdatedOn : 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 + lastUpdatedOn = int64 0 + showInPageList = false + text = "" + revisions = List.empty + } + + +/// 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) + 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 : Page list + } +with + /// An empty web log + 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 + } + + +/// 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 + } +with + /// An empty user + 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) + + +/// A category to which posts may be assigned +type Category = { + /// The Id + id : 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 = "" + 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 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 + 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 + } +with + static member empty = + { id = "" + 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/Rethink.fs b/src/myWebLog.Data/Rethink.fs new file mode 100644 index 0000000..be50fdf --- /dev/null +++ b/src/myWebLog.Data/Rethink.fs @@ -0,0 +1,23 @@ +module myWebLog.Data.Rethink + +open RethinkDb.Driver.Ast +open RethinkDb.Driver.Net + +let private r = RethinkDb.Driver.RethinkDB.R +let private await task = task |> Async.AwaitTask |> Async.RunSynchronously + +let delete (expr : ReqlExpr) = expr.Delete () +let get (expr : obj) (table : Table) = table.Get expr +let getAll (exprs : obj[]) (table : Table) = table.GetAll exprs +let insert (expr : obj) (table : Table) = table.Insert expr +let optArg key (value : obj) (expr : GetAll) = expr.OptArg (key, value) +let orderBy (exprA : obj) (expr : ReqlExpr) = expr.OrderBy exprA +let replace (exprA : obj) (expr : ReqlExpr) = expr.Replace exprA +let runAtomAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync<'T> conn |> await +let runCursorAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunCursorAsync<'T> conn |> await +let runListAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync> conn + |> await +let runResultAsync (conn : IConnection) (ast : ReqlAst) = ast.RunResultAsync conn |> await +let table (expr : obj) = r.Table expr +let update (exprA : obj) (expr : ReqlExpr) = expr.Update exprA +let without (exprs : obj[]) (expr : ReqlExpr) = expr.Without exprs \ No newline at end of file diff --git a/src/myWebLog.Data/SetUp.fs b/src/myWebLog.Data/SetUp.fs new file mode 100644 index 0000000..da73d9a --- /dev/null +++ b/src/myWebLog.Data/SetUp.fs @@ -0,0 +1,154 @@ +module myWebLog.Data.SetUp + +open RethinkDb.Driver +open System +open Rethink +open RethinkDb.Driver.Ast + +let private r = RethinkDB.R +let private logStep step = Console.Out.WriteLine(sprintf "[myWebLog] %s" step) +let private logStepStart text = Console.Out.Write (sprintf "[myWebLog] %s..." text) +let private logStepDone () = Console.Out.WriteLine(" done.") + +let private result task = task |> Async.AwaitTask |> Async.RunSynchronously + +let checkDatabase (cfg : DataConfig) = + logStep "|> Checking database" + let dbs = r.DbList() + |> runListAsync cfg.conn + match dbs.Contains cfg.database with + | true -> () + | _ -> logStepStart (sprintf " %s database not found - creating" cfg.database) + r.DbCreate cfg.database + |> runResultAsync cfg.conn + |> ignore + logStepDone () + +let checkTables cfg = + logStep "|> Checking tables" + let tables = r.Db(cfg.database).TableList() + |> runListAsync cfg.conn + [ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ] + |> List.map (fun tbl -> match tables.Contains tbl with + | true -> None + | _ -> Some (tbl, r.TableCreate tbl)) + |> 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 + |> ignore + logStepDone ()) + +let tbl cfg table = r.Db(cfg.database).Table(table) + +let createIndex cfg table index = + logStepStart (sprintf """ Creating index "%s" on table %s""" index table) + (tbl cfg table).IndexCreate(index) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg table).IndexWait(index) + |> runResultAsync cfg.conn + |> ignore + logStepDone () + +let chkIndexes cfg table (indexes : string list) = + let idx = (tbl cfg table).IndexList() + |> runListAsync cfg.conn + indexes + |> List.iter (fun index -> match idx.Contains index with + | true -> () + | _ -> createIndex cfg table index) + idx + +let checkCategoryIndexes cfg = + chkIndexes cfg Table.Category [ "webLogId"; "slug" ] + |> ignore + +let checkCommentIndexes cfg = + chkIndexes cfg Table.Comment [ "postId" ] + |> ignore + +let checkPageIndexes cfg = + let idx = chkIndexes cfg Table.Page [ "webLogId" ] + match idx.Contains "permalink" with + | true -> () + | _ -> logStepStart (sprintf """ Creating index "permalink" on table %s""" Table.Page) + (tbl cfg Table.Page) + .IndexCreate("permalink", ReqlFunction1(fun row -> upcast r.Array(row.["webLogId"], row.["permalink"]))) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg Table.Page).IndexWait "permalink" + |> runResultAsync cfg.conn + |> ignore + logStepDone () + match idx.Contains "pageList" with + | true -> () + | _ -> logStepStart (sprintf """ Creating index "pageList" on table %s""" Table.Page) + (tbl cfg Table.Page) + .IndexCreate("pageList", ReqlFunction1(fun row -> upcast r.Array(row.["webLogId"], row.["showInPageList"]))) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg Table.Page).IndexWait "pageList" + |> runResultAsync cfg.conn + |> ignore + logStepDone () + +let checkPostIndexes cfg = + let idx = chkIndexes cfg Table.Post [ "webLogId" ] + match idx.Contains "webLogAndStatus" with + | true -> () + | _ -> logStepStart (sprintf """ Creating index "webLogAndStatus" on table %s""" Table.Post) + (tbl cfg Table.Post) + .IndexCreate("webLogAndStatus", ReqlFunction1(fun row -> upcast r.Array(row.["webLogId"], row.["status"]))) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg Table.Post).IndexWait "webLogAndStatus" + |> runResultAsync cfg.conn + |> ignore + logStepDone () + match idx.Contains "permalink" with + | true -> () + | _ -> logStepStart (sprintf """ Creating index "permalink" on table %s""" Table.Post) + (tbl cfg Table.Post) + .IndexCreate("permalink", ReqlFunction1(fun row -> upcast r.Array(row.["webLogId"], row.["permalink"]))) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg Table.Post).IndexWait "permalink" + |> runResultAsync cfg.conn + |> ignore + logStepDone () + +let checkUserIndexes cfg = + let idx = chkIndexes cfg Table.User [ ] + match idx.Contains "logOn" with + | true -> () + | _ -> logStepStart (sprintf """ Creating index "logOn" on table %s""" Table.User) + (tbl cfg Table.User) + .IndexCreate("logOn", ReqlFunction1(fun row -> upcast r.Array(row.["userName"], row.["passwordHash"]))) + |> runResultAsync cfg.conn + |> ignore + (tbl cfg Table.User).IndexWait "logOn" + |> runResultAsync cfg.conn + |> ignore + logStepDone () + +let checkWebLogIndexes cfg = + chkIndexes cfg Table.WebLog [ "urlBase" ] + |> ignore + +let checkIndexes cfg = + logStep "|> Checking indexes" + checkCategoryIndexes cfg + checkCommentIndexes cfg + checkPageIndexes cfg + checkPostIndexes cfg + checkUserIndexes cfg + checkWebLogIndexes cfg + +let startUpCheck cfg = + logStep "Database Start Up Checks Starting" + checkDatabase cfg + checkTables cfg + checkIndexes cfg + logStep "Database Start Up Checks Complete" diff --git a/src/myWebLog.Data/Table.fs b/src/myWebLog.Data/Table.fs new file mode 100644 index 0000000..8aec6ec --- /dev/null +++ b/src/myWebLog.Data/Table.fs @@ -0,0 +1,19 @@ +module myWebLog.Data.Table + +/// The Category table +let Category = "Category" + +/// The Comment table +let Comment = "Comment" + +/// The Page table +let Page = "Page" + +/// The Post table +let Post = "Post" + +/// The WebLog table +let WebLog = "WebLog" + +/// The User table +let User = "User" \ No newline at end of file diff --git a/src/myWebLog.Data/WebLog.fs b/src/myWebLog.Data/WebLog.fs new file mode 100644 index 0000000..83b90b0 --- /dev/null +++ b/src/myWebLog.Data/WebLog.fs @@ -0,0 +1,20 @@ +module myWebLog.Data.WebLog + +open myWebLog.Entities +open Rethink +open RethinkDb.Driver + +let private r = RethinkDB.R + +type PageList = { pageList : Ast.CoerceTo } + +/// Detemine the web log by the URL base +let tryFindWebLogByUrlBase (cfg : DataConfig) (urlBase : string) = + r.Table(Table.WebLog).GetAll([| urlBase |]).OptArg("index", "urlBase") + .Merge(fun webLog -> { pageList = r.Table(Table.Page) + .GetAll([| webLog.["id"], true |]).OptArg("index", "pageList") + .OrderBy("title") + .Pluck([| "title", "permalink" |]) + .CoerceTo("array") }) + |> runCursorAsync cfg.conn + |> Seq.tryHead diff --git a/src/myWebLog.Data/myWebLog.Data.fsproj b/src/myWebLog.Data/myWebLog.Data.fsproj new file mode 100644 index 0000000..16ee24f --- /dev/null +++ b/src/myWebLog.Data/myWebLog.Data.fsproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + 2.0 + 1fba0b84-b09e-4b16-b9b6-5730dea27192 + Library + myWebLog.Data + myWebLog.Data + v4.5.2 + 4.4.0.0 + true + myWebLog.Data + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\myWebLog.Data.XML + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\myWebLog.Data.XML + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + + + + + + + + + + ..\packages\Common.Logging.3.3.0\lib\net40\Common.Logging.dll + True + + + ..\packages\Common.Logging.Core.3.3.0\lib\net40\Common.Logging.Core.dll + True + + + + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll + True + + + + + + + \ No newline at end of file diff --git a/src/myWebLog.Data/packages.config b/src/myWebLog.Data/packages.config new file mode 100644 index 0000000..8639c4f --- /dev/null +++ b/src/myWebLog.Data/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/myWebLog.Resources/Properties/AssemblyInfo.cs b/src/myWebLog.Resources/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c93da61 --- /dev/null +++ b/src/myWebLog.Resources/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("myWebLog.Resources")] +[assembly: AssemblyDescription("Resources for the myWebLog package")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("myWebLog.Resources")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("a12ea8da-88bc-4447-90cb-a0e2dcc37523")] +[assembly: AssemblyVersion("0.9.1.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] diff --git a/src/myWebLog.Resources/Resources.Designer.cs b/src/myWebLog.Resources/Resources.Designer.cs new file mode 100644 index 0000000..de30025 --- /dev/null +++ b/src/myWebLog.Resources/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace myWebLog { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + 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); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Log On. + /// + public static string LogOn { + get { + return ResourceManager.GetString("LogOn", resourceCulture); + } + } + } +} diff --git a/src/myWebLog.Resources/Resources.resx b/src/myWebLog.Resources/Resources.resx new file mode 100644 index 0000000..cf2393c --- /dev/null +++ b/src/myWebLog.Resources/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Log On + + \ No newline at end of file diff --git a/src/myWebLog.Resources/myWebLog.Resources.csproj b/src/myWebLog.Resources/myWebLog.Resources.csproj new file mode 100644 index 0000000..d9951b1 --- /dev/null +++ b/src/myWebLog.Resources/myWebLog.Resources.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {A12EA8DA-88BC-4447-90CB-A0E2DCC37523} + Library + Properties + myWebLog + myWebLog.Resources + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + \ No newline at end of file diff --git a/src/myWebLog.Web/App.fs b/src/myWebLog.Web/App.fs new file mode 100644 index 0000000..095027f --- /dev/null +++ b/src/myWebLog.Web/App.fs @@ -0,0 +1,127 @@ +module myWebLog.App + +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 +open Nancy.Cryptography +open Nancy.Owin +open Nancy.Security +open Nancy.Session +open Nancy.Session.Persistable +open Nancy.Session.RethinkDb +open Nancy.TinyIoc +open Nancy.ViewEngines.SuperSimpleViewEngine +open RethinkDb.Driver +open RethinkDb.Driver.Net +open Suave +open Suave.Owin +open System.Text.RegularExpressions + +/// Set up a database connection +let cfg = + { database = "myWebLog" + conn = RethinkDB.R.Connection() + .Hostname(RethinkDBConstants.DefaultHostname) + .Port(RethinkDBConstants.DefaultPort) + .AuthKey(RethinkDBConstants.DefaultAuthkey) + .Db("myWebLog") + .Timeout(RethinkDBConstants.DefaultTimeout) + .Connect() } + +do + startUpCheck cfg + +type TranslateTokenViewEngineMatcher() = + static let regex = Regex("@Translate\.(?[a-zA-Z0-9-_]+);?", RegexOptions.Compiled) + interface ISuperSimpleViewEngineMatcher with + member this.Invoke (content, model, host) = + regex.Replace(content, fun m -> let key = m.Groups.["TranslationKey"].Value + match Resources.ResourceManager.GetString key with + | null -> key + | xlat -> xlat) + + +/// Handle forms authentication +type MyWebLogUser(name, claims) = + interface IUserIdentity with + member this.UserName with get() = name + member this.Claims with get() = claims + member this.UserName with get() = (this :> IUserIdentity).UserName + member this.Claims with get() = (this :> IUserIdentity).Claims + +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) + | _ -> null + + +/// Set up the RethinkDB connection instance to be used by the IoC container +type ApplicationBootstrapper() = + inherit DefaultNancyBootstrapper() + override this.ConfigureRequestContainer (container, context) = + base.ConfigureRequestContainer (container, context) + container.Register() + |> ignore + override this.ApplicationStartup (container, pipelines) = + base.ApplicationStartup (container, pipelines) + // Data configuration + container.Register(cfg) + |> ignore + // I18N in SSVE + container.Register>(fun _ _ -> + Seq.singleton (TranslateTokenViewEngineMatcher() :> ISuperSimpleViewEngineMatcher)) + |> ignore + // Forms authentication configuration + let salt = (System.Text.ASCIIEncoding()).GetBytes "NoneOfYourBeesWax" + let auth = + FormsAuthenticationConfiguration( + CryptographyConfiguration = CryptographyConfiguration + (RijndaelEncryptionProvider(PassphraseKeyGenerator("Secrets", salt)), + DefaultHmacProvider(PassphraseKeyGenerator("Clandestine", salt))), + RedirectUrl = "~/user/logon", + UserMapper = container.Resolve()) + FormsAuthentication.Enable (pipelines, auth) + // CSRF + Csrf.Enable pipelines + // Sessions + let sessions = RethinkDbSessionConfiguration(cfg.conn) + sessions.Database <- cfg.database + PersistableSessions.Enable (pipelines, sessions) + () + + +let version = + let v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version + match v.Build with + | 0 -> match v.Minor with + | 0 -> string v.Major + | _ -> sprintf "%d.%d" v.Major v.Minor + | _ -> sprintf "%d.%d.%d" v.Major v.Minor v.Build + |> sprintf "v%s" + +/// Set up the request environment +type RequestEnvironment() = + interface IRequestStartup with + member this.Initialize (pipelines, context) = + pipelines.BeforeRequest.AddItemToStartOfPipeline + (fun ctx -> ctx.Items.["requestStart"] <- System.DateTime.Now.Ticks + match tryFindWebLogByUrlBase cfg ctx.Request.Url.HostName with + | Some webLog -> ctx.Items.["webLog"] <- webLog + | None -> System.ApplicationException + (sprintf "%s is not properly configured for myWebLog" ctx.Request.Url.HostName) + |> raise + ctx.Items.["version"] <- version + null) + + +let app = OwinApp.ofMidFunc "/" (NancyMiddleware.UseNancy (NancyOptions())) + +let run () = startWebServer defaultConfig app // webPart diff --git a/src/myWebLog.Web/AssemblyInfo.fs b/src/myWebLog.Web/AssemblyInfo.fs new file mode 100644 index 0000000..ea73017 --- /dev/null +++ b/src/myWebLog.Web/AssemblyInfo.fs @@ -0,0 +1,21 @@ +namespace myWebLog.Web.AssemblyInfo + +open System.Reflection +open System.Runtime.CompilerServices +open System.Runtime.InteropServices + +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] + +do + () \ No newline at end of file diff --git a/src/myWebLog.Web/Keys.fs b/src/myWebLog.Web/Keys.fs new file mode 100644 index 0000000..ae2bf3b --- /dev/null +++ b/src/myWebLog.Web/Keys.fs @@ -0,0 +1,3 @@ +module myWebLog.Keys + +let User = "user" \ No newline at end of file diff --git a/src/myWebLog.Web/myWebLog.Web.fsproj b/src/myWebLog.Web/myWebLog.Web.fsproj new file mode 100644 index 0000000..09fe36d --- /dev/null +++ b/src/myWebLog.Web/myWebLog.Web.fsproj @@ -0,0 +1,123 @@ + + + + + Debug + AnyCPU + 2.0 + e6ee110a-27a6-4a19-b0cb-d24f48f71b53 + Library + myWebLog.Web + myWebLog.Web + v4.5.2 + 4.4.0.0 + true + myWebLog.Web + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\myWebLog.Web.XML + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\myWebLog.Web.XML + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + + + + + + ..\packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll + True + + + ..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll + True + + + ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll + True + + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll + True + + + ..\packages\Nancy.Session.Persistable.0.8.6\lib\net452\Nancy.Session.Persistable.dll + True + + + ..\packages\Nancy.Session.RethinkDB.0.8.6\lib\net452\Nancy.Session.RethinkDb.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll + True + + + ..\packages\Suave.1.1.3\lib\net40\Suave.dll + True + + + + + + + + myWebLog.Data + {1fba0b84-b09e-4b16-b9b6-5730dea27192} + True + + + myWebLog.Resources + {a12ea8da-88bc-4447-90cb-a0e2dcc37523} + True + + + + \ No newline at end of file diff --git a/src/myWebLog.Web/packages.config b/src/myWebLog.Web/packages.config new file mode 100644 index 0000000..4dce297 --- /dev/null +++ b/src/myWebLog.Web/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/myWebLog.sln b/src/myWebLog.sln new file mode 100644 index 0000000..efd878b --- /dev/null +++ b/src/myWebLog.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myWebLog", "myWebLog\myWebLog.csproj", "{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "myWebLog.Web", "myWebLog.Web\myWebLog.Web.fsproj", "{E6EE110A-27A6-4A19-B0CB-D24F48F71B53}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "myWebLog.Data", "myWebLog.Data\myWebLog.Data.fsproj", "{1FBA0B84-B09E-4B16-B9B6-5730DEA27192}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myWebLog.Resources", "myWebLog.Resources\myWebLog.Resources.csproj", "{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}.Release|Any CPU.Build.0 = Release|Any CPU + {E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6EE110A-27A6-4A19-B0CB-D24F48F71B53}.Release|Any CPU.Build.0 = Release|Any CPU + {1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FBA0B84-B09E-4B16-B9B6-5730DEA27192}.Release|Any CPU.Build.0 = Release|Any CPU + {A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A12EA8DA-88BC-4447-90CB-A0E2DCC37523}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/myWebLog/App.config b/src/myWebLog/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/src/myWebLog/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/myWebLog/Program.cs b/src/myWebLog/Program.cs new file mode 100644 index 0000000..608dfc0 --- /dev/null +++ b/src/myWebLog/Program.cs @@ -0,0 +1,10 @@ +namespace myWebLog +{ + class Program + { + static void Main(string[] args) + { + App.run(); + } + } +} diff --git a/src/myWebLog/Properties/AssemblyInfo.cs b/src/myWebLog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f796b02 --- /dev/null +++ b/src/myWebLog/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("myWebLog")] +[assembly: AssemblyDescription("A lightweight blogging platform built on Nancy and RethinkDB")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("myWebLog")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("b9f6db52-65a1-4c2a-8c97-739e08a1d4fb")] +[assembly: AssemblyVersion("0.9.1.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/myWebLog/myWebLog.csproj b/src/myWebLog/myWebLog.csproj new file mode 100644 index 0000000..1ebe146 --- /dev/null +++ b/src/myWebLog/myWebLog.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB} + Exe + Properties + myWebLog + myWebLog + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {1fba0b84-b09e-4b16-b9b6-5730dea27192} + myWebLog.Data + + + {a12ea8da-88bc-4447-90cb-a0e2dcc37523} + myWebLog.Resources + + + {e6ee110a-27a6-4a19-b0cb-d24f48f71b53} + myWebLog.Web + + + + + + + + + + + \ No newline at end of file diff --git a/src/myWebLog/views/default/index-content.html b/src/myWebLog/views/default/index-content.html new file mode 100644 index 0000000..83c18d3 --- /dev/null +++ b/src/myWebLog/views/default/index-content.html @@ -0,0 +1,38 @@ +@If.subTitle +

+ @Model.subTitle +

+@EndIf +@Each.posts +
+
+
+

+ @Current.title +

+ +

+   @Current.publishedDate +   @Current.publishedTime +

+ @Current.text +
+
+
+
+@EndEach +
+
+ // TODO: stopped here + if hasNext + - var nextLink = (2 < page) ? '/page/' + (page - 1) : '/' + if '/posts' == extraUrl && 2 == page + //- Leave the "next" link at '/' + else + - nextLink = extraUrl + nextLink + p: a.btn.btn-primary(href="#{nextLink}")= __("Newer Posts") +
+ .col-xs-3.text-right + if hasPrior + p: a.btn.btn-primary(href=extraUrl + '/page/' + (page + 1))= __("Older Posts") +
\ No newline at end of file diff --git a/src/myWebLog/views/default/index.html b/src/myWebLog/views/default/index.html new file mode 100644 index 0000000..1bd0be6 --- /dev/null +++ b/src/myWebLog/views/default/index.html @@ -0,0 +1,5 @@ +@Master['default/layout'] + +@Section['Content'] + @Partial['default/index-content', Model] +@EndSection \ No newline at end of file diff --git a/src/myWebLog/views/default/layout.html b/src/myWebLog/views/default/layout.html new file mode 100644 index 0000000..ed430a1 --- /dev/null +++ b/src/myWebLog/views/default/layout.html @@ -0,0 +1,46 @@ + + + + + + @Model.pageTitle | @Model.webLog.name + + + + @Section['Head']; + + +
+ +
+
+ @Section['Content']; +
+ @Section['Footer']; + + + @Section['Scripts']; + + \ No newline at end of file