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 0000000..64fdf24 Binary files /dev/null and b/src/.paket/paket.bootstrapper.exe differ 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 @@ - + + + + + + + + +