V2 #1

Merged
danieljsummers merged 102 commits from v2 into main 2022-06-23 00:35:12 +00:00
3 changed files with 549 additions and 120 deletions
Showing only changes of commit 409019333b - Show all commits

View File

@ -42,7 +42,7 @@ type IPageData =
/// Add a page /// Add a page
abstract member add : Page -> Task<unit> abstract member add : Page -> Task<unit>
/// Get all pages for the web log (excluding text, revisions, and prior permalinks) /// Get all pages for the web log (excluding meta items, text, revisions, and prior permalinks)
abstract member all : WebLogId -> Task<Page list> abstract member all : WebLogId -> Task<Page list>
/// Count all pages for the given web log /// Count all pages for the given web log
@ -72,7 +72,7 @@ type IPageData =
/// Find pages marked as "show in page list" for the given web log (excluding text, revisions, and prior permalinks) /// Find pages marked as "show in page list" for the given web log (excluding text, revisions, and prior permalinks)
abstract member findListed : WebLogId -> Task<Page list> abstract member findListed : WebLogId -> Task<Page list>
/// Find a page of pages (displayed in admin section) (excluding revisions and prior permalinks) /// Find a page of pages (displayed in admin section) (excluding meta items, revisions and prior permalinks)
abstract member findPageOfPages : WebLogId -> pageNbr : int -> Task<Page list> abstract member findPageOfPages : WebLogId -> pageNbr : int -> Task<Page list>
/// Restore pages from a backup /// Restore pages from a backup

View File

@ -280,7 +280,7 @@ type RethinkDbData (conn : Net.IConnection, config : DataConfig, log : ILogger<R
member _.all webLogId = rethink<Page list> { member _.all webLogId = rethink<Page list> {
withTable Table.Page withTable Table.Page
getAll [ webLogId ] (nameof webLogId) getAll [ webLogId ] (nameof webLogId)
without [ "text"; "revisions"; "priorPermalinks" ] without [ "text"; "metadata"; "revisions"; "priorPermalinks" ]
orderByFunc (fun row -> row["title"].Downcase () :> obj) orderByFunc (fun row -> row["title"].Downcase () :> obj)
result; withRetryDefault conn result; withRetryDefault conn
} }
@ -370,7 +370,7 @@ type RethinkDbData (conn : Net.IConnection, config : DataConfig, log : ILogger<R
member _.findPageOfPages webLogId pageNbr = rethink<Page list> { member _.findPageOfPages webLogId pageNbr = rethink<Page list> {
withTable Table.Page withTable Table.Page
getAll [ webLogId ] (nameof webLogId) getAll [ webLogId ] (nameof webLogId)
without [ "priorPermalinks"; "revisions" ] without [ "metadata"; "priorPermalinks"; "revisions" ]
orderByFunc (fun row -> row["title"].Downcase ()) orderByFunc (fun row -> row["title"].Downcase ())
skip ((pageNbr - 1) * 25) skip ((pageNbr - 1) * 25)
limit 25 limit 25

View File

@ -16,6 +16,11 @@ module private SqliteHelpers =
return it :?> int return it :?> int
} }
/// Get lists of items removed from and added to the given lists
let diffLists<'T, 'U when 'U : equality> oldItems newItems (f : 'T -> 'U) =
let diff compList = fun item -> not (compList |> List.exists (fun other -> f item = f other))
List.filter (diff newItems) oldItems, List.filter (diff oldItems) newItems
/// Create a list of items from the given data reader /// Create a list of items from the given data reader
let toList<'T> (it : SqliteDataReader -> 'T) (rdr : SqliteDataReader) = let toList<'T> (it : SqliteDataReader -> 'T) (rdr : SqliteDataReader) =
seq { while rdr.Read () do it rdr } seq { while rdr.Read () do it rdr }
@ -39,57 +44,78 @@ module private SqliteHelpers =
module Map = module Map =
/// Get a boolean value from a data reader /// Get a boolean value from a data reader
let getBoolean (rdr : SqliteDataReader) col = rdr.GetBoolean (rdr.GetOrdinal col) let getBoolean col (rdr : SqliteDataReader) = rdr.GetBoolean (rdr.GetOrdinal col)
/// Get a date/time value from a data reader /// Get a date/time value from a data reader
let getDateTime (rdr : SqliteDataReader) col = rdr.GetDateTime (rdr.GetOrdinal col) let getDateTime col (rdr : SqliteDataReader) = rdr.GetDateTime (rdr.GetOrdinal col)
/// Get a string value from a data reader /// Get a string value from a data reader
let getString (rdr : SqliteDataReader) col = rdr.GetString (rdr.GetOrdinal col) let getString col (rdr : SqliteDataReader) = rdr.GetString (rdr.GetOrdinal col)
/// Get a possibly null date/time value from a data reader
let tryDateTime col (rdr : SqliteDataReader) =
if rdr.IsDBNull (rdr.GetOrdinal col) then None else Some (getDateTime col rdr)
/// Get a possibly null string value from a data reader /// Get a possibly null string value from a data reader
let tryString (rdr : SqliteDataReader) col = let tryString col (rdr : SqliteDataReader) =
if rdr.IsDBNull (rdr.GetOrdinal col) then None else Some (getString rdr col) if rdr.IsDBNull (rdr.GetOrdinal col) then None else Some (getString col rdr)
/// Create a category ID from the current row in the given data reader
let toCategoryId = getString "id" >> CategoryId
/// Create a category from the current row in the given data reader /// Create a category from the current row in the given data reader
let toCategory (rdr : SqliteDataReader) : Category = let toCategory (rdr : SqliteDataReader) : Category =
{ id = CategoryId (getString rdr "id") { id = toCategoryId rdr
webLogId = WebLogId (getString rdr "web_log_id") webLogId = WebLogId (getString "web_log_id" rdr)
name = getString rdr "name" name = getString "name" rdr
slug = getString rdr "slug" slug = getString "slug" rdr
description = tryString rdr "description" description = tryString "description" rdr
parentId = tryString rdr "parent_id" |> Option.map CategoryId parentId = tryString "parent_id" rdr |> Option.map CategoryId
} }
/// Create a meta item from the current row in the given data reader /// Create a meta item from the current row in the given data reader
let toMetaItem (rdr : SqliteDataReader) : MetaItem = let toMetaItem (rdr : SqliteDataReader) : MetaItem =
{ name = getString rdr "name" { name = getString "name" rdr
value = getString rdr "value" value = getString "value" rdr
} }
/// Create a permalink from the current row in the given data reader /// Create a permalink from the current row in the given data reader
let toPermalink (rdr : SqliteDataReader) : Permalink = let toPermalink = getString "permalink" >> Permalink
Permalink (getString rdr "permalink")
/// Create a page from the current row in the given data reader /// Create a page from the current row in the given data reader
let toPage (rdr : SqliteDataReader) : Page = let toPage (rdr : SqliteDataReader) : Page =
{ Page.empty with { Page.empty with
id = PageId (getString rdr "id") id = PageId (getString "id" rdr)
webLogId = WebLogId (getString rdr "web_log_id") webLogId = WebLogId (getString "web_log_id" rdr)
authorId = WebLogUserId (getString rdr "author_id") authorId = WebLogUserId (getString "author_id" rdr)
title = getString rdr "title" title = getString "title" rdr
permalink = toPermalink rdr permalink = toPermalink rdr
publishedOn = getDateTime rdr "published_on" publishedOn = getDateTime "published_on" rdr
updatedOn = getDateTime rdr "updated_on" updatedOn = getDateTime "updated_on" rdr
showInPageList = getBoolean rdr "show_in_page_list" showInPageList = getBoolean "show_in_page_list" rdr
template = tryString rdr "template" template = tryString "template" rdr
text = getString rdr "page_text" text = getString "page_text" rdr
}
/// Create a post from the current row in the given data reader
let toPost (rdr : SqliteDataReader) : Post =
{ Post.empty with
id = PostId (getString "id" rdr)
webLogId = WebLogId (getString "web_log_id" rdr)
authorId = WebLogUserId (getString "author_id" rdr)
status = PostStatus.parse (getString "status" rdr)
title = getString "title" rdr
permalink = toPermalink rdr
publishedOn = tryDateTime "published_on" rdr
updatedOn = getDateTime "updated_on" rdr
template = tryString "template" rdr
text = getString "page_text" rdr
} }
/// Create a revision from the current row in the given data reader /// Create a revision from the current row in the given data reader
let toRevision (rdr : SqliteDataReader) : Revision = let toRevision (rdr : SqliteDataReader) : Revision =
{ asOf = getDateTime rdr "as_of" { asOf = getDateTime "as_of" rdr
text = MarkupText.parse (getString rdr "revision_text") text = MarkupText.parse (getString "revision_text" rdr)
} }
@ -135,29 +161,177 @@ type SQLiteData (conn : SqliteConnection) =
cmd.Parameters.AddWithValue ("@text", page.text) cmd.Parameters.AddWithValue ("@text", page.text)
] |> ignore ] |> ignore
/// Add parameters for post INSERT or UPDATE statements
let addPostParameters (cmd : SqliteCommand) (post : Post) =
[ cmd.Parameters.AddWithValue ("@id", PostId.toString post.id)
cmd.Parameters.AddWithValue ("@webLogId", WebLogId.toString post.webLogId)
cmd.Parameters.AddWithValue ("@authorId", WebLogUserId.toString post.authorId)
cmd.Parameters.AddWithValue ("@status", PostStatus.toString post.status)
cmd.Parameters.AddWithValue ("@title", post.title)
cmd.Parameters.AddWithValue ("@permalink", Permalink.toString post.permalink)
cmd.Parameters.AddWithValue ("@publishedOn",
match post.publishedOn with Some p -> p :> obj | None -> DBNull.Value)
cmd.Parameters.AddWithValue ("@updatedOn", post.updatedOn)
cmd.Parameters.AddWithValue ("@template",
match post.template with Some t -> t :> obj | None -> DBNull.Value)
cmd.Parameters.AddWithValue ("@text", post.text)
] |> ignore
/// Add a web log ID parameter /// Add a web log ID parameter
let addWebLogId (cmd : SqliteCommand) webLogId = let addWebLogId (cmd : SqliteCommand) webLogId =
cmd.Parameters.AddWithValue ("@webLogId", WebLogId.toString webLogId) |> ignore cmd.Parameters.AddWithValue ("@webLogId", WebLogId.toString webLogId) |> ignore
// -- PAGE STUFF --
/// Append meta items to a page /// Append meta items to a page
let appendPageMeta (page : Page) = backgroundTask { let appendPageMeta (page : Page) = backgroundTask {
use cmd = conn.CreateCommand () use cmd = conn.CreateCommand ()
cmd.CommandText <- "SELECT name, value FROM page_meta WHERE page_id = @id" cmd.CommandText <- "SELECT name, value FROM page_meta WHERE page_id = @id"
cmd.Parameters.AddWithValue ("@id", PageId.toString page.id) |> ignore
use! rdr = cmd.ExecuteReaderAsync () use! rdr = cmd.ExecuteReaderAsync ()
return { page with metadata = toList Map.toMetaItem rdr } return { page with metadata = toList Map.toMetaItem rdr }
} }
/// Return a page with no revisions or prior permalinks /// Append revisions and permalinks to a page
let pageWithoutRevisions (page : Page) = let appendPageRevisionsAndPermalinks (page : Page) = backgroundTask {
{ page with revisions = []; priorPermalinks = [] } use cmd = conn.CreateCommand ()
cmd.CommandText <- "SELECT permalink FROM page_permalink WHERE page_id = @pageId"
cmd.Parameters.AddWithValue ("@pageId", PageId.toString page.id) |> ignore
use! linkRdr = cmd.ExecuteReaderAsync ()
let page = { page with priorPermalinks = toList Map.toPermalink linkRdr }
/// Return a page with no revisions, prior permalinks, or text cmd.CommandText <- "SELECT as_of, revision_text FROM page_revision WHERE page_id = @pageId ORDER BY as_of DESC"
let pageWithoutText page = use! revRdr = cmd.ExecuteReaderAsync ()
{ pageWithoutRevisions page with text = "" } return { page with revisions = toList Map.toRevision revRdr }
}
/// Sort function for pages /// Return a page with no text (or meta items, prior permalinks, or revisions)
let pageSort (page : Page) = let pageWithoutTextOrMeta rdr =
page.title.ToLowerInvariant () { Map.toPage rdr with text = "" }
/// Find meta items added and removed
let diffMetaItems (oldItems : MetaItem list) newItems =
diffLists oldItems newItems (fun item -> $"{item.name}|{item.value}")
/// Find the permalinks added and removed
let diffPermalinks oldLinks newLinks =
diffLists oldLinks newLinks Permalink.toString
/// Find the revisions added and removed
let diffRevisions oldRevs newRevs =
diffLists oldRevs newRevs (fun (rev : Revision) -> $"{rev.asOf.Ticks}|{MarkupText.toString rev.text}")
/// Update a page's metadata items
let updatePageMeta pageId oldItems newItems = backgroundTask {
let toDelete, toAdd = diffMetaItems oldItems newItems
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd (item : MetaItem) = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@pageId", PageId.toString pageId)
cmd.Parameters.AddWithValue ("@name", item.name)
cmd.Parameters.AddWithValue ("@value", item.value)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM page_meta WHERE page_id = @pageId AND name = @name AND value = @value"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO page_meta VALUES (@pageId, @name, @value)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a page's prior permalinks
let updatePagePermalinks pageId oldLinks newLinks = backgroundTask {
let toDelete, toAdd = diffPermalinks oldLinks newLinks
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd link = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@pageId", PageId.toString pageId)
cmd.Parameters.AddWithValue ("@link", Permalink.toString link)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM page_permalink WHERE page_id = @pageId AND permalink = @link"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO page_permalink VALUES (@pageId, @link)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a page's revisions
let updatePageRevisions pageId oldRevs newRevs = backgroundTask {
let toDelete, toAdd = diffRevisions oldRevs newRevs
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd withText rev = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@pageId", PageId.toString pageId)
cmd.Parameters.AddWithValue ("@asOf", rev.asOf)
] |> ignore
if withText then cmd.Parameters.AddWithValue ("@text", MarkupText.toString rev.text) |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM page_revision WHERE page_id = @pageId AND as_of = @asOf"
toDelete
|> List.map (runCmd false)
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO page_revision VALUES (@pageId, @asOf, @text)"
toAdd
|> List.map (runCmd true)
|> Task.WhenAll
|> ignore
}
// -- POST STUFF --
/// Append category IDs, tags, and meta items to a post
let appendPostCategoryTagAndMeta (post : Post) = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <- "SELECT category_id AS id FROM post_category WHERE post_id = @id"
cmd.Parameters.AddWithValue ("@id", PostId.toString post.id) |> ignore
use! catRdr = cmd.ExecuteReaderAsync ()
let post = { post with categoryIds = toList Map.toCategoryId catRdr }
cmd.CommandText <- "SELECT tag FROM post_tag WHERE post_id = @id"
use! tagRdr = cmd.ExecuteReaderAsync ()
let post = { post with tags = toList (Map.getString "tag") tagRdr }
cmd.CommandText <- "SELECT name, value FROM post_meta WHERE post_id = @id"
use! rdr = cmd.ExecuteReaderAsync ()
return { post with metadata = toList Map.toMetaItem rdr }
}
/// Append revisions and permalinks to a post
let appendPostRevisionsAndPermalinks (post : Post) = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <- "SELECT permalink FROM post_permalink WHERE post_id = @postId"
cmd.Parameters.AddWithValue ("@postId", PostId.toString post.id) |> ignore
use! linkRdr = cmd.ExecuteReaderAsync ()
let post = { post with priorPermalinks = toList Map.toPermalink linkRdr }
cmd.CommandText <- "SELECT as_of, revision_text FROM post_revision WHERE post_id = @postId ORDER BY as_of DESC"
use! revRdr = cmd.ExecuteReaderAsync ()
return { post with revisions = toList Map.toRevision revRdr }
}
/// Return a post with no revisions or prior permalinks /// Return a post with no revisions or prior permalinks
let postWithoutRevisions (post : Post) = let postWithoutRevisions (post : Post) =
@ -167,6 +341,139 @@ type SQLiteData (conn : SqliteConnection) =
let postWithoutText post = let postWithoutText post =
{ postWithoutRevisions post with text = "" } { postWithoutRevisions post with text = "" }
/// Update a post's assigned categories
let updatePostCategories postId oldCats newCats = backgroundTask {
let toDelete, toAdd = diffLists oldCats newCats CategoryId.toString
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd catId = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@postId", PostId.toString postId)
cmd.Parameters.AddWithValue ("@categoryId", CategoryId.toString catId)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM post_category WHERE post_id = @postId AND category_id = @categoryId"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO post_category VALUES (@postId, @categoryId)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a post's assigned categories
let updatePostTags postId oldTags newTags = backgroundTask {
let toDelete, toAdd = diffLists oldTags newTags id
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd tag = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@postId", PostId.toString postId)
cmd.Parameters.AddWithValue ("@tag", tag)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM post_tag WHERE post_id = @postId AND tag = @tag"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO post_tag VALUES (@postId, @tag)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a post's metadata items
let updatePostMeta postId oldItems newItems = backgroundTask {
let toDelete, toAdd = diffMetaItems oldItems newItems
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd (item : MetaItem) = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@postId", PostId.toString postId)
cmd.Parameters.AddWithValue ("@name", item.name)
cmd.Parameters.AddWithValue ("@value", item.value)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM post_meta WHERE post_id = @postId AND name = @name AND value = @value"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO post_meta VALUES (@postId, @name, @value)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a post's prior permalinks
let updatePostPermalinks postId oldLinks newLinks = backgroundTask {
let toDelete, toAdd = diffPermalinks oldLinks newLinks
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd link = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@postId", PostId.toString postId)
cmd.Parameters.AddWithValue ("@link", Permalink.toString link)
] |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM post_permalink WHERE post_id = @postId AND permalink = @link"
toDelete
|> List.map runCmd
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO post_permalink VALUES (@postId, @link)"
toAdd
|> List.map runCmd
|> Task.WhenAll
|> ignore
}
/// Update a post's revisions
let updatePostRevisions postId oldRevs newRevs = backgroundTask {
let toDelete, toAdd = diffRevisions oldRevs newRevs
if List.isEmpty toDelete && List.isEmpty toAdd then
return ()
else
use cmd = conn.CreateCommand ()
let runCmd withText rev = backgroundTask {
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@postId", PostId.toString postId)
cmd.Parameters.AddWithValue ("@asOf", rev.asOf)
] |> ignore
if withText then cmd.Parameters.AddWithValue ("@text", MarkupText.toString rev.text) |> ignore
do! write cmd
}
cmd.CommandText <- "DELETE FROM post_revision WHERE post_id = @postId AND as_of = @asOf"
toDelete
|> List.map (runCmd false)
|> Task.WhenAll
|> ignore
cmd.CommandText <- "INSERT INTO post_revision VALUES (@postId, @asOf, @text)"
toAdd
|> List.map (runCmd true)
|> Task.WhenAll
|> ignore
}
/// The connection for this instance /// The connection for this instance
@ -327,33 +634,17 @@ type SQLiteData (conn : SqliteConnection) =
@showInPageList, @template, @text)""" @showInPageList, @template, @text)"""
addPageParameters cmd page addPageParameters cmd page
do! write cmd do! write cmd
// Metadata do! updatePageMeta page.id [] page.metadata
cmd.CommandText <- "INSERT INTO page_meta VALUES (@pageId, @name, @value)" do! updatePagePermalinks page.id [] page.priorPermalinks
for meta in page.metadata do do! updatePageRevisions page.id [] page.revisions
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@pageId", PageId.toString page.id)
cmd.Parameters.AddWithValue ("@name", meta.name)
cmd.Parameters.AddWithValue ("@value", meta.value)
] |> ignore
do! write cmd
// Revisions
cmd.CommandText <- "INSERT INTO page_revision VALUES (@pageId, @asOf, @text)"
for rev in page.revisions do
cmd.Parameters.Clear ()
[ cmd.Parameters.AddWithValue ("@pageId", PageId.toString page.id)
cmd.Parameters.AddWithValue ("@asOf", rev.asOf)
cmd.Parameters.AddWithValue ("@text", MarkupText.toString rev.text)
] |> ignore
do! write cmd
} }
member _.all webLogId = backgroundTask { member _.all webLogId = backgroundTask {
use cmd = conn.CreateCommand () use cmd = conn.CreateCommand ()
cmd.CommandText <- "SELECT * FROM page WHERE web_log_id = @webLogId ORDER BY LOWER(title)" cmd.CommandText <- "SELECT * FROM page WHERE web_log_id = @webLogId ORDER BY LOWER(title)"
cmd.Parameters.AddWithValue ("@webLogId", WebLogId.toString webLogId) |> ignore addWebLogId cmd webLogId
let noText rdr = { Map.toPage rdr with text = "" }
use! rdr = cmd.ExecuteReaderAsync () use! rdr = cmd.ExecuteReaderAsync ()
return toList noText rdr return toList pageWithoutTextOrMeta rdr
} }
member _.countAll webLogId = backgroundTask { member _.countAll webLogId = backgroundTask {
@ -390,14 +681,8 @@ type SQLiteData (conn : SqliteConnection) =
member this.findFullById pageId webLogId = backgroundTask { member this.findFullById pageId webLogId = backgroundTask {
match! this.findById pageId webLogId with match! this.findById pageId webLogId with
| Some page -> | Some page ->
use cmd = conn.CreateCommand () let! page = appendPageRevisionsAndPermalinks page
cmd.CommandText <- "SELECT * FROM page_permalink WHERE page_id = @pageId" return Some page
cmd.Parameters.AddWithValue ("@pageId", PageId.toString page.id) |> ignore
use! linkRdr = cmd.ExecuteReaderAsync ()
let page = { page with priorPermalinks = toList Map.toPermalink linkRdr }
cmd.CommandText <- "SELECT * FROM page_revision WHERE page_id = @pageId"
use! revRdr = cmd.ExecuteReaderAsync ()
return Some { page with revisions = toList Map.toRevision revRdr }
| None -> return None | None -> return None
} }
@ -450,36 +735,89 @@ type SQLiteData (conn : SqliteConnection) =
return if rdr.Read () then Some (Map.toPermalink rdr) else None return if rdr.Read () then Some (Map.toPermalink rdr) else None
} }
member _.findFullByWebLog webLogId = member _.findFullByWebLog webLogId = backgroundTask {
Collection.Page.Find (fun p -> p.webLogId = webLogId) use cmd = conn.CreateCommand ()
|> toList cmd.CommandText <- "SELECT * FROM page WHERE web_log_id = @webLogId"
addWebLogId cmd webLogId
member _.findListed webLogId = use! rdr = cmd.ExecuteReaderAsync ()
Collection.Page.Find (fun p -> p.webLogId = webLogId && p.showInPageList) let! pages =
|> Seq.map pageWithoutText toList Map.toPage rdr
|> Seq.sortBy pageSort |> List.map (fun page -> backgroundTask {
|> toList let! page = appendPageMeta page
return! appendPageRevisionsAndPermalinks page
member _.findPageOfPages webLogId pageNbr = })
Collection.Page.Find (fun p -> p.webLogId = webLogId) |> Task.WhenAll
|> Seq.map pageWithoutRevisions return List.ofArray pages
|> Seq.sortBy pageSort
|> toPagedList pageNbr 25
member _.restore pages = backgroundTask {
let _ = Collection.Page.InsertBulk pages
do! checkpoint ()
} }
member _.update page = backgroundTask { member _.findListed webLogId = backgroundTask {
let _ = Collection.Page.Update page use cmd = conn.CreateCommand ()
do! checkpoint () cmd.CommandText <-
"""SELECT *
FROM page
WHERE web_log_id = @webLogId
AND show_in_page_list = @showInPageList
ORDER BY LOWER(title)"""
addWebLogId cmd webLogId
cmd.Parameters.AddWithValue ("@showInPageList", true) |> ignore
use! rdr = cmd.ExecuteReaderAsync ()
let! pages =
toList pageWithoutTextOrMeta rdr
|> List.map (fun page -> backgroundTask { return! appendPageMeta page })
|> Task.WhenAll
return List.ofArray pages
}
member _.findPageOfPages webLogId pageNbr = backgroundTask {
use cmd = conn.CreateCommand ()
cmd.CommandText <-
"""SELECT *
FROM page
WHERE web_log_id = @webLogId
ORDER BY LOWER(title)
LIMIT @pageSize OFFSET @toSkip"""
addWebLogId cmd webLogId
[ cmd.Parameters.AddWithValue ("@pageSize", 26)
cmd.Parameters.AddWithValue ("@offset", pageNbr * 25)
] |> ignore
use! rdr = cmd.ExecuteReaderAsync ()
return toList Map.toPage rdr
}
member this.restore pages = backgroundTask {
for page in pages do
do! this.add page
}
member this.update page = backgroundTask {
match! this.findFullById page.id page.webLogId with
| Some oldPage ->
use cmd = conn.CreateCommand ()
cmd.CommandText <-
"""UPDATE page
SET author_id = @authorId,
title = @title,
permalink = @permalink,
published_on = @publishedOn,
updated_on = @updatedOn,
show_in_page_list = @showInPageList,
template = @template,
page_text = @text
WHERE id = @pageId
AND web_log_id = @webLogId"""
addPageParameters cmd page
do! write cmd
do! updatePageMeta page.id oldPage.metadata page.metadata
do! updatePagePermalinks page.id oldPage.priorPermalinks page.priorPermalinks
do! updatePageRevisions page.id oldPage.revisions page.revisions
return ()
| None -> return ()
} }
member this.updatePriorPermalinks pageId webLogId permalinks = backgroundTask { member this.updatePriorPermalinks pageId webLogId permalinks = backgroundTask {
match! this.findFullById pageId webLogId with match! this.findFullById pageId webLogId with
| Some page -> | Some page ->
do! this.update { page with priorPermalinks = permalinks } do! updatePagePermalinks pageId page.priorPermalinks permalinks
return true return true
| None -> return false | None -> return false
} }
@ -489,43 +827,113 @@ type SQLiteData (conn : SqliteConnection) =
new IPostData with new IPostData with
member _.add post = backgroundTask { member _.add post = backgroundTask {
let _ = Collection.Post.Insert post use cmd = conn.CreateCommand ()
do! checkpoint () cmd.CommandText <-
"""INSERT INTO post
VALUES (@id, @webLogId, @authorId, @status, @title, @permalink, @publishedOn, @updatedOn,
@template, @text)"""
addPostParameters cmd post
do! write cmd
do! updatePostCategories post.id [] post.categoryIds
do! updatePostTags post.id [] post.tags
do! updatePostMeta post.id [] post.metadata
do! updatePostPermalinks post.id [] post.priorPermalinks
do! updatePostRevisions post.id [] post.revisions
} }
member _.countByStatus status webLogId = member _.countByStatus status webLogId = backgroundTask {
Collection.Post.Count (fun p -> p.webLogId = webLogId && p.status = status) use cmd = conn.CreateCommand ()
|> Task.FromResult cmd.CommandText <-
"SELECT COUNT(page_id) FROM page WHERE web_log_id = @webLogId AND status = @status"
addWebLogId cmd webLogId
cmd.Parameters.AddWithValue ("@status", PostStatus.toString status) |> ignore
return! count cmd
}
member _.findByPermalink permalink webLogId = member _.findByPermalink permalink webLogId = backgroundTask {
Collection.Post.Find (fun p -> p.webLogId = webLogId && p.permalink = permalink) use cmd = conn.CreateCommand ()
|> tryFirst cmd.CommandText <- "SELECT * FROM post WHERE web_log_id = @webLogId AND permalink = @link"
addWebLogId cmd webLogId
cmd.Parameters.AddWithValue ("@link", Permalink.toString permalink) |> ignore
use! rdr = cmd.ExecuteReaderAsync ()
if rdr.Read () then
let! post = appendPostCategoryTagAndMeta (Map.toPost rdr)
return Some post
else
return None
}
member _.findFullById postId webLogId = member _.findFullById postId webLogId = backgroundTask {
Collection.Post.FindById (PostIdMapping.toBson postId) use cmd = conn.CreateCommand ()
//|> verifyWebLog webLogId (fun p -> p.webLogId) cmd.CommandText <- "SELECT * FROM post WHERE id = @id"
cmd.Parameters.AddWithValue ("@id", PostId.toString postId) |> ignore
use! rdr = cmd.ExecuteReaderAsync ()
if rdr.Read () then
match verifyWebLog<Post> webLogId (fun p -> p.webLogId) Map.toPost rdr with
| Some post ->
let! post = appendPostCategoryTagAndMeta post
let! post = appendPostRevisionsAndPermalinks post
return Some post
| None ->
return None
else
return None
}
member this.delete postId webLogId = backgroundTask { member this.delete postId webLogId = backgroundTask {
match! this.findFullById postId webLogId with match! this.findFullById postId webLogId with
| Some _ -> | Some _ ->
let _ = Collection.Post.Delete (PostIdMapping.toBson postId) use cmd = conn.CreateCommand ()
do! checkpoint () cmd.CommandText <- "DELETE FROM post_revision WHERE post_id = @id"
cmd.Parameters.AddWithValue ("@id", PostId.toString postId) |> ignore
do! write cmd
cmd.CommandText <- "DELETE FROM post_permalink WHERE post_id = @id"
do! write cmd
cmd.CommandText <- "DELETE FROM post_meta WHERE post_id = @id"
do! write cmd
cmd.CommandText <- "DELETE FROM post_tag WHERE post_id = @id"
do! write cmd
cmd.CommandText <- "DELETE FROM post_category WHERE post_id = @id"
do! write cmd
cmd.CommandText <- "DELETE FROM post WHERE id = @id"
do! write cmd
return true return true
| None -> return false | None -> return false
} }
member _.findCurrentPermalink permalinks webLogId = backgroundTask { member _.findCurrentPermalink permalinks webLogId = backgroundTask {
let! result = use cmd = conn.CreateCommand ()
Collection.Post.Find (fun p -> cmd.CommandText <-
p.webLogId = webLogId """SELECT p.permalink
&& p.priorPermalinks |> List.exists (fun link -> permalinks |> List.contains link)) FROM post p
|> tryFirst INNER JOIN post_permalink pp ON pp.post_id = p.id
return result |> Option.map (fun post -> post.permalink) WHERE p.web_log_id = @webLogId
AND pp.permalink IN ("""
permalinks
|> List.iteri (fun idx link ->
if idx > 0 then cmd.CommandText <- $"{cmd.CommandText}, "
cmd.CommandText <- $"{cmd.CommandText}@link{idx}"
cmd.Parameters.AddWithValue ($"@link{idx}", Permalink.toString link) |> ignore)
cmd.CommandText <- $"{cmd.CommandText})"
addWebLogId cmd webLogId
use! rdr = cmd.ExecuteReaderAsync ()
return if rdr.Read () then Some (Map.toPermalink rdr) else None
} }
member _.findFullByWebLog webLogId = member _.findFullByWebLog webLogId = backgroundTask {
Collection.Post.Find (fun p -> p.webLogId = webLogId) use cmd = conn.CreateCommand ()
|> toList cmd.CommandText <- "SELECT * FROM post WHERE web_log_id = @webLogId"
addWebLogId cmd webLogId
use! rdr = cmd.ExecuteReaderAsync ()
let! posts =
toList Map.toPost rdr
|> List.map (fun post -> backgroundTask {
let! post = appendPostCategoryTagAndMeta post
return! appendPostRevisionsAndPermalinks post
})
|> Task.WhenAll
return List.ofArray posts
}
member _.findPageOfCategorizedPosts webLogId categoryIds pageNbr postsPerPage = member _.findPageOfCategorizedPosts webLogId categoryIds pageNbr postsPerPage =
Collection.Post.Find (fun p -> Collection.Post.Find (fun p ->
@ -571,20 +979,41 @@ type SQLiteData (conn : SqliteConnection) =
return older, newer return older, newer
} }
member _.restore posts = backgroundTask { member this.restore posts = backgroundTask {
let _ = Collection.Post.InsertBulk posts for post in posts do
do! checkpoint () do! this.add post
} }
member _.update post = backgroundTask { member this.update post = backgroundTask {
let _ = Collection.Post.Update post match! this.findFullById post.id post.webLogId with
do! checkpoint () | Some oldPost ->
use cmd = conn.CreateCommand ()
cmd.CommandText <-
"""UPDATE post
SET author_id = @author_id,
status = @status,
title = @title,
permalink = @permalink,
published_on = @publishedOn,
updated_on = @updatedOn,
template = @template,
post_text = @text
WHERE id = @id
AND web_log_id = @webLogId"""
addPostParameters cmd post
do! write cmd
do! updatePostCategories post.id oldPost.categoryIds post.categoryIds
do! updatePostTags post.id oldPost.tags post.tags
do! updatePostMeta post.id oldPost.metadata post.metadata
do! updatePostPermalinks post.id oldPost.priorPermalinks post.priorPermalinks
do! updatePostRevisions post.id oldPost.revisions post.revisions
| None -> return ()
} }
member this.updatePriorPermalinks postId webLogId permalinks = backgroundTask { member this.updatePriorPermalinks postId webLogId permalinks = backgroundTask {
match! this.findFullById postId webLogId with match! this.findFullById postId webLogId with
| Some post -> | Some post ->
do! this.update { post with priorPermalinks = permalinks } do! updatePostPermalinks postId post.priorPermalinks permalinks
return true return true
| None -> return false | None -> return false
} }