299 lines
9.2 KiB
Forth

[<RequireQualifiedAccess>]
module MyWebLog.Data
/// Table names
[<RequireQualifiedAccess>]
module private 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 web log table
let WebLog = "WebLog"
/// The web log user table
let WebLogUser = "WebLogUser"
/// A list of all tables
let all = [ Category; Comment; Page; Post; WebLog; WebLogUser ]
/// Functions to assist with retrieving data
[<AutoOpen>]
module Helpers =
open RethinkDb.Driver
open RethinkDb.Driver.Net
open System.Threading.Tasks
/// Shorthand for the ReQL starting point
let r = RethinkDB.R
/// Verify that the web log ID matches before returning an item
let verifyWebLog<'T> webLogId (prop : 'T -> WebLogId) (f : IConnection -> Task<'T option>) =
fun conn -> task {
match! f conn with Some it when (prop it) = webLogId -> return Some it | _ -> return None
}
open RethinkDb.Driver.FSharp
open Microsoft.Extensions.Logging
module Startup =
/// Ensure field indexes exist, as well as special indexes for selected tables
let private ensureIndexes (log : ILogger) conn table fields = task {
let! indexes = rethink<string list> { withTable table; indexList; result; withRetryOnce conn }
for field in fields do
match indexes |> List.contains field with
| true -> ()
| false ->
log.LogInformation($"Creating index {table}.{field}...")
let! _ = rethink { withTable table; indexCreate field; write; withRetryOnce conn }
()
// Post and page need index by web log ID and permalink
match [ Table.Page; Table.Post ] |> List.contains table with
| true ->
match indexes |> List.contains "permalink" with
| true -> ()
| false ->
log.LogInformation($"Creating index {table}.permalink...")
let! _ =
rethink {
withTable table
indexCreate "permalink" (fun row -> r.Array(row.G "webLogId", row.G "permalink"))
write
withRetryOnce conn
}
()
| false -> ()
// Users log on with e-mail
match Table.WebLogUser = table with
| true ->
match indexes |> List.contains "logOn" with
| true -> ()
| false ->
log.LogInformation($"Creating index {table}.logOn...")
let! _ =
rethink {
withTable table
indexCreate "logOn" (fun row -> r.Array(row.G "webLogId", row.G "email"))
write
withRetryOnce conn
}
()
| false -> ()
}
/// Ensure all necessary tables and indexes exist
let ensureDb (config : DataConfig) (log : ILogger) conn = task {
let! dbs = rethink<string list> { dbList; result; withRetryOnce conn }
match dbs |> List.contains config.Database with
| true -> ()
| false ->
log.LogInformation($"Creating database {config.Database}...")
let! _ = rethink { dbCreate config.Database; write; withRetryOnce conn }
()
let! tables = rethink<string list> { tableList; result; withRetryOnce conn }
for tbl in Table.all do
match tables |> List.contains tbl with
| true -> ()
| false ->
log.LogInformation($"Creating table {tbl}...")
let! _ = rethink { tableCreate tbl; write; withRetryOnce conn }
()
let makeIdx = ensureIndexes log conn
do! makeIdx Table.Category [ "webLogId" ]
do! makeIdx Table.Comment [ "postId" ]
do! makeIdx Table.Page [ "webLogId"; "authorId" ]
do! makeIdx Table.Post [ "webLogId"; "authorId" ]
do! makeIdx Table.WebLog [ "urlBase" ]
do! makeIdx Table.WebLogUser [ "webLogId" ]
}
/// Functions to manipulate categories
module Category =
/// Count all categories for a web log
let countAll (webLogId : WebLogId) =
rethink<int> {
withTable Table.Category
getAll [ webLogId ] (nameof webLogId)
count
result
withRetryDefault
}
/// Count top-level categories for a web log
let countTopLevel (webLogId : WebLogId) =
rethink<int> {
withTable Table.Category
getAll [ webLogId ] (nameof webLogId)
filter "parentId" None
count
result
withRetryDefault
}
/// Functions to manipulate pages
module Page =
/// Count all pages for a web log
let countAll (webLogId : WebLogId) =
rethink<int> {
withTable Table.Page
getAll [ webLogId ] (nameof webLogId)
count
result
withRetryDefault
}
/// Count listed pages for a web log
let countListed (webLogId : WebLogId) =
rethink<int> {
withTable Table.Page
getAll [ webLogId ] (nameof webLogId)
filter "showInPageList" true
count
result
withRetryDefault
}
/// Retrieve all pages for a web log
let findAll (webLogId : WebLogId) =
rethink<Page list> {
withTable Table.Page
getAll [ webLogId ] (nameof webLogId)
without [ "priorPermalinks", "revisions" ]
result
withRetryDefault
}
/// Find a page by its ID
let findById (pageId : PageId) webLogId =
rethink<Page> {
withTable Table.Page
get pageId
without [ "priorPermalinks", "revisions" ]
resultOption
withRetryDefault
}
|> verifyWebLog webLogId (fun it -> it.webLogId)
/// Find a page by its permalink
let findByPermalink (permalink : Permalink) (webLogId : WebLogId) =
rethink<Page> {
withTable Table.Page
getAll [ r.Array (webLogId, permalink) ] (nameof permalink)
without [ "priorPermalinks", "revisions" ]
limit 1
resultOption
withRetryDefault
}
/// Find a page by its ID (including permalinks and revisions)
let findByFullId (pageId : PageId) webLogId =
rethink<Page> {
withTable Table.Page
get pageId
resultOption
withRetryDefault
}
|> verifyWebLog webLogId (fun it -> it.webLogId)
/// Find a list of pages (displayed in admin area)
let findPageOfPages (webLogId : WebLogId) pageNbr =
rethink<Page list> {
withTable Table.Page
getAll [ webLogId ] (nameof webLogId)
without [ "priorPermalinks", "revisions" ]
orderBy "title"
skip ((pageNbr - 1) * 25)
limit 25
result
withRetryDefault
}
/// Functions to manipulate posts
module Post =
/// Count posts for a web log by their status
let countByStatus (status : PostStatus) (webLogId : WebLogId) =
rethink<int> {
withTable Table.Post
getAll [ webLogId ] (nameof webLogId)
filter "status" status
count
result
withRetryDefault
}
/// Find a post by its permalink
let findByPermalink (permalink : Permalink) (webLogId : WebLogId) =
rethink<Post> {
withTable Table.Post
getAll [ r.Array(permalink, webLogId) ] (nameof permalink)
without [ "priorPermalinks", "revisions" ]
limit 1
resultOption
withRetryDefault
}
/// Find posts to be displayed on a page
let findPageOfPublishedPosts (webLogId : WebLogId) pageNbr postsPerPage =
rethink<Post list> {
withTable Table.Post
getAll [ webLogId ] (nameof webLogId)
filter "status" Published
without [ "priorPermalinks", "revisions" ]
orderBy "publishedOn"
skip ((pageNbr - 1) * postsPerPage)
limit postsPerPage
result
withRetryDefault
}
/// Functions to manipulate web logs
module WebLog =
/// Retrieve web log details by the URL base
let findByHost (url : string) =
rethink<WebLog> {
withTable Table.WebLog
getAll [ url ] "urlBase"
limit 1
resultOption
withRetryDefault
}
/// Update web log settings
let updateSettings (webLog : WebLog) =
rethink {
withTable Table.WebLog
get webLog.id
update [
"name", webLog.name
"subtitle", webLog.subtitle
"defaultPage", webLog.defaultPage
"postsPerPage", webLog.postsPerPage
"timeZone", webLog.timeZone
]
write
withRetryDefault
ignoreResult
}