From 710004dfc44bd936db1cd76c84864e63c67fb22c Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 6 Aug 2016 13:55:49 -0500 Subject: [PATCH] Huge repo reorganization In no particular order... - Created projects using F# generator, using Paket and FAKE - Split "entities" into their own project, and created interface for data functions required on those entities - Renamed "data" project and used it as an implementation of data access - Created "logic" layer that takes the data interface, and does the non-persistence-related manipulation of items - Moved "web" project to "app", and modified Nancy modules to utilize Logic project and data interface instead of Data project and RethinkDB connection - Created test placeholder project; will be filling that out shortly (TAD?) --- .gitignore | 4 +- src/.paket/paket.bootstrapper.exe | Bin 0 -> 28208 bytes .../AdminModule.fs | 9 +- src/{myWebLog.Web => MyWebLog.App}/App.fs | 19 +- .../AppConfig.fs | 2 +- .../AssemblyInfo.fs | 0 .../CategoryModule.fs | 37 ++- src/{myWebLog.Web => MyWebLog.App}/Keys.fs | 0 .../ModuleExtensions.fs | 0 src/MyWebLog.App/MyWebLog.App.fsproj | 302 ++++++++++++++++++ .../PageModule.fs | 25 +- .../PostModule.fs | 89 +++--- .../UserModule.fs | 15 +- .../ViewModels.fs | 2 +- src/MyWebLog.App/paket.references | 7 + .../AssemblyInfo.fs | 8 +- .../Category.fs | 85 ++--- .../DataConfig.fs | 2 +- .../Extensions.fs} | 3 +- .../MyWebLog.Data.RethinkDB.fsproj | 161 ++++++++++ src/MyWebLog.Data.RethinkDB/Page.fs | 70 ++++ .../Post.fs | 19 +- .../RethinkMyWebLogData.fs | 47 +++ .../SetUp.fs | 3 +- .../Table.fs | 2 +- .../User.fs | 3 +- .../WebLog.fs | 12 +- src/MyWebLog.Data.RethinkDB/paket.references | 2 + .../Entities.fs | 15 +- src/MyWebLog.Entities/IMyWebLogData.fs | 114 +++++++ .../MyWebLog.Entities.fsproj | 91 ++++++ src/MyWebLog.Entities/paket.references | 1 + src/MyWebLog.Logic/Category.fs | 56 ++++ .../MyWebLog.Logic.fsproj} | 95 ++---- src/MyWebLog.Logic/Page.fs | 29 ++ src/MyWebLog.Logic/Post.fs | 60 ++++ src/MyWebLog.Logic/User.fs | 7 + src/MyWebLog.Logic/WebLog.fs | 11 + src/MyWebLog.Tests/MyWebLog.Tests.fs | 4 + src/MyWebLog.Tests/MyWebLog.Tests.fsproj | 70 ++++ src/build.cmd | 14 + src/build.fsx | 42 +++ src/build.sh | 33 ++ src/myWebLog.Data/Page.fs | 77 ----- src/myWebLog.Data/packages.config | 10 - src/myWebLog.Web/myWebLog.Web.fsproj | 186 ----------- src/myWebLog.Web/packages.config | 19 -- src/myWebLog.sln | 39 +-- src/myWebLog/App.config | 15 +- src/myWebLog/myWebLog.csproj | 25 +- .../views/themes/default/index-content.html | 2 +- src/paket.dependencies | 13 + src/paket.lock | 34 ++ 53 files changed, 1418 insertions(+), 572 deletions(-) create mode 100644 src/.paket/paket.bootstrapper.exe rename src/{myWebLog.Web => MyWebLog.App}/AdminModule.fs (71%) rename src/{myWebLog.Web => MyWebLog.App}/App.fs (91%) rename src/{myWebLog.Web => MyWebLog.App}/AppConfig.fs (97%) rename src/{myWebLog.Web => MyWebLog.App}/AssemblyInfo.fs (100%) rename src/{myWebLog.Web => MyWebLog.App}/CategoryModule.fs (77%) rename src/{myWebLog.Web => MyWebLog.App}/Keys.fs (100%) rename src/{myWebLog.Web => MyWebLog.App}/ModuleExtensions.fs (100%) create mode 100644 src/MyWebLog.App/MyWebLog.App.fsproj rename src/{myWebLog.Web => MyWebLog.App}/PageModule.fs (87%) rename src/{myWebLog.Web => MyWebLog.App}/PostModule.fs (82%) rename src/{myWebLog.Web => MyWebLog.App}/UserModule.fs (85%) rename src/{myWebLog.Web => MyWebLog.App}/ViewModels.fs (99%) create mode 100644 src/MyWebLog.App/paket.references rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/AssemblyInfo.fs (68%) rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/Category.fs (51%) rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/DataConfig.fs (98%) rename src/{myWebLog.Data/Rethink.fs => MyWebLog.Data.RethinkDB/Extensions.fs} (84%) create mode 100644 src/MyWebLog.Data.RethinkDB/MyWebLog.Data.RethinkDB.fsproj create mode 100644 src/MyWebLog.Data.RethinkDB/Page.fs rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/Post.fs (94%) create mode 100644 src/MyWebLog.Data.RethinkDB/RethinkMyWebLogData.fs rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/SetUp.fs (98%) rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/Table.fs (89%) rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/User.fs (93%) rename src/{myWebLog.Data => MyWebLog.Data.RethinkDB}/WebLog.fs (80%) create mode 100644 src/MyWebLog.Data.RethinkDB/paket.references rename src/{myWebLog.Data => MyWebLog.Entities}/Entities.fs (95%) create mode 100644 src/MyWebLog.Entities/IMyWebLogData.fs create mode 100644 src/MyWebLog.Entities/MyWebLog.Entities.fsproj create mode 100644 src/MyWebLog.Entities/paket.references create mode 100644 src/MyWebLog.Logic/Category.fs rename src/{myWebLog.Data/myWebLog.Data.fsproj => MyWebLog.Logic/MyWebLog.Logic.fsproj} (57%) create mode 100644 src/MyWebLog.Logic/Page.fs create mode 100644 src/MyWebLog.Logic/Post.fs create mode 100644 src/MyWebLog.Logic/User.fs create mode 100644 src/MyWebLog.Logic/WebLog.fs create mode 100644 src/MyWebLog.Tests/MyWebLog.Tests.fs create mode 100644 src/MyWebLog.Tests/MyWebLog.Tests.fsproj create mode 100644 src/build.cmd create mode 100644 src/build.fsx create mode 100644 src/build.sh delete mode 100644 src/myWebLog.Data/Page.fs delete mode 100644 src/myWebLog.Data/packages.config delete mode 100644 src/myWebLog.Web/myWebLog.Web.fsproj delete mode 100644 src/myWebLog.Web/packages.config create mode 100644 src/paket.dependencies create mode 100644 src/paket.lock diff --git a/.gitignore b/.gitignore index f1e3d20..4fa91e2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Bb]uild/ +[Dd]eploy/ # Visual Studio 2015 cache/options directory .vs/ @@ -241,7 +243,7 @@ FakesAssemblies/ _Pvt_Extensions # Paket dependency manager -.paket/paket.exe +**/.paket/paket.exe paket-files/ # FAKE - F# Make diff --git a/src/.paket/paket.bootstrapper.exe b/src/.paket/paket.bootstrapper.exe new file mode 100644 index 0000000000000000000000000000000000000000..64fdf248bfcf48ee396f801e8a8a266b85974238 GIT binary patch literal 28208 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
SV|C^5SU~ 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 literal 0 HcmV?d00001 diff --git a/src/myWebLog.Web/AdminModule.fs b/src/MyWebLog.App/AdminModule.fs similarity index 71% rename from src/myWebLog.Web/AdminModule.fs rename to src/MyWebLog.App/AdminModule.fs index c89efa7..4a1ec4a 100644 --- a/src/myWebLog.Web/AdminModule.fs +++ b/src/MyWebLog.App/AdminModule.fs @@ -1,12 +1,13 @@ namespace MyWebLog -open MyWebLog.Data.WebLog +open MyWebLog.Data open MyWebLog.Entities +open MyWebLog.Logic.WebLog open Nancy open RethinkDb.Driver.Net /// Handle /admin routes -type AdminModule(conn : IConnection) as this = +type AdminModule(data : IMyWebLogData) as this = inherit NancyModule("/admin") do @@ -15,6 +16,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 data this.WebLog.Id) + model.PageTitle <- Resources.Dashboard upcast this.View.["admin/dashboard", model] diff --git a/src/myWebLog.Web/App.fs b/src/MyWebLog.App/App.fs similarity index 91% rename from src/myWebLog.Web/App.fs rename to src/MyWebLog.App/App.fs index 3c137a2..fd707e8 100644 --- a/src/myWebLog.Web/App.fs +++ b/src/MyWebLog.App/App.fs @@ -2,9 +2,9 @@ open MyWebLog open MyWebLog.Data -open MyWebLog.Data.SetUp -open MyWebLog.Data.WebLog +open MyWebLog.Data.RethinkDB open MyWebLog.Entities +open MyWebLog.Logic.WebLog open Nancy open Nancy.Authentication.Forms open Nancy.Bootstrapper @@ -13,6 +13,7 @@ open Nancy.Cryptography open Nancy.Owin open Nancy.Security open Nancy.Session.Persistable +//open Nancy.Session.Relational open Nancy.Session.RethinkDb open Nancy.TinyIoc open Nancy.ViewEngines.SuperSimpleViewEngine @@ -21,6 +22,7 @@ open RethinkDb.Driver.Net open Suave open Suave.Owin open System +open System.Configuration open System.IO open System.Text.RegularExpressions @@ -28,9 +30,11 @@ open System.Text.RegularExpressions let cfg = try AppConfig.FromJson (System.IO.File.ReadAllText "config.json") with ex -> raise <| ApplicationException(Resources.ErrBadAppConfig, ex) +let data : IMyWebLogData = upcast RethinkMyWebLogData(cfg.DataConfig.Conn, cfg.DataConfig) + do - startUpCheck cfg.DataConfig - + data.SetUp () + /// Support RESX lookup via the @Translate SSVE alias type TranslateTokenViewEngineMatcher() = static let regex = Regex("@Translate\.(?[a-zA-Z0-9-_]+);?", RegexOptions.Compiled) @@ -83,10 +87,10 @@ type MyWebLogBootstrapper() = override this.ApplicationStartup (container, pipelines) = base.ApplicationStartup (container, pipelines) - // Data configuration (both config and the connection; Nancy modules just need the connection) + // Application configuration container.Register(cfg) |> ignore - container.Register(cfg.DataConfig.Conn) + container.Register(data) |> ignore // NodaTime container.Register(SystemClock.Instance) @@ -110,6 +114,7 @@ type MyWebLogBootstrapper() = // Sessions let sessions = RethinkDbSessionConfiguration(cfg.DataConfig.Conn) sessions.Database <- cfg.DataConfig.Database + //let sessions = RelationalSessionConfiguration(ConfigurationManager.ConnectionStrings.["SessionStore"].ConnectionString) PersistableSessions.Enable (pipelines, sessions) () @@ -127,7 +132,7 @@ type RequestEnvironment() = member this.Initialize (pipelines, context) = let establishEnv (ctx : NancyContext) = ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks - match tryFindWebLogByUrlBase cfg.DataConfig.Conn ctx.Request.Url.HostName with + match tryFindWebLogByUrlBase data 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) diff --git a/src/myWebLog.Web/AppConfig.fs b/src/MyWebLog.App/AppConfig.fs similarity index 97% rename from src/myWebLog.Web/AppConfig.fs rename to src/MyWebLog.App/AppConfig.fs index dbc2762..915a18e 100644 --- a/src/myWebLog.Web/AppConfig.fs +++ b/src/MyWebLog.App/AppConfig.fs @@ -1,6 +1,6 @@ namespace MyWebLog -open MyWebLog.Data +open MyWebLog.Data.RethinkDB open Newtonsoft.Json open System.Text diff --git a/src/myWebLog.Web/AssemblyInfo.fs b/src/MyWebLog.App/AssemblyInfo.fs similarity index 100% rename from src/myWebLog.Web/AssemblyInfo.fs rename to src/MyWebLog.App/AssemblyInfo.fs diff --git a/src/myWebLog.Web/CategoryModule.fs b/src/MyWebLog.App/CategoryModule.fs similarity index 77% rename from src/myWebLog.Web/CategoryModule.fs rename to src/MyWebLog.App/CategoryModule.fs index f0a67d9..73aba5f 100644 --- a/src/myWebLog.Web/CategoryModule.fs +++ b/src/MyWebLog.App/CategoryModule.fs @@ -1,6 +1,7 @@ namespace MyWebLog -open MyWebLog.Data.Category +open MyWebLog.Data +open MyWebLog.Logic.Category open MyWebLog.Entities open Nancy open Nancy.ModelBinding @@ -8,7 +9,7 @@ open Nancy.Security open RethinkDb.Driver.Net /// Handle /category and /categories URLs -type CategoryModule(conn : IConnection) as this = +type CategoryModule(data : IMyWebLogData) as this = inherit NancyModule() do @@ -21,7 +22,7 @@ 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 + (findAllCategories data this.WebLog.Id |> List.map (fun cat -> IndentedCategory.Create cat (fun _ -> false)))) upcast this.View.["/admin/category/list", model] @@ -31,13 +32,13 @@ type CategoryModule(conn : IConnection) as this = let catId = parameters.["id"].ToString () match (match catId with | "new" -> Some Category.Empty - | _ -> tryFindCategory conn this.WebLog.Id catId) with + | _ -> tryFindCategory data this.WebLog.Id catId) with | Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat) - model.Categories <- getAllCategories conn this.WebLog.Id + model.Categories <- findAllCategories data 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 () + | _ -> this.NotFound () /// Save a category member this.SaveCategory (parameters : DynamicDictionary) = @@ -45,41 +46,43 @@ type CategoryModule(conn : IConnection) as this = this.RequiresAccessLevel AuthorizationLevel.Administrator let catId = parameters.["id"].ToString () let form = this.Bind () - let oldCat = match catId with "new" -> Some Category.Empty | _ -> tryFindCategory conn this.WebLog.Id catId + let oldCat = match catId with + | "new" -> Some { Category.Empty with WebLogId = this.WebLog.Id } + | _ -> tryFindCategory data 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 + let newCatId = saveCategory data cat match old.ParentId = cat.ParentId with | true -> () | _ -> match old.ParentId with - | Some parentId -> removeCategoryFromParent conn this.WebLog.Id parentId newCatId - | None -> () + | Some parentId -> removeCategoryFromParent data this.WebLog.Id parentId newCatId + | _ -> () match cat.ParentId with - | Some parentId -> addCategoryToParent conn this.WebLog.Id parentId newCatId - | None -> () + | Some parentId -> addCategoryToParent data this.WebLog.Id parentId newCatId + | _ -> () let model = MyWebLogModel(this.Context, this.WebLog) { UserMessage.Empty with Level = Level.Info Message = System.String.Format (Resources.MsgCategoryEditSuccess, - (match catId with | "new" -> Resources.Added | _ -> Resources.Updated)) } + (match catId with "new" -> Resources.Added | _ -> Resources.Updated)) } |> model.AddMessage this.Redirect (sprintf "/category/%s/edit" newCatId) model - | None -> this.NotFound () + | _ -> this.NotFound () /// Delete a category member this.DeleteCategory (parameters : DynamicDictionary) = this.ValidateCsrfToken () this.RequiresAccessLevel AuthorizationLevel.Administrator let catId = parameters.["id"].ToString () - match tryFindCategory conn this.WebLog.Id catId with - | Some cat -> deleteCategory conn cat + match tryFindCategory data this.WebLog.Id catId with + | Some cat -> deleteCategory data cat let model = MyWebLogModel(this.Context, this.WebLog) { UserMessage.Empty with Level = Level.Info Message = System.String.Format(Resources.MsgCategoryDeleted, cat.Name) } |> model.AddMessage this.Redirect "/categories" model - | None -> this.NotFound () + | _ -> this.NotFound () diff --git a/src/myWebLog.Web/Keys.fs b/src/MyWebLog.App/Keys.fs similarity index 100% rename from src/myWebLog.Web/Keys.fs rename to src/MyWebLog.App/Keys.fs diff --git a/src/myWebLog.Web/ModuleExtensions.fs b/src/MyWebLog.App/ModuleExtensions.fs similarity index 100% rename from src/myWebLog.Web/ModuleExtensions.fs rename to src/MyWebLog.App/ModuleExtensions.fs diff --git a/src/MyWebLog.App/MyWebLog.App.fsproj b/src/MyWebLog.App/MyWebLog.App.fsproj new file mode 100644 index 0000000..1b629d8 --- /dev/null +++ b/src/MyWebLog.App/MyWebLog.App.fsproj @@ -0,0 +1,302 @@ + + + + + Debug + AnyCPU + 2.0 + 9cea3a8b-e8aa-44e6-9f5f-2095ceed54eb + Library + MyWebLog.App + MyWebLog.App + v4.5.2 + 4.4.0.0 + MyWebLog.App + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\MyWebLog.App.xml + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\MyWebLog.App.xml + + + + + True + + + + + + + + + + + + + + + + + + + + + + MyWebLog.Data.RethinkDB + {d6c2be5e-883a-4f34-9905-b730543ca380} + True + + + MyWebLog.Entities + {a87f3cf5-2189-442b-8acf-929f5153ac22} + True + + + MyWebLog.Logic + {29f6eda3-4f43-4bb3-9c63-ae238a9b7f12} + True + + + MyWebLog.Resources + {a12ea8da-88bc-4447-90cb-a0e2dcc37523} + True + + + + 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\lib\net40\Common.Logging.dll + True + True + + + + + + + + + ..\packages\Common.Logging.Core\lib\net40\Common.Logging.Core.dll + True + True + + + + + + + + + ..\packages\FSharp.Compiler.Service\lib\net40\FSharp.Compiler.Service.dll + True + True + + + + + + + ..\packages\FSharp.Compiler.Service\lib\net45\FSharp.Compiler.Service.dll + True + True + + + + + + + + + ..\packages\FSharp.Formatting\lib\net40\CSharpFormat.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\FSharp.CodeFormat.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\FSharp.Formatting.Common.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\FSharp.Literate.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\FSharp.Markdown.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\FSharp.MetadataFormat.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\RazorEngine.dll + True + True + + + ..\packages\FSharp.Formatting\lib\net40\System.Web.Razor.dll + True + True + + + + + + + + + ..\packages\FSharpVSPowerTools.Core\lib\net45\FSharpVSPowerTools.Core.dll + True + True + + + + + + + + + ..\packages\Nancy\lib\net40\Nancy.dll + True + True + + + + + + + + + ..\packages\Nancy.Authentication.Forms\lib\net40\Nancy.Authentication.Forms.dll + True + True + + + + + + + + + ..\packages\Nancy.Session.Persistable\lib\net452\Nancy.Session.Persistable.dll + True + True + + + + + + + + + ..\packages\Nancy.Session.RethinkDB\lib\net452\Nancy.Session.RethinkDb.dll + True + True + + + + + + + + + ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + + + + + ..\packages\NodaTime\lib\net35-Client\NodaTime.dll + True + True + + + True + + + + + + + + + ..\packages\RethinkDb.Driver\lib\net45\RethinkDb.Driver.dll + True + True + + + + + + + + + ..\packages\Suave\lib\net40\Suave.dll + True + True + + + + + \ No newline at end of file diff --git a/src/myWebLog.Web/PageModule.fs b/src/MyWebLog.App/PageModule.fs similarity index 87% rename from src/myWebLog.Web/PageModule.fs rename to src/MyWebLog.App/PageModule.fs index 17f8d3a..130a3ca 100644 --- a/src/myWebLog.Web/PageModule.fs +++ b/src/MyWebLog.App/PageModule.fs @@ -1,8 +1,9 @@ namespace MyWebLog open FSharp.Markdown -open MyWebLog.Data.Page +open MyWebLog.Data open MyWebLog.Entities +open MyWebLog.Logic.Page open Nancy open Nancy.ModelBinding open Nancy.Security @@ -10,7 +11,7 @@ open NodaTime open RethinkDb.Driver.Net /// Handle /pages and /page URLs -type PageModule(conn : IConnection, clock : IClock) as this = +type PageModule(data : IMyWebLogData, clock : IClock) as this = inherit NancyModule() do @@ -22,7 +23,7 @@ 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 data this.WebLog.Id |> List.map (fun p -> PageForDisplay(this.WebLog, p)))) model.PageTitle <- Resources.Pages upcast this.View.["admin/page/list", model] @@ -33,16 +34,16 @@ type PageModule(conn : IConnection, clock : IClock) as this = let pageId = parameters.["id"].ToString () match (match pageId with | "new" -> Some Page.Empty - | _ -> tryFindPage conn this.WebLog.Id pageId) with + | _ -> tryFindPage data 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 + | _ -> Revision.Empty let model = EditPageModel(this.Context, this.WebLog, page, rev) model.PageTitle <- match pageId with "new" -> Resources.AddNewPage | _ -> Resources.EditPage upcast this.View.["admin/page/edit", model] - | None -> this.NotFound () + | _ -> this.NotFound () /// Save a page member this.SavePage (parameters : DynamicDictionary) = @@ -51,7 +52,7 @@ type PageModule(conn : IConnection, clock : IClock) as this = let pageId = parameters.["id"].ToString () let form = this.Bind () let now = clock.Now.Ticks - match (match pageId with "new" -> Some Page.Empty | _ -> tryFindPage conn this.WebLog.Id pageId) with + match (match pageId with "new" -> Some Page.Empty | _ -> tryFindPage data 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 @@ -64,7 +65,7 @@ type PageModule(conn : IConnection, clock : IClock) as this = Revisions = { AsOf = now SourceType = form.Source Text = form.Text } :: page.Revisions } - |> savePage conn + |> savePage data let model = MyWebLogModel(this.Context, this.WebLog) { UserMessage.Empty with Level = Level.Info @@ -73,18 +74,18 @@ type PageModule(conn : IConnection, clock : IClock) as this = (match pageId with "new" -> Resources.Added | _ -> Resources.Updated)) } |> model.AddMessage this.Redirect (sprintf "/page/%s/edit" pId) model - | None -> this.NotFound () + | _ -> this.NotFound () /// Delete a page member this.DeletePage (parameters : DynamicDictionary) = 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 data this.WebLog.Id pageId with + | Some page -> deletePage data page.WebLogId page.Id let model = MyWebLogModel(this.Context, this.WebLog) { UserMessage.Empty with Level = Level.Info Message = Resources.MsgPageDeleted } |> model.AddMessage this.Redirect "/pages" model - | None -> this.NotFound () + | _ -> this.NotFound () diff --git a/src/myWebLog.Web/PostModule.fs b/src/MyWebLog.App/PostModule.fs similarity index 82% rename from src/myWebLog.Web/PostModule.fs rename to src/MyWebLog.App/PostModule.fs index 466152b..f4a3018 100644 --- a/src/myWebLog.Web/PostModule.fs +++ b/src/MyWebLog.App/PostModule.fs @@ -1,10 +1,11 @@ namespace MyWebLog open FSharp.Markdown -open MyWebLog.Data.Category -open MyWebLog.Data.Page -open MyWebLog.Data.Post +open MyWebLog.Data open MyWebLog.Entities +open MyWebLog.Logic.Category +open MyWebLog.Logic.Page +open MyWebLog.Logic.Post open Nancy open Nancy.ModelBinding open Nancy.Security @@ -15,7 +16,7 @@ open System open System.ServiceModel.Syndication /// Routes dealing with posts (including the home page, /tag, /category, RSS, and catch-all routes) -type PostModule(conn : IConnection, clock : IClock) as this = +type PostModule(data : IMyWebLogData, clock : IClock) as this = inherit NancyModule() /// Get the page number from the dictionary @@ -27,14 +28,14 @@ type PostModule(conn : IConnection, clock : IClock) as this = /// Generate an RSS/Atom feed of the latest posts let generateFeed format : obj = - let posts = findFeedPosts conn this.WebLog.Id 10 + let posts = findFeedPosts data this.WebLog.Id 10 let feed = SyndicationFeed( this.WebLog.Name, defaultArg this.WebLog.Subtitle null, Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase), null, (match posts |> List.tryHead with | Some (post, _) -> Instant(post.UpdatedOn).ToDateTimeOffset () - | _ -> System.DateTimeOffset(System.DateTime.MinValue)), + | _ -> System.DateTimeOffset(System.DateTime.MinValue)), posts |> List.map (fun (post, user) -> let item = @@ -76,15 +77,15 @@ type PostModule(conn : IConnection, clock : IClock) as this = 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.Posts <- findPageOfPublishedPosts data this.WebLog.Id pageNbr 10 |> forDisplay model.HasNewer <- match pageNbr with | 1 -> false | _ -> match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindNewerPost conn (List.last model.Posts).Post + | _ -> Option.isSome <| tryFindNewerPost data (List.last model.Posts).Post model.HasOlder <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindOlderPost conn (List.head model.Posts).Post + | _ -> Option.isSome <| tryFindOlderPost data (List.head model.Posts).Post model.UrlPrefix <- "/posts" model.PageTitle <- match pageNbr with 1 -> "" | _ -> sprintf "%s%i" Resources.PageHash pageNbr this.ThemedView "index" model @@ -93,59 +94,59 @@ type PostModule(conn : IConnection, clock : IClock) as this = member this.HomePage () = match this.WebLog.DefaultPage with | "posts" -> this.PublishedPostsPage 1 - | pageId -> match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with + | pageId -> match tryFindPageWithoutRevisions data this.WebLog.Id pageId with | Some page -> let model = PageModel(this.Context, this.WebLog, page) model.PageTitle <- page.Title this.ThemedView "page" model - | None -> this.NotFound () + | _ -> 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 data 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.NewerPost <- tryFindNewerPost data post + model.OlderPost <- tryFindOlderPost data 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 - | Some page -> // ...and it is! - let model = PageModel(this.Context, this.WebLog, page) - 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 - | Some post -> // Redirect them to the proper permalink - upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink) - .WithStatusCode HttpStatusCode.MovedPermanently - | None -> this.NotFound () + | _ -> // Maybe it's a page permalink instead... + match tryFindPageByPermalink data this.WebLog.Id url with + | Some page -> // ...and it is! + let model = PageModel(this.Context, this.WebLog, page) + model.PageTitle <- page.Title + this.ThemedView "page" model + | _ -> // Maybe it's an old permalink for a post + match tryFindPostByPriorPermalink data this.WebLog.Id url with + | Some post -> // Redirect them to the proper permalink + upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink) + .WithStatusCode HttpStatusCode.MovedPermanently + | _ -> 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 data 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.Posts <- findPageOfCategorizedPosts data this.WebLog.Id cat.Id pageNbr 10 |> forDisplay model.HasNewer <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.Id + | _ -> Option.isSome <| tryFindNewerCategorizedPost data cat.Id (List.head model.Posts).Post model.HasOlder <- match List.isEmpty model.Posts with | true -> false - | _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.Id + | _ -> Option.isSome <| tryFindOlderCategorizedPost data 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 | Some desc -> desc - | None -> sprintf "Posts in the \"%s\" category" cat.Name + | _ -> sprintf "Posts in the \"%s\" category" cat.Name this.ThemedView "index" model - | None -> this.NotFound () + | _ -> this.NotFound () /// Display tagged posts member this.TaggedPosts (parameters : DynamicDictionary) = @@ -153,13 +154,13 @@ type PostModule(conn : IConnection, clock : IClock) as this = 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.Posts <- findPageOfTaggedPosts data 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 + | _ -> Option.isSome <| tryFindNewerTaggedPost data 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 + | _ -> Option.isSome <| tryFindOlderTaggedPost data 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 @@ -182,7 +183,7 @@ type PostModule(conn : IConnection, clock : IClock) as this = 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.Posts <- findPageOfAllPosts data this.WebLog.Id pageNbr 25 |> forDisplay model.HasNewer <- pageNbr > 1 model.HasOlder <- List.length model.Posts > 24 model.UrlPrefix <- "/posts/list" @@ -193,21 +194,21 @@ type PostModule(conn : IConnection, clock : IClock) as this = member this.EditPost (parameters : DynamicDictionary) = this.RequiresAccessLevel AuthorizationLevel.Administrator let postId = parameters.["postId"].ToString () - match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost conn this.WebLog.Id postId) with + match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost data 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 let model = EditPostModel(this.Context, this.WebLog, post, rev) - model.Categories <- getAllCategories conn this.WebLog.Id + model.Categories <- findAllCategories data 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 upcast this.View.["admin/post/edit"] - | None -> this.NotFound () + | _ -> this.NotFound () /// Save a post member this.SavePost (parameters : DynamicDictionary) = @@ -216,7 +217,7 @@ type PostModule(conn : IConnection, clock : IClock) as this = 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 + match (match postId with "new" -> Some Post.Empty | _ -> tryFindPost data this.WebLog.Id postId) with | Some p -> let justPublished = p.PublishedOn = int64 0 && form.PublishNow let post = match postId with | "new" -> { p with @@ -242,14 +243,14 @@ type PostModule(conn : IConnection, clock : IClock) as this = Revisions = { AsOf = now SourceType = form.Source Text = form.Text } :: post.Revisions } - |> savePost conn + |> savePost data let model = MyWebLogModel(this.Context, this.WebLog) { 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 | _ -> "")) } + (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 () + | _ -> this.NotFound () diff --git a/src/myWebLog.Web/UserModule.fs b/src/MyWebLog.App/UserModule.fs similarity index 85% rename from src/myWebLog.Web/UserModule.fs rename to src/MyWebLog.App/UserModule.fs index fe51ccc..1dc6b8f 100644 --- a/src/myWebLog.Web/UserModule.fs +++ b/src/MyWebLog.App/UserModule.fs @@ -1,7 +1,8 @@ namespace MyWebLog -open MyWebLog.Data.User +open MyWebLog.Data open MyWebLog.Entities +open MyWebLog.Logic.User open Nancy open Nancy.Authentication.Forms open Nancy.Cryptography @@ -12,7 +13,7 @@ open RethinkDb.Driver.Net open System.Text /// Handle /user URLs -type UserModule(conn : IConnection, cfg : AppConfig) as this = +type UserModule(data : IMyWebLogData, cfg : AppConfig) as this = inherit NancyModule("/user") /// Hash the user's password @@ -37,7 +38,7 @@ type UserModule(conn : IConnection, cfg : AppConfig) 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 data form.Email (pbkdf2 form.Password) with | Some user -> this.Session.[Keys.User] <- user { UserMessage.Empty with Level = Level.Info Message = Resources.MsgLogOnSuccess } @@ -46,10 +47,10 @@ type UserModule(conn : IConnection, cfg : AppConfig) as this = // 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 -> { UserMessage.Empty with Level = Level.Error - Message = Resources.ErrBadLogOnAttempt } - |> model.AddMessage - this.Redirect (sprintf "/user/logon?returnUrl=%s" form.ReturnUrl) model + | _ -> { 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 () = diff --git a/src/myWebLog.Web/ViewModels.fs b/src/MyWebLog.App/ViewModels.fs similarity index 99% rename from src/myWebLog.Web/ViewModels.fs rename to src/MyWebLog.App/ViewModels.fs index 4c13656..7670570 100644 --- a/src/myWebLog.Web/ViewModels.fs +++ b/src/MyWebLog.App/ViewModels.fs @@ -1,7 +1,7 @@ namespace MyWebLog -open MyWebLog.Data.WebLog open MyWebLog.Entities +open MyWebLog.Logic.WebLog open Nancy open Nancy.Session.Persistable open Newtonsoft.Json diff --git a/src/MyWebLog.App/paket.references b/src/MyWebLog.App/paket.references new file mode 100644 index 0000000..baa3c1b --- /dev/null +++ b/src/MyWebLog.App/paket.references @@ -0,0 +1,7 @@ +FSharp.Formatting +Nancy +Nancy.Authentication.Forms +Nancy.Session.RethinkDB +NodaTime +RethinkDb.Driver +Suave \ No newline at end of file diff --git a/src/myWebLog.Data/AssemblyInfo.fs b/src/MyWebLog.Data.RethinkDB/AssemblyInfo.fs similarity index 68% rename from src/myWebLog.Data/AssemblyInfo.fs rename to src/MyWebLog.Data.RethinkDB/AssemblyInfo.fs index e5d523c..8325c20 100644 --- a/src/myWebLog.Data/AssemblyInfo.fs +++ b/src/MyWebLog.Data.RethinkDB/AssemblyInfo.fs @@ -4,17 +4,17 @@ open System.Reflection open System.Runtime.CompilerServices open System.Runtime.InteropServices -[] -[] +[] +[] [] [] -[] +[] [] [] [] [] [] -[] +[] [] do diff --git a/src/myWebLog.Data/Category.fs b/src/MyWebLog.Data.RethinkDB/Category.fs similarity index 51% rename from src/myWebLog.Data/Category.fs rename to src/MyWebLog.Data.RethinkDB/Category.fs index 043b287..da98279 100644 --- a/src/myWebLog.Data/Category.fs +++ b/src/MyWebLog.Data.RethinkDB/Category.fs @@ -1,8 +1,7 @@ -module MyWebLog.Data.Category +module MyWebLog.Data.RethinkDB.Category open FSharp.Interop.Dynamic open MyWebLog.Entities -open Rethink open RethinkDb.Driver.Ast open System.Dynamic @@ -14,20 +13,6 @@ let private category (webLogId : string) (catId : string) = .Get(catId) .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 - yield! getChildren child (indent + 1) - } - categories - |> List.filter (fun c -> c.ParentId.IsNone) - |> List.map (fun c -> getChildren c 0) - |> Seq.collect id - |> Seq.toList - /// Get all categories for a web log let getAllCategories conn (webLogId : string) = r.Table(Table.Category) @@ -36,7 +21,6 @@ let getAllCategories conn (webLogId : string) = .RunListAsync(conn) |> await |> Seq.toList - |> sortCategories /// Get a specific category by its Id let tryFindCategory conn webLogId catId : Category option = @@ -45,52 +29,41 @@ let tryFindCategory conn webLogId catId : Category option = | null -> None | cat -> Some <| unbox cat -/// 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 } - r.Table(Table.Category) - .Insert(newCat) - .RunResultAsync(conn) |> await |> ignore - 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) - .Update(upd8) - .RunResultAsync(conn) |> await |> ignore - cat.Id +/// Add a category +let addCategory conn (cat : Category) = + r.Table(Table.Category) + .Insert(cat) + .RunResultAsync(conn) |> await |> ignore -/// 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 - |> List.filter (fun childId -> childId <> catId) - (category webLogId parentId) - .Update(upd8) - .RunResultAsync(conn) |> await |> ignore - | None -> () +/// Update a category +let updateCategory conn (cat : Category) = + let upd8 = ExpandoObject() + upd8?Name <- cat.Name + upd8?Slug <- cat.Slug + upd8?Description <- cat.Description + upd8?ParentId <- cat.ParentId + (category cat.WebLogId cat.Id) + .Update(upd8) + .RunResultAsync(conn) |> await |> ignore -/// Add a category to a given parent -let addCategoryToParent conn webLogId parentId catId = - match tryFindCategory conn webLogId parentId with - | Some parent -> let upd8 = ExpandoObject() - upd8?Children <- catId :: parent.Children - (category webLogId parentId) - .Update(upd8) - .RunResultAsync(conn) |> await |> ignore - | None -> () +/// Update a category's children +let updateChildren conn webLogId parentId (children : string list) = + let upd8 = ExpandoObject() + upd8?Children <- children + (category webLogId parentId) + .Update(upd8) + .RunResultAsync(conn) |> await |> ignore /// 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 - | None -> () + | Some parentId -> match tryFindCategory conn cat.WebLogId parentId with + | Some parent -> parent.Children + |> List.filter (fun childId -> childId <> cat.Id) + |> updateChildren conn cat.WebLogId parentId + | _ -> () + | _ -> () // Move this category's children to its parent let newParent = ExpandoObject() newParent?ParentId <- cat.ParentId diff --git a/src/myWebLog.Data/DataConfig.fs b/src/MyWebLog.Data.RethinkDB/DataConfig.fs similarity index 98% rename from src/myWebLog.Data/DataConfig.fs rename to src/MyWebLog.Data.RethinkDB/DataConfig.fs index 95e63f5..86057f5 100644 --- a/src/myWebLog.Data/DataConfig.fs +++ b/src/MyWebLog.Data.RethinkDB/DataConfig.fs @@ -1,4 +1,4 @@ -namespace MyWebLog.Data +namespace MyWebLog.Data.RethinkDB open RethinkDb.Driver open RethinkDb.Driver.Net diff --git a/src/myWebLog.Data/Rethink.fs b/src/MyWebLog.Data.RethinkDB/Extensions.fs similarity index 84% rename from src/myWebLog.Data/Rethink.fs rename to src/MyWebLog.Data.RethinkDB/Extensions.fs index 2d92d39..747f212 100644 --- a/src/myWebLog.Data/Rethink.fs +++ b/src/MyWebLog.Data.RethinkDB/Extensions.fs @@ -1,4 +1,5 @@ -module MyWebLog.Data.Rethink +[] +module MyWebLog.Data.RethinkDB.Extensions open RethinkDb.Driver.Ast open RethinkDb.Driver.Net diff --git a/src/MyWebLog.Data.RethinkDB/MyWebLog.Data.RethinkDB.fsproj b/src/MyWebLog.Data.RethinkDB/MyWebLog.Data.RethinkDB.fsproj new file mode 100644 index 0000000..d71b9e4 --- /dev/null +++ b/src/MyWebLog.Data.RethinkDB/MyWebLog.Data.RethinkDB.fsproj @@ -0,0 +1,161 @@ + + + + + Debug + AnyCPU + 2.0 + d6c2be5e-883a-4f34-9905-b730543ca380 + Library + MyWebLog.Data.RethinkDB + MyWebLog.Data.RethinkDB + v4.5.2 + 4.4.0.0 + MyWebLog.Data.RethinkDB + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\MyWebLog.Data.RethinkDB.xml + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\MyWebLog.Data.RethinkDB.xml + + + + + True + + + + + + + + + + + + + + + + + + + + MyWebLog.Entities + {a87f3cf5-2189-442b-8acf-929f5153ac22} + True + + + + 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\lib\net40\Common.Logging.dll + True + True + + + + + + + + + ..\packages\Common.Logging.Core\lib\net40\Common.Logging.Core.dll + True + True + + + + + + + + + ..\packages\Dynamitey\lib\net40\Dynamitey.dll + True + True + + + + + + + + + ..\packages\FSharp.Interop.Dynamic\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll + True + True + + + + + + + + + ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + + + + + ..\packages\RethinkDb.Driver\lib\net45\RethinkDb.Driver.dll + True + True + + + + + \ No newline at end of file diff --git a/src/MyWebLog.Data.RethinkDB/Page.fs b/src/MyWebLog.Data.RethinkDB/Page.fs new file mode 100644 index 0000000..ea42253 --- /dev/null +++ b/src/MyWebLog.Data.RethinkDB/Page.fs @@ -0,0 +1,70 @@ +module MyWebLog.Data.RethinkDB.Page + +open FSharp.Interop.Dynamic +open MyWebLog.Entities +open RethinkDb.Driver.Ast +open System.Dynamic + +let private r = RethinkDb.Driver.RethinkDB.R + +/// Try to find a page by its Id, optionally including revisions +let tryFindPageById conn webLogId (pageId : string) includeRevs = + let pg = r.Table(Table.Page) + .Get(pageId) + match (match includeRevs with + | true -> pg.RunAtomAsync(conn) + | _ -> pg.Without("Revisions").RunAtomAsync(conn) + |> await |> box) with + | null -> None + | page -> let pg : Page = unbox page + match pg.WebLogId = webLogId with true -> Some pg | _ -> None + +/// 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") + .RunCursorAsync(conn) + |> await + |> Seq.tryHead + +/// 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") + .RunListAsync(conn) + |> await + |> Seq.toList + +/// Add a page +let addPage conn (page : Page) = + r.Table(Table.Page) + .Insert(page) + .RunResultAsync(conn) |> await |> ignore + +/// Update a page +let updatePage conn (page : Page) = + match tryFindPageById conn page.WebLogId page.Id false with + | Some _ -> let upd8 = ExpandoObject() + upd8?Title <- page.Title + upd8?Permalink <- page.Permalink + upd8?PublishedOn <- page.PublishedOn + upd8?UpdatedOn <- page.UpdatedOn + upd8?Text <- page.Text + upd8?Revisions <- page.Revisions + r.Table(Table.Page) + .Get(page.Id) + .Update(upd8) + .RunResultAsync(conn) |> await |> ignore + | _ -> () + +/// Delete a page +let deletePage conn webLogId pageId = + match tryFindPageById conn webLogId pageId false with + | Some _ -> r.Table(Table.Page) + .Get(pageId) + .Delete() + .RunResultAsync(conn) |> await |> ignore + | _ -> () diff --git a/src/myWebLog.Data/Post.fs b/src/MyWebLog.Data.RethinkDB/Post.fs similarity index 94% rename from src/myWebLog.Data/Post.fs rename to src/MyWebLog.Data.RethinkDB/Post.fs index d78a201..93e18d0 100644 --- a/src/myWebLog.Data/Post.fs +++ b/src/MyWebLog.Data.RethinkDB/Post.fs @@ -1,8 +1,7 @@ -module MyWebLog.Data.Post +module MyWebLog.Data.RethinkDB.Post open FSharp.Interop.Dynamic open MyWebLog.Entities -open Rethink open RethinkDb.Driver.Ast open System.Dynamic @@ -146,6 +145,22 @@ let findFeedPosts conn webLogId nbr : (Post * User option) list = | null -> None | user -> Some <| unbox user) +/// Add a post +let addPost conn post = + r.Table(Table.Post) + .Insert(post) + .RunResultAsync(conn) + |> ignore + +/// Update a post +let updatePost conn post = + r.Table(Table.Post) + .Get(post.Id) + .Replace( { post with Categories = [] + Comments = [] } ) + .RunResultAsync(conn) + |> ignore + /// Save a post let savePost conn post = match post.Id with diff --git a/src/MyWebLog.Data.RethinkDB/RethinkMyWebLogData.fs b/src/MyWebLog.Data.RethinkDB/RethinkMyWebLogData.fs new file mode 100644 index 0000000..a310399 --- /dev/null +++ b/src/MyWebLog.Data.RethinkDB/RethinkMyWebLogData.fs @@ -0,0 +1,47 @@ +namespace MyWebLog.Data.RethinkDB + +open MyWebLog.Data +open RethinkDb.Driver.Net + +/// RethinkDB implementation of myWebLog data persistence +type RethinkMyWebLogData(conn : IConnection, cfg : DataConfig) = + interface IMyWebLogData with + member this.SetUp = fun () -> SetUp.startUpCheck cfg + + member this.AllCategories = Category.getAllCategories conn + member this.CategoryById = Category.tryFindCategory conn + member this.CategoryBySlug = Category.tryFindCategoryBySlug conn + member this.AddCategory = Category.addCategory conn + member this.UpdateCategory = Category.updateCategory conn + member this.UpdateChildren = Category.updateChildren conn + member this.DeleteCategory = Category.deleteCategory conn + + member this.PageById = Page.tryFindPageById conn + member this.PageByPermalink = Page.tryFindPageByPermalink conn + member this.AllPages = Page.findAllPages conn + member this.AddPage = Page.addPage conn + member this.UpdatePage = Page.updatePage conn + member this.DeletePage = Page.deletePage conn + + member this.PageOfPublishedPosts = Post.findPageOfPublishedPosts conn + member this.PageOfCategorizedPosts = Post.findPageOfCategorizedPosts conn + member this.PageOfTaggedPosts = Post.findPageOfTaggedPosts conn + member this.NewerPost = Post.tryFindNewerPost conn + member this.NewerCategorizedPost = Post.tryFindNewerCategorizedPost conn + member this.NewerTaggedPost = Post.tryFindNewerTaggedPost conn + member this.OlderPost = Post.tryFindOlderPost conn + member this.OlderCategorizedPost = Post.tryFindOlderCategorizedPost conn + member this.OlderTaggedPost = Post.tryFindOlderTaggedPost conn + member this.PageOfAllPosts = Post.findPageOfAllPosts conn + member this.PostById = Post.tryFindPost conn + member this.PostByPermalink = Post.tryFindPostByPermalink conn + member this.PostByPriorPermalink = Post.tryFindPostByPriorPermalink conn + member this.FeedPosts = Post.findFeedPosts conn + member this.AddPost = Post.addPost conn + member this.UpdatePost = Post.updatePost conn + + member this.LogOn = User.tryUserLogOn conn + + member this.WebLogByUrlBase = WebLog.tryFindWebLogByUrlBase conn + member this.DashboardCounts = WebLog.findDashboardCounts conn + \ No newline at end of file diff --git a/src/myWebLog.Data/SetUp.fs b/src/MyWebLog.Data.RethinkDB/SetUp.fs similarity index 98% rename from src/myWebLog.Data/SetUp.fs rename to src/MyWebLog.Data.RethinkDB/SetUp.fs index 676415d..244af16 100644 --- a/src/myWebLog.Data/SetUp.fs +++ b/src/MyWebLog.Data.RethinkDB/SetUp.fs @@ -1,6 +1,5 @@ -module MyWebLog.Data.SetUp +module MyWebLog.Data.RethinkDB.SetUp -open Rethink open RethinkDb.Driver.Ast open System diff --git a/src/myWebLog.Data/Table.fs b/src/MyWebLog.Data.RethinkDB/Table.fs similarity index 89% rename from src/myWebLog.Data/Table.fs rename to src/MyWebLog.Data.RethinkDB/Table.fs index 082f3cb..8a84652 100644 --- a/src/myWebLog.Data/Table.fs +++ b/src/MyWebLog.Data.RethinkDB/Table.fs @@ -1,6 +1,6 @@ /// Constants for tables used in myWebLog [] -module MyWebLog.Data.Table +module MyWebLog.Data.RethinkDB.Table /// The Category table let Category = "Category" diff --git a/src/myWebLog.Data/User.fs b/src/MyWebLog.Data.RethinkDB/User.fs similarity index 93% rename from src/myWebLog.Data/User.fs rename to src/MyWebLog.Data.RethinkDB/User.fs index 59ce636..e4e3330 100644 --- a/src/myWebLog.Data/User.fs +++ b/src/MyWebLog.Data.RethinkDB/User.fs @@ -1,7 +1,6 @@ -module MyWebLog.Data.User +module MyWebLog.Data.RethinkDB.User open MyWebLog.Entities -open Rethink let private r = RethinkDb.Driver.RethinkDB.R diff --git a/src/myWebLog.Data/WebLog.fs b/src/MyWebLog.Data.RethinkDB/WebLog.fs similarity index 80% rename from src/myWebLog.Data/WebLog.fs rename to src/MyWebLog.Data.RethinkDB/WebLog.fs index d616a8d..95fbfd8 100644 --- a/src/myWebLog.Data/WebLog.fs +++ b/src/MyWebLog.Data.RethinkDB/WebLog.fs @@ -1,20 +1,10 @@ -module MyWebLog.Data.WebLog +module MyWebLog.Data.RethinkDB.WebLog 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 } - /// Detemine the web log by the URL base let tryFindWebLogByUrlBase conn (urlBase : string) = r.Table(Table.WebLog) diff --git a/src/MyWebLog.Data.RethinkDB/paket.references b/src/MyWebLog.Data.RethinkDB/paket.references new file mode 100644 index 0000000..d576c94 --- /dev/null +++ b/src/MyWebLog.Data.RethinkDB/paket.references @@ -0,0 +1,2 @@ +FSharp.Interop.Dynamic +RethinkDb.Driver \ No newline at end of file diff --git a/src/myWebLog.Data/Entities.fs b/src/MyWebLog.Entities/Entities.fs similarity index 95% rename from src/myWebLog.Data/Entities.fs rename to src/MyWebLog.Entities/Entities.fs index 1d10d1b..b35a111 100644 --- a/src/myWebLog.Data/Entities.fs +++ b/src/MyWebLog.Entities/Entities.fs @@ -2,7 +2,7 @@ open Newtonsoft.Json -// ---- Constants ---- +// --- Constants --- /// Constants to use for revision source language [] @@ -38,7 +38,7 @@ module CommentStatus = [] let Spam = "Spam" -// ---- Entities ---- +// --- Entities --- /// A revision of a post or page type Revision = @@ -288,3 +288,14 @@ with Revisions = [] Categories = [] Comments = [] } + +// --- UI Support --- + +/// 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 } diff --git a/src/MyWebLog.Entities/IMyWebLogData.fs b/src/MyWebLog.Entities/IMyWebLogData.fs new file mode 100644 index 0000000..3f4840b --- /dev/null +++ b/src/MyWebLog.Entities/IMyWebLogData.fs @@ -0,0 +1,114 @@ +namespace MyWebLog.Data + +open MyWebLog.Entities + +/// Interface required to provide data to myWebLog's logic layer +type IMyWebLogData = + /// Function to set up the data store + abstract SetUp : (unit -> unit) + + // --- Category --- + + /// Get all categories for a web log + abstract AllCategories : (string -> Category list) + + /// Try to find a category by its Id and web log Id (web log, category Ids) + abstract CategoryById : (string -> string -> Category option) + + /// Try to find a category by its slug (web log Id, slug) + abstract CategoryBySlug : (string -> string -> Category option) + + /// Add a category + abstract AddCategory : (Category -> unit) + + /// Update a category + abstract UpdateCategory : (Category -> unit) + + /// Update a category's children + abstract UpdateChildren : (string -> string -> string list -> unit) + + /// Delete a Category + abstract DeleteCategory : (Category -> unit) + + // --- Page --- + + /// Try to find a page by its Id and web log Id (web log, page Ids), choosing whether to include revisions + abstract PageById : (string -> string -> bool -> Page option) + + /// Try to find a page by its permalink and web log Id (web log Id, permalink) + abstract PageByPermalink : (string -> string -> Page option) + + /// Get all pages for a web log + abstract AllPages : (string -> Page list) + + /// Add a page + abstract AddPage : (Page -> unit) + + /// Update a page + abstract UpdatePage : (Page -> unit) + + /// Delete a page by its Id and web log Id (web log, page Ids) + abstract DeletePage : (string -> string -> unit) + + // --- Post --- + + /// Find a page of published posts for the given web log (web log Id, page #, # per page) + abstract PageOfPublishedPosts : (string -> int -> int -> Post list) + + /// Find a page of published posts within a given category (web log Id, cat Id, page #, # per page) + abstract PageOfCategorizedPosts : (string -> string -> int -> int -> Post list) + + /// Find a page of published posts tagged with a given tag (web log Id, tag, page #, # per page) + abstract PageOfTaggedPosts : (string -> string -> int -> int -> Post list) + + /// Try to find the next newer published post for the given post + abstract NewerPost : (Post -> Post option) + + /// Try to find the next newer published post within a given category + abstract NewerCategorizedPost : (string -> Post -> Post option) + + /// Try to find the next newer published post tagged with a given tag + abstract NewerTaggedPost : (string -> Post -> Post option) + + /// Try to find the next older published post for the given post + abstract OlderPost : (Post -> Post option) + + /// Try to find the next older published post within a given category + abstract OlderCategorizedPost : (string -> Post -> Post option) + + /// Try to find the next older published post tagged with a given tag + abstract OlderTaggedPost : (string -> Post -> Post option) + + /// Find a page of all posts for the given web log (web log Id, page #, # per page) + abstract PageOfAllPosts : (string -> int -> int -> Post list) + + /// Try to find a post by its Id and web log Id (web log, post Ids) + abstract PostById : (string -> string -> Post option) + + /// Try to find a post by its permalink (web log Id, permalink) + abstract PostByPermalink : (string -> string -> Post option) + + /// Try to find a post by a prior permalink (web log Id, permalink) + abstract PostByPriorPermalink : (string -> string -> Post option) + + /// Get posts for the RSS feed for the given web log and number of posts + abstract FeedPosts : (string -> int -> (Post * User option) list) + + /// Add a post + abstract AddPost : (Post -> unit) + + /// Update a post + abstract UpdatePost : (Post -> unit) + + // --- User --- + + /// Attempt to log on a user + abstract LogOn : (string -> string -> User option) + + // --- WebLog --- + + /// Get a web log by its URL base + abstract WebLogByUrlBase : (string -> WebLog option) + + /// Get dashboard counts for a web log + abstract DashboardCounts : (string -> DashboardCounts) diff --git a/src/MyWebLog.Entities/MyWebLog.Entities.fsproj b/src/MyWebLog.Entities/MyWebLog.Entities.fsproj new file mode 100644 index 0000000..e0cf41e --- /dev/null +++ b/src/MyWebLog.Entities/MyWebLog.Entities.fsproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + 2.0 + a87f3cf5-2189-442b-8acf-929f5153ac22 + Library + MyWebLog.Entities + MyWebLog.Entities + v4.5.2 + 4.4.0.0 + MyWebLog.Entities + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\MyWebLog.Entities.xml + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\MyWebLog.Entities.xml + + + + + True + + + + + + + + + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + + + ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + \ No newline at end of file diff --git a/src/MyWebLog.Entities/paket.references b/src/MyWebLog.Entities/paket.references new file mode 100644 index 0000000..1063d00 --- /dev/null +++ b/src/MyWebLog.Entities/paket.references @@ -0,0 +1 @@ +Newtonsoft.Json \ No newline at end of file diff --git a/src/MyWebLog.Logic/Category.fs b/src/MyWebLog.Logic/Category.fs new file mode 100644 index 0000000..e91e771 --- /dev/null +++ b/src/MyWebLog.Logic/Category.fs @@ -0,0 +1,56 @@ +module MyWebLog.Logic.Category + +open MyWebLog.Data +open MyWebLog.Entities + +/// 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 + yield! getChildren child (indent + 1) + } + categories + |> List.filter (fun c -> c.ParentId.IsNone) + |> List.map (fun c -> getChildren c 0) + |> Seq.collect id + |> Seq.toList + +/// Find all categories for a given web log +let findAllCategories (data : IMyWebLogData) webLogId = + data.AllCategories webLogId + |> sortCategories + +/// Try to find a category for a given web log Id and category Id +let tryFindCategory (data : IMyWebLogData) webLogId catId = data.CategoryById webLogId catId + +/// Try to find a category by its slug for a given web log +let tryFindCategoryBySlug (data : IMyWebLogData) webLogId slug = data.CategoryBySlug webLogId slug + +/// Save a category +let saveCategory (data : IMyWebLogData) (cat : Category) = + match cat.Id with + | "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid() } + data.AddCategory newCat + newCat.Id + | _ -> data.UpdateCategory cat + cat.Id + +/// Remove a category from its parent +let removeCategoryFromParent (data : IMyWebLogData) webLogId parentId catId = + match tryFindCategory data webLogId parentId with + | Some parent -> parent.Children + |> List.filter (fun childId -> childId <> catId) + |> data.UpdateChildren webLogId parentId + | None -> () + +/// Add a category to a given parent +let addCategoryToParent (data : IMyWebLogData) webLogId parentId catId = + match tryFindCategory data webLogId parentId with + | Some parent -> catId :: parent.Children + |> data.UpdateChildren webLogId parentId + | None -> () + +/// Delete a category +let deleteCategory (data : IMyWebLogData) cat = data.DeleteCategory cat diff --git a/src/myWebLog.Data/myWebLog.Data.fsproj b/src/MyWebLog.Logic/MyWebLog.Logic.fsproj similarity index 57% rename from src/myWebLog.Data/myWebLog.Data.fsproj rename to src/MyWebLog.Logic/MyWebLog.Logic.fsproj index 9182b36..5d71dae 100644 --- a/src/myWebLog.Data/myWebLog.Data.fsproj +++ b/src/MyWebLog.Logic/MyWebLog.Logic.fsproj @@ -1,18 +1,17 @@ - - + + Debug AnyCPU 2.0 - 1fba0b84-b09e-4b16-b9b6-5730dea27192 + 29f6eda3-4f43-4bb3-9c63-ae238a9b7f12 Library - myWebLog.Data - MyWebLog.Data + MyWebLog.Logic + MyWebLog.Logic v4.5.2 4.4.0.0 - true - myWebLog.Data + MyWebLog.Logic true @@ -22,7 +21,7 @@ bin\Debug\ DEBUG;TRACE 3 - bin\Debug\MyWebLog.Data.xml + bin\Debug\MyWebLog.Logic.xml pdbonly @@ -31,8 +30,31 @@ bin\Release\ TRACE 3 - bin\Release\myWebLog.Data.XML + bin\Release\MyWebLog.Logic.xml + + + + True + + + + + + + + + + + + + + + MyWebLog.Entities + {a87f3cf5-2189-442b-8acf-929f5153ac22} + True + + 11 @@ -48,61 +70,12 @@ - - - - - - - - - - - - - - - - - - - ..\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\Dynamitey.1.0.2.0\lib\net40\Dynamitey.dll - True - - - ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll - True - - - ..\packages\FSharp.Interop.Dynamic.3.0.0.0\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll - True - - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll - True - - - - - - - \ No newline at end of file + diff --git a/src/MyWebLog.Logic/Page.fs b/src/MyWebLog.Logic/Page.fs new file mode 100644 index 0000000..5c6bb8c --- /dev/null +++ b/src/MyWebLog.Logic/Page.fs @@ -0,0 +1,29 @@ +/// Logic for manipulating entities +module MyWebLog.Logic.Page + +open MyWebLog.Data +open MyWebLog.Entities + +/// Find a page by its Id and web log Id +let tryFindPage (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId true + +/// Find a page by its Id and web log Id, without the revision list +let tryFindPageWithoutRevisions (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId false + +/// Find a page by its permalink +let tryFindPageByPermalink (data : IMyWebLogData) webLogId permalink = data.PageByPermalink webLogId permalink + +/// Find a list of all pages (excludes text and revisions) +let findAllPages (data : IMyWebLogData) webLogId = data.AllPages webLogId + +/// Save a page +let savePage (data : IMyWebLogData) (page : Page) = + match page.Id with + | "new" -> let newPg = { page with Id = string <| System.Guid.NewGuid() } + data.AddPage newPg + newPg.Id + | _ -> data.UpdatePage page + page.Id + +/// Delete a page +let deletePage (data : IMyWebLogData) webLogId pageId = data.DeletePage webLogId pageId diff --git a/src/MyWebLog.Logic/Post.fs b/src/MyWebLog.Logic/Post.fs new file mode 100644 index 0000000..410b819 --- /dev/null +++ b/src/MyWebLog.Logic/Post.fs @@ -0,0 +1,60 @@ +/// Logic for manipulating entities +module MyWebLog.Logic.Post + +open MyWebLog.Data +open MyWebLog.Entities + +/// Find a page of published posts +let findPageOfPublishedPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage = + data.PageOfPublishedPosts webLogId pageNbr nbrPerPage + +/// Find a pages of published posts in a given category +let findPageOfCategorizedPosts (data : IMyWebLogData) webLogId catId pageNbr nbrPerPage = + data.PageOfCategorizedPosts webLogId catId pageNbr nbrPerPage + +/// Find a page of published posts tagged with a given tag +let findPageOfTaggedPosts (data : IMyWebLogData) webLogId tag pageNbr nbrPerPage = + data.PageOfTaggedPosts webLogId tag pageNbr nbrPerPage + +/// Find the next newer published post for the given post +let tryFindNewerPost (data : IMyWebLogData) post = data.NewerPost post + +/// Find the next newer published post in a given category for the given post +let tryFindNewerCategorizedPost (data : IMyWebLogData) catId post = data.NewerCategorizedPost catId post + +/// Find the next newer published post tagged with a given tag for the given post +let tryFindNewerTaggedPost (data : IMyWebLogData) tag post = data.NewerTaggedPost tag post + +/// Find the next older published post for the given post +let tryFindOlderPost (data : IMyWebLogData) post = data.OlderPost post + +/// Find the next older published post in a given category for the given post +let tryFindOlderCategorizedPost (data : IMyWebLogData) catId post = data.OlderCategorizedPost catId post + +/// Find the next older published post tagged with a given tag for the given post +let tryFindOlderTaggedPost (data : IMyWebLogData) tag post = data.OlderTaggedPost tag post + +/// Find a page of all posts for a web log +let findPageOfAllPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage = + data.PageOfAllPosts webLogId pageNbr nbrPerPage + +/// Try to find a post by its Id +let tryFindPost (data : IMyWebLogData) webLogId postId = data.PostById webLogId postId + +/// Try to find a post by its permalink +let tryFindPostByPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPermalink webLogId permalink + +/// Try to find a post by its prior permalink +let tryFindPostByPriorPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPriorPermalink webLogId permalink + +/// Find posts for the RSS feed +let findFeedPosts (data : IMyWebLogData) webLogId nbrOfPosts = data.FeedPosts webLogId nbrOfPosts + +/// Save a post +let savePost (data : IMyWebLogData) post = + match post.Id with + | "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() } + data.AddPost newPost + newPost.Id + | _ -> data.UpdatePost post + post.Id diff --git a/src/MyWebLog.Logic/User.fs b/src/MyWebLog.Logic/User.fs new file mode 100644 index 0000000..4ffa4d4 --- /dev/null +++ b/src/MyWebLog.Logic/User.fs @@ -0,0 +1,7 @@ +/// Logic for manipulating entities +module MyWebLog.Logic.User + +open MyWebLog.Data + +/// Try to log on a user +let tryUserLogOn (data : IMyWebLogData) email passwordHash = data.LogOn email passwordHash diff --git a/src/MyWebLog.Logic/WebLog.fs b/src/MyWebLog.Logic/WebLog.fs new file mode 100644 index 0000000..a1dfc70 --- /dev/null +++ b/src/MyWebLog.Logic/WebLog.fs @@ -0,0 +1,11 @@ +/// Logic for manipulating entities +module MyWebLog.Logic.WebLog + +open MyWebLog.Data +open MyWebLog.Entities + +/// Find a web log by its URL base +let tryFindWebLogByUrlBase (data : IMyWebLogData) urlBase = data.WebLogByUrlBase urlBase + +/// Find the counts for the admin dashboard +let findDashboardCounts (data : IMyWebLogData) webLogId = data.DashboardCounts webLogId diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fs b/src/MyWebLog.Tests/MyWebLog.Tests.fs new file mode 100644 index 0000000..9210d97 --- /dev/null +++ b/src/MyWebLog.Tests/MyWebLog.Tests.fs @@ -0,0 +1,4 @@ +namespace MyWebLog.Web + +type Web() = + member this.X = "F#" diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj new file mode 100644 index 0000000..74198ff --- /dev/null +++ b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + 2.0 + 07e60874-6cf5-4d53-aee0-f17ef28228dd + Library + MyWebLog.Tests + MyWebLog.Tests + v4.5.2 + 4.4.0.0 + MyWebLog.Tests + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\MyWebLog.Tests.xml + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\MyWebLog.Tests.xml + + + + + True + + + + + + + + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + diff --git a/src/build.cmd b/src/build.cmd new file mode 100644 index 0000000..40aff45 --- /dev/null +++ b/src/build.cmd @@ -0,0 +1,14 @@ +@echo off +cls + +.paket\paket.bootstrapper.exe +if errorlevel 1 ( + exit /b %errorlevel% +) + +.paket\paket.exe restore +if errorlevel 1 ( + exit /b %errorlevel% +) + +packages\FAKE\tools\FAKE.exe build.fsx %* diff --git a/src/build.fsx b/src/build.fsx new file mode 100644 index 0000000..a13fe19 --- /dev/null +++ b/src/build.fsx @@ -0,0 +1,42 @@ +// include Fake libs +#r "./packages/FAKE/tools/FakeLib.dll" + +open Fake + +// Directories +let buildDir = "./build/" +let deployDir = "./deploy/" + + +// Filesets +let appReferences = + !! "/**/*.csproj" + ++ "/**/*.fsproj" + +// version info +let version = "0.1" // or retrieve from CI server + +// Targets +Target "Clean" (fun _ -> + CleanDirs [buildDir; deployDir] +) + +Target "Build" (fun _ -> + // compile all projects below src/app/ + MSBuildDebug buildDir "Build" appReferences + |> Log "AppBuild-Output: " +) + +Target "Deploy" (fun _ -> + !! (buildDir + "/**/*.*") + -- "*.zip" + |> Zip buildDir (deployDir + "ApplicationName." + version + ".zip") +) + +// Build order +"Clean" + ==> "Build" + ==> "Deploy" + +// start build +RunTargetOrDefault "Build" diff --git a/src/build.sh b/src/build.sh new file mode 100644 index 0000000..c40c3f8 --- /dev/null +++ b/src/build.sh @@ -0,0 +1,33 @@ +#!/bin/bash +if test "$OS" = "Windows_NT" +then + # use .Net + + .paket/paket.bootstrapper.exe + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + .paket/paket.exe restore + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + packages/FAKE/tools/FAKE.exe $@ --fsiargs build.fsx +else + # use mono + mono .paket/paket.bootstrapper.exe + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + + mono .paket/paket.exe restore + exit_code=$? + if [ $exit_code -ne 0 ]; then + exit $exit_code + fi + mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx +fi diff --git a/src/myWebLog.Data/Page.fs b/src/myWebLog.Data/Page.fs deleted file mode 100644 index c1cec81..0000000 --- a/src/myWebLog.Data/Page.fs +++ /dev/null @@ -1,77 +0,0 @@ -module MyWebLog.Data.Page - -open FSharp.Interop.Dynamic -open MyWebLog.Entities -open Rethink -open RethinkDb.Driver.Ast -open System.Dynamic - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Shorthand to get the page by its Id, filtering on web log Id -let private page (webLogId : string) (pageId : string) = - r.Table(Table.Page) - .Get(pageId) - .Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId))) - -/// Get a page by its Id -let tryFindPage conn webLogId pageId = - match r.Table(Table.Page) - .Get(pageId) - .RunAtomAsync(conn) |> await |> box with - | null -> None - | page -> let pg : Page = unbox page - 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") - .RunAtomAsync(conn) |> await |> box with - | null -> None - | page -> Some <| unbox page - -/// 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") - .RunCursorAsync(conn) - |> await - |> Seq.tryHead - -/// 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") - .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() } - r.Table(Table.Page) - .Insert(page) - .RunResultAsync(conn) |> await |> ignore - 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) - .Update(upd8) - .RunResultAsync(conn) |> await |> ignore - pg.Id - -/// Delete a page -let deletePage conn webLogId pageId = - (page webLogId pageId) - .Delete() - .RunResultAsync(conn) |> await |> ignore diff --git a/src/myWebLog.Data/packages.config b/src/myWebLog.Data/packages.config deleted file mode 100644 index 16d155d..0000000 --- a/src/myWebLog.Data/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/myWebLog.Web/myWebLog.Web.fsproj b/src/myWebLog.Web/myWebLog.Web.fsproj deleted file mode 100644 index 66bdbba..0000000 --- a/src/myWebLog.Web/myWebLog.Web.fsproj +++ /dev/null @@ -1,186 +0,0 @@ - - - - - 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.Formatting.2.14.4\lib\net40\CSharpFormat.dll - True - - - ..\packages\Dynamitey.1.0.2.0\lib\net40\Dynamitey.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.CodeFormat.dll - True - - - ..\packages\FSharp.Compiler.Service.2.0.0.6\lib\net45\FSharp.Compiler.Service.dll - True - - - ..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Formatting.Common.dll - True - - - ..\packages\FSharp.Interop.Dynamic.3.0.0.0\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Literate.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Markdown.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.MetadataFormat.dll - True - - - ..\packages\FSharpVSPowerTools.Core.2.3.0\lib\net45\FSharpVSPowerTools.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.9.0\lib\net452\Nancy.Session.Persistable.dll - True - - - ..\packages\Nancy.Session.RethinkDB.0.9.0\lib\net452\Nancy.Session.RethinkDb.dll - True - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NodaTime.1.3.2\lib\net35-Client\NodaTime.dll - True - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\RazorEngine.dll - True - - - ..\packages\RethinkDb.Driver.2.3.9\lib\net45\RethinkDb.Driver.dll - True - - - ..\packages\Suave.1.1.3\lib\net40\Suave.dll - True - - - - - - - - ..\packages\FSharp.Formatting.2.14.4\lib\net40\System.Web.Razor.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 deleted file mode 100644 index cd8ef6e..0000000 --- a/src/myWebLog.Web/packages.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/myWebLog.sln b/src/myWebLog.sln index efd878b..09b6777 100644 --- a/src/myWebLog.sln +++ b/src/myWebLog.sln @@ -1,39 +1,14 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 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}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{DF15419B-90C6-4F45-8EC1-7A63C5D3565C}" + ProjectSection(SolutionItems) = preProject + paket.dependencies = paket.dependencies + EndProjectSection 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 diff --git a/src/myWebLog/App.config b/src/myWebLog/App.config index 16f1295..0ee6bce 100644 --- a/src/myWebLog/App.config +++ b/src/myWebLog/App.config @@ -15,7 +15,20 @@ - + + + + + + + + +