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?)
This commit is contained in:
Daniel J. Summers
2016-08-06 13:55:49 -05:00
parent 8194649018
commit 710004dfc4
53 changed files with 1418 additions and 572 deletions

View File

@@ -0,0 +1,21 @@
namespace myWebLog.Data.AssemblyInfo
open System.Reflection
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
[<assembly: AssemblyTitle("MyWebLog.Data.RethinkDB")>]
[<assembly: AssemblyDescription("RethinkDB data access for myWebLog")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("DJS Consulting")>]
[<assembly: AssemblyProduct("MyWebLog.Data.RethinkDB")>]
[<assembly: AssemblyCopyright("Copyright © 2016")>]
[<assembly: AssemblyTrademark("")>]
[<assembly: AssemblyCulture("")>]
[<assembly: ComVisible(false)>]
[<assembly: Guid("1fba0b84-b09e-4b16-b9b6-5730dea27192")>]
[<assembly: AssemblyVersion("0.9.2.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>]
do
()

View File

@@ -0,0 +1,100 @@
module MyWebLog.Data.RethinkDB.Category
open FSharp.Interop.Dynamic
open MyWebLog.Entities
open RethinkDb.Driver.Ast
open System.Dynamic
let private r = RethinkDb.Driver.RethinkDB.R
/// Shorthand to get a category by Id and filter by web log Id
let private category (webLogId : string) (catId : string) =
r.Table(Table.Category)
.Get(catId)
.Filter(fun c -> c.["WebLogId"].Eq(webLogId))
/// Get all categories for a web log
let getAllCategories conn (webLogId : string) =
r.Table(Table.Category)
.GetAll(webLogId).OptArg("index", "WebLogId")
.OrderBy("Name")
.RunListAsync<Category>(conn)
|> await
|> Seq.toList
/// Get a specific category by its Id
let tryFindCategory conn webLogId catId : Category option =
match (category webLogId catId)
.RunAtomAsync<Category>(conn) |> await |> box with
| null -> None
| cat -> Some <| unbox cat
/// Add a category
let addCategory conn (cat : Category) =
r.Table(Table.Category)
.Insert(cat)
.RunResultAsync(conn) |> await |> ignore
/// 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
/// 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 -> 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
cat.Children
|> List.iter (fun childId -> (category cat.WebLogId childId)
.Update(newParent)
.RunResultAsync(conn) |> await |> ignore)
// Remove the category from posts where it is assigned
r.Table(Table.Post)
.GetAll(cat.WebLogId).OptArg("index", "WebLogId")
.Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(cat.Id)))
.RunCursorAsync<Post>(conn)
|> await
|> Seq.toList
|> List.iter (fun post -> let newCats = ExpandoObject()
newCats?CategoryIds <- post.CategoryIds
|> List.filter (fun c -> c <> cat.Id)
r.Table(Table.Post)
.Get(post.Id)
.Update(newCats)
.RunResultAsync(conn) |> await |> ignore)
// Now, delete the category
r.Table(Table.Category)
.Get(cat.Id)
.Delete()
.RunResultAsync(conn) |> await |> ignore
/// Get a category by its slug
let tryFindCategoryBySlug conn (webLogId : string) (slug : string) =
r.Table(Table.Category)
.GetAll(r.Array(webLogId, slug)).OptArg("index", "Slug")
.RunCursorAsync<Category>(conn)
|> await
|> Seq.tryHead

View File

@@ -0,0 +1,58 @@
namespace MyWebLog.Data.RethinkDB
open RethinkDb.Driver
open RethinkDb.Driver.Net
open Newtonsoft.Json
/// Data configuration
type DataConfig =
{ /// The hostname for the RethinkDB server
[<JsonProperty("hostname")>]
Hostname : string
/// The port for the RethinkDB server
[<JsonProperty("port")>]
Port : int
/// The authorization key to use when connecting to the server
[<JsonProperty("authKey")>]
AuthKey : string
/// How long an attempt to connect to the server should wait before giving up
[<JsonProperty("timeout")>]
Timeout : int
/// The name of the default database to use on the connection
[<JsonProperty("database")>]
Database : string
/// A connection to the RethinkDB server using the configuration in this object
[<JsonIgnore>]
Conn : IConnection }
with
/// Use RethinkDB defaults for non-provided options, and connect to the server
static member Connect config =
let ensureHostname cfg = match cfg.Hostname with
| null -> { cfg with Hostname = RethinkDBConstants.DefaultHostname }
| _ -> cfg
let ensurePort cfg = match cfg.Port with
| 0 -> { cfg with Port = RethinkDBConstants.DefaultPort }
| _ -> cfg
let ensureAuthKey cfg = match cfg.AuthKey with
| null -> { cfg with AuthKey = RethinkDBConstants.DefaultAuthkey }
| _ -> cfg
let ensureTimeout cfg = match cfg.Timeout with
| 0 -> { cfg with Timeout = RethinkDBConstants.DefaultTimeout }
| _ -> cfg
let ensureDatabase cfg = match cfg.Database with
| null -> { cfg with Database = RethinkDBConstants.DefaultDbName }
| _ -> cfg
let connect cfg = { cfg with Conn = RethinkDB.R.Connection()
.Hostname(cfg.Hostname)
.Port(cfg.Port)
.AuthKey(cfg.AuthKey)
.Db(cfg.Database)
.Timeout(cfg.Timeout)
.Connect() }
config
|> ensureHostname
|> ensurePort
|> ensureAuthKey
|> ensureTimeout
|> ensureDatabase
|> connect

View File

@@ -0,0 +1,11 @@
[<AutoOpen>]
module MyWebLog.Data.RethinkDB.Extensions
open RethinkDb.Driver.Ast
open RethinkDb.Driver.Net
let await task = task |> Async.AwaitTask |> Async.RunSynchronously
type ReqlExpr with
/// Run a SUCCESS_ATOM response that returns multiple values
member this.RunListAsync<'T> (conn : IConnection) = this.RunAtomAsync<System.Collections.Generic.List<'T>> conn

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>d6c2be5e-883a-4f34-9905-b730543ca380</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>MyWebLog.Data.RethinkDB</RootNamespace>
<AssemblyName>MyWebLog.Data.RethinkDB</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<Name>MyWebLog.Data.RethinkDB</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\MyWebLog.Data.RethinkDB.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\MyWebLog.Data.RethinkDB.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions.fs" />
<Compile Include="Table.fs" />
<Compile Include="DataConfig.fs" />
<Compile Include="Category.fs" />
<Compile Include="Page.fs" />
<Compile Include="Post.fs" />
<Compile Include="User.fs" />
<Compile Include="WebLog.fs" />
<Compile Include="SetUp.fs" />
<Compile Include="RethinkMyWebLogData.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyWebLog.Entities\MyWebLog.Entities.fsproj">
<Name>MyWebLog.Entities</Name>
<Project>{a87f3cf5-2189-442b-8acf-929f5153ac22}</Project>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging">
<HintPath>..\packages\Common.Logging\lib\net40\Common.Logging.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Common.Logging.Core">
<HintPath>..\packages\Common.Logging.Core\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.0' Or $(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Dynamitey">
<HintPath>..\packages\Dynamitey\lib\net40\Dynamitey.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="FSharp.Interop.Dynamic">
<HintPath>..\packages\FSharp.Interop.Dynamic\lib\portable-net45+sl50+win\FSharp.Interop.Dynamic.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And $(TargetFrameworkVersion) == 'v4.0'">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="$(TargetFrameworkIdentifier) == '.NETFramework' And ($(TargetFrameworkVersion) == 'v4.5' Or $(TargetFrameworkVersion) == 'v4.5.2')">
<ItemGroup>
<Reference Include="RethinkDb.Driver">
<HintPath>..\packages\RethinkDb.Driver\lib\net45\RethinkDb.Driver.dll</HintPath>
<Private>True</Private>
<Paket>True</Paket>
</Reference>
</ItemGroup>
</When>
</Choose>
</Project>

View File

@@ -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<Page>(conn)
| _ -> pg.Without("Revisions").RunAtomAsync<Page>(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<Page>(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<Page>(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
| _ -> ()

View File

@@ -0,0 +1,179 @@
module MyWebLog.Data.RethinkDB.Post
open FSharp.Interop.Dynamic
open MyWebLog.Entities
open RethinkDb.Driver.Ast
open System.Dynamic
let private r = RethinkDb.Driver.RethinkDB.R
/// Shorthand to select all published posts for a web log
let private publishedPosts (webLogId : string)=
r.Table(Table.Post)
.GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus")
/// Shorthand to sort posts by published date, slice for the given page, and return a list
let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) =
filter
.OrderBy(r.Desc("PublishedOn"))
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
.RunListAsync<Post>(conn)
|> await
|> Seq.toList
/// Shorthand to get a newer or older post
let private adjacentPost conn post (theFilter : ReqlExpr -> obj) (sort : obj) =
(publishedPosts post.WebLogId)
.Filter(theFilter)
.OrderBy(sort)
.Limit(1)
.RunListAsync<Post>(conn)
|> await
|> Seq.tryHead
/// Find a newer post
let private newerPost conn post theFilter = adjacentPost conn post theFilter <| r.Asc "publishedOn"
/// Find an older post
let private olderPost conn post theFilter = adjacentPost conn post theFilter <| r.Desc "publishedOn"
/// Get a page of published posts
let findPageOfPublishedPosts conn webLogId pageNbr nbrPerPage =
publishedPosts webLogId
|> toPostList conn pageNbr nbrPerPage
/// Get a page of published posts assigned to a given category
let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage =
(publishedPosts webLogId)
.Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(categoryId)))
|> toPostList conn pageNbr nbrPerPage
/// Get a page of published posts tagged with a given tag
let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage =
(publishedPosts webLogId)
.Filter(ReqlFunction1(fun p -> upcast p.["Tags"].Contains(tag)))
|> toPostList conn pageNbr nbrPerPage
/// Try to get the next newest post from the given post
let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn))
/// Try to get the next newest post assigned to the given category
let tryFindNewerCategorizedPost conn (categoryId : string) post =
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn)
.And(p.["CategoryIds"].Contains(categoryId)))
/// Try to get the next newest tagged post from the given tagged post
let tryFindNewerTaggedPost conn (tag : string) post =
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn).And(p.["Tags"].Contains(tag)))
/// Try to get the next oldest post from the given post
let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn))
/// Try to get the next oldest post assigned to the given category
let tryFindOlderCategorizedPost conn (categoryId : string) post =
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn)
.And(p.["CategoryIds"].Contains(categoryId)))
/// Try to get the next oldest tagged post from the given tagged post
let tryFindOlderTaggedPost conn (tag : string) post =
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn).And(p.["Tags"].Contains(tag)))
/// Get a page of all posts in all statuses
let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage =
// FIXME: sort unpublished posts by their last updated date
r.Table(Table.Post)
.GetAll(webLogId).OptArg("index", "WebLogId")
.OrderBy(r.Desc("PublishedOn"))
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
.RunListAsync<Post>(conn)
|> await
|> Seq.toList
/// Try to find a post by its Id and web log Id
let tryFindPost conn webLogId postId : Post option =
match r.Table(Table.Post)
.Get(postId)
.Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId)))
.RunAtomAsync<Post>(conn)
|> box with
| null -> None
| post -> Some <| unbox post
/// Try to find a post by its permalink
let tryFindPostByPermalink conn webLogId permalink =
r.Table(Table.Post)
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink")
.Filter(fun p -> p.["Status"].Eq(PostStatus.Published))
.Without("Revisions")
.Merge(fun p -> r.HashMap("Categories", r.Table(Table.Category)
.GetAll(p.["CategoryIds"])
.Without("Children")
.OrderBy("Name")
.CoerceTo("array")))
.Merge(fun p -> r.HashMap("Comments", r.Table(Table.Comment)
.GetAll(p.["Id"]).OptArg("index", "PostId")
.OrderBy("PostedOn")
.CoerceTo("array")))
.RunCursorAsync<Post>(conn)
|> await
|> Seq.tryHead
/// Try to find a post by its prior permalink
let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Post)
.GetAll(webLogId).OptArg("index", "WebLogId")
.Filter(fun p -> p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq(PostStatus.Published)))
.Without("Revisions")
.RunCursorAsync<Post>(conn)
|> await
|> Seq.tryHead
/// Get a set of posts for RSS
let findFeedPosts conn webLogId nbr : (Post * User option) list =
(publishedPosts webLogId)
.Merge(fun post -> r.HashMap("Categories", r.Table(Table.Category)
.GetAll(post.["CategoryIds"])
.OrderBy("Name")
.Pluck("Id", "Name")
.CoerceTo("array")))
|> toPostList conn 1 nbr
|> List.map (fun post -> post, match r.Table(Table.User)
.Get(post.AuthorId)
.RunAtomAsync<User>(conn)
|> await
|> box with
| 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
| "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() }
r.Table(Table.Post)
.Insert(newPost)
.RunResultAsync(conn)
|> ignore
newPost.Id
| _ -> r.Table(Table.Post)
.Get(post.Id)
.Replace( { post with Categories = []
Comments = [] } )
.RunResultAsync(conn)
|> ignore
post.Id

View File

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

View File

@@ -0,0 +1,89 @@
module MyWebLog.Data.RethinkDB.SetUp
open RethinkDb.Driver.Ast
open System
let private r = RethinkDb.Driver.RethinkDB.R
let private logStep step = Console.Out.WriteLine (sprintf "[myWebLog] %s" step)
let private logStepStart text = Console.Out.Write (sprintf "[myWebLog] %s..." text)
let private logStepDone () = Console.Out.WriteLine (" done.")
/// Ensure the myWebLog database exists
let private checkDatabase (cfg : DataConfig) =
logStep "|> Checking database"
let dbs = r.DbList().RunListAsync<string>(cfg.Conn) |> await
match dbs.Contains cfg.Database with
| true -> ()
| _ -> logStepStart (sprintf " %s database not found - creating" cfg.Database)
r.DbCreate(cfg.Database).RunResultAsync(cfg.Conn) |> await |> ignore
logStepDone ()
/// Ensure all required tables exist
let private checkTables cfg =
logStep "|> Checking tables"
let tables = r.Db(cfg.Database).TableList().RunListAsync<string>(cfg.Conn) |> await
[ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ]
|> List.map (fun tbl -> match tables.Contains tbl with true -> None | _ -> Some (tbl, r.TableCreate tbl))
|> List.filter Option.isSome
|> List.map Option.get
|> List.iter (fun (tbl, create) -> logStepStart (sprintf " Creating table %s" tbl)
create.RunResultAsync(cfg.Conn) |> await |> ignore
logStepDone ())
/// Shorthand to get the table
let private tbl cfg table = r.Db(cfg.Database).Table(table)
/// Create the given index
let private createIndex cfg table (index : string * (ReqlExpr -> obj) option) =
let idxName, idxFunc = index
logStepStart (sprintf """ Creating index "%s" on table %s""" idxName table)
(match idxFunc with
| Some f -> (tbl cfg table).IndexCreate(idxName, f)
| None -> (tbl cfg table).IndexCreate(idxName))
.RunResultAsync(cfg.Conn)
|> await |> ignore
(tbl cfg table).IndexWait(idxName).RunAtomAsync(cfg.Conn) |> await |> ignore
logStepDone ()
/// Ensure that the given indexes exist, and create them if required
let private ensureIndexes cfg (indexes : (string * (string * (ReqlExpr -> obj) option) list) list) =
let ensureForTable (tblName, idxs) =
let idx = (tbl cfg tblName).IndexList().RunListAsync<string>(cfg.Conn) |> await
idxs
|> List.iter (fun index -> match idx.Contains (fst index) with true -> () | _ -> createIndex cfg tblName index)
indexes
|> List.iter ensureForTable
/// Create an index on web log Id and the given field
let private webLogField (name : string) : (ReqlExpr -> obj) option =
Some <| fun row -> upcast r.Array(row.["WebLogId"], row.[name])
/// Ensure all the required indexes exist
let private checkIndexes cfg =
logStep "|> Checking indexes"
[ Table.Category, [ "WebLogId", None
"Slug", webLogField "Slug"
]
Table.Comment, [ "PostId", None
]
Table.Page, [ "WebLogId", None
"Permalink", webLogField "Permalink"
]
Table.Post, [ "WebLogId", None
"WebLogAndStatus", webLogField "Status"
"Permalink", webLogField "Permalink"
]
Table.User, [ "UserName", None
]
Table.WebLog, [ "UrlBase", None
]
]
|> ensureIndexes cfg
/// Start up checks to ensure the database, tables, and indexes exist
let startUpCheck cfg =
logStep "Database Start Up Checks Starting"
checkDatabase cfg
checkTables cfg
checkIndexes cfg
logStep "Database Start Up Checks Complete"

View File

@@ -0,0 +1,21 @@
/// Constants for tables used in myWebLog
[<RequireQualifiedAccess>]
module MyWebLog.Data.RethinkDB.Table
/// The Category table
let Category = "Category"
/// The Comment table
let Comment = "Comment"
/// The Page table
let Page = "Page"
/// The Post table
let Post = "Post"
/// The WebLog table
let WebLog = "WebLog"
/// The User table
let User = "User"

View File

@@ -0,0 +1,17 @@
module MyWebLog.Data.RethinkDB.User
open MyWebLog.Entities
let private r = RethinkDb.Driver.RethinkDB.R
/// Log on a user
// NOTE: The significant length of a RethinkDB index is 238 - [PK size]; as we're storing 1,024 characters of password,
// including it in an index does not get any performance gain, and would unnecessarily bloat the index. See
// http://rethinkdb.com/docs/secondary-indexes/java/ for more information.
let tryUserLogOn conn (email : string) (passwordHash : string) =
r.Table(Table.User)
.GetAll(email).OptArg("index", "UserName")
.Filter(fun u -> u.["PasswordHash"].Eq(passwordHash))
.RunCursorAsync<User>(conn)
|> await
|> Seq.tryHead

View File

@@ -0,0 +1,29 @@
module MyWebLog.Data.RethinkDB.WebLog
open MyWebLog.Entities
open RethinkDb.Driver.Ast
let private r = RethinkDb.Driver.RethinkDB.R
/// Detemine the web log by the URL base
let tryFindWebLogByUrlBase conn (urlBase : string) =
r.Table(Table.WebLog)
.GetAll(urlBase).OptArg("index", "UrlBase")
.Merge(fun w -> r.HashMap("PageList", r.Table(Table.Page)
.GetAll(w.["Id"]).OptArg("index", "WebLogId")
.Filter(ReqlFunction1(fun pg -> upcast pg.["ShowInPageList"].Eq(true)))
.OrderBy("Title")
.Pluck("Title", "Permalink")
.CoerceTo("array")))
.RunCursorAsync<WebLog>(conn)
|> await
|> Seq.tryHead
/// Get counts for the admin dashboard
let findDashboardCounts conn (webLogId : string) =
r.Expr( r.HashMap("Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
.Merge(r.HashMap("Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
.Merge(r.HashMap("Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
.RunAtomAsync<DashboardCounts>(conn)
|> await

View File

@@ -0,0 +1,2 @@
FSharp.Interop.Dynamic
RethinkDb.Driver