WIP on SQLite doc library
This commit is contained in:
parent
d330c97d9f
commit
bb4252f3c1
@ -76,13 +76,6 @@ let toList<'T> (it: SqliteDataReader -> 'T) (rdr: SqliteDataReader) =
|
|||||||
seq { while rdr.Read () do it rdr }
|
seq { while rdr.Read () do it rdr }
|
||||||
|> List.ofSeq
|
|> List.ofSeq
|
||||||
|
|
||||||
/// Verify that the web log ID matches before returning an item
|
|
||||||
let verifyWebLog<'T> webLogId (prop : 'T -> WebLogId) (it : SqliteDataReader -> 'T) (rdr : SqliteDataReader) =
|
|
||||||
if rdr.Read() then
|
|
||||||
let item = it rdr
|
|
||||||
if prop item = webLogId then Some item else None
|
|
||||||
else None
|
|
||||||
|
|
||||||
/// Execute a command that returns no data
|
/// Execute a command that returns no data
|
||||||
let write (cmd: SqliteCommand) = backgroundTask {
|
let write (cmd: SqliteCommand) = backgroundTask {
|
||||||
let! _ = cmd.ExecuteNonQueryAsync()
|
let! _ = cmd.ExecuteNonQueryAsync()
|
||||||
@ -90,7 +83,7 @@ let write (cmd: SqliteCommand) = backgroundTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a possibly-missing parameter, substituting null for None
|
/// Add a possibly-missing parameter, substituting null for None
|
||||||
let maybe<'T> (it : 'T option) : obj = match it with Some x -> x :> obj | None -> DBNull.Value
|
let maybe<'T> (it: 'T option) : obj = match it with Some x -> x :> obj | None -> DBNull.Value
|
||||||
|
|
||||||
/// Create a value for a Duration
|
/// Create a value for a Duration
|
||||||
let durationParam =
|
let durationParam =
|
||||||
@ -261,7 +254,8 @@ let cmdToList<'TDoc> (cmd: SqliteCommand) ser = backgroundTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Queries to assist with document manipulation
|
/// Queries to assist with document manipulation
|
||||||
module Query =
|
[<Obsolete("change me")>]
|
||||||
|
module QueryOld =
|
||||||
|
|
||||||
/// Fragment to add an ID condition to a WHERE clause (parameter @id)
|
/// Fragment to add an ID condition to a WHERE clause (parameter @id)
|
||||||
let whereById =
|
let whereById =
|
||||||
@ -292,6 +286,14 @@ module Query =
|
|||||||
$"DELETE FROM %s{table} WHERE {whereById}"
|
$"DELETE FROM %s{table} WHERE {whereById}"
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a document ID parameter
|
||||||
|
let idParam (key: 'TKey) =
|
||||||
|
SqliteParameter("@id", string key)
|
||||||
|
|
||||||
|
/// Create a web log ID parameter
|
||||||
|
let webLogParam (webLogId: WebLogId) =
|
||||||
|
SqliteParameter("@webLogId", string webLogId)
|
||||||
|
|
||||||
let addParam (cmd: SqliteCommand) name (value: obj) =
|
let addParam (cmd: SqliteCommand) name (value: obj) =
|
||||||
cmd.Parameters.AddWithValue(name, value) |> ignore
|
cmd.Parameters.AddWithValue(name, value) |> ignore
|
||||||
|
|
||||||
@ -307,18 +309,39 @@ let addDocParam<'TDoc> (cmd: SqliteCommand) (doc: 'TDoc) ser =
|
|||||||
let addWebLogId (cmd: SqliteCommand) (webLogId: WebLogId) =
|
let addWebLogId (cmd: SqliteCommand) (webLogId: WebLogId) =
|
||||||
addParam cmd "@webLogId" (string webLogId)
|
addParam cmd "@webLogId" (string webLogId)
|
||||||
|
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||||
|
|
||||||
/// Functions for manipulating documents
|
/// Functions for manipulating documents
|
||||||
module Document =
|
module Document =
|
||||||
|
|
||||||
|
/// Queries to assist with document manipulation
|
||||||
|
module Query =
|
||||||
|
|
||||||
|
/// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId)
|
||||||
|
let whereByWebLog =
|
||||||
|
Query.whereFieldEquals "WebLogId" "@webLogId"
|
||||||
|
|
||||||
|
/// A SELECT query to count documents for a given web log ID
|
||||||
|
let countByWebLog table =
|
||||||
|
$"{Query.Count.all table} WHERE {whereByWebLog}"
|
||||||
|
|
||||||
|
/// A query to select from a table by the document's ID and its web log ID
|
||||||
|
let selectByIdAndWebLog table =
|
||||||
|
$"{Query.Find.byFieldEquals table} AND {whereByWebLog}"
|
||||||
|
|
||||||
|
/// A query to select from a table by its web log ID
|
||||||
|
let selectByWebLog table =
|
||||||
|
$"{Query.selectFromTable table} WHERE {whereByWebLog}"
|
||||||
|
|
||||||
/// Count documents for the given web log ID
|
/// Count documents for the given web log ID
|
||||||
let countByWebLog (conn: SqliteConnection) table webLogId = backgroundTask {
|
let countByWebLog table (webLogId: WebLogId) conn = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
let! count = Count.byFieldEquals table "WebLogId" webLogId conn
|
||||||
cmd.CommandText <- Query.countByWebLog table
|
return int count
|
||||||
addWebLogId cmd webLogId
|
|
||||||
return! count cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a document by its ID
|
/// Find a document by its ID
|
||||||
|
[<Obsolete("replace this")>]
|
||||||
let findById<'TKey, 'TDoc> (conn: SqliteConnection) ser table (key: 'TKey) = backgroundTask {
|
let findById<'TKey, 'TDoc> (conn: SqliteConnection) ser table (key: 'TKey) = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- $"{Query.selectFromTable table} WHERE {Query.whereById}"
|
cmd.CommandText <- $"{Query.selectFromTable table} WHERE {Query.whereById}"
|
||||||
@ -329,55 +352,49 @@ module Document =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find a document by its ID and web log ID
|
/// Find a document by its ID and web log ID
|
||||||
let findByIdAndWebLog<'TKey, 'TDoc> (conn: SqliteConnection) ser table (key: 'TKey) webLogId = backgroundTask {
|
let findByIdAndWebLog<'TKey, 'TDoc> table (key: 'TKey) webLogId conn =
|
||||||
use cmd = conn.CreateCommand()
|
Custom.single (Query.selectByIdAndWebLog table) [ idParam key; webLogParam webLogId ] fromData<'TDoc> conn
|
||||||
cmd.CommandText <- $"{Query.selectFromTable table} WHERE {Query.whereById} AND {Query.whereByWebLog}"
|
|
||||||
addDocId cmd key
|
|
||||||
addWebLogId cmd webLogId
|
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
|
||||||
let! isFound = rdr.ReadAsync()
|
|
||||||
return if isFound then Some (Map.fromDoc<'TDoc> ser rdr) else None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find documents for the given web log
|
/// Find documents for the given web log
|
||||||
let findByWebLog<'TDoc> (conn: SqliteConnection) ser table webLogId =
|
let findByWebLog<'TDoc> table (webLogId: WebLogId) conn =
|
||||||
use cmd = conn.CreateCommand()
|
Find.byFieldEquals<'TDoc> table "WebLogId" webLogId conn
|
||||||
cmd.CommandText <- $"{Query.selectFromTable table} WHERE {Query.whereByWebLog}"
|
|
||||||
addWebLogId cmd webLogId
|
|
||||||
cmdToList<'TDoc> cmd ser
|
|
||||||
|
|
||||||
/// Insert a document
|
/// Insert a document
|
||||||
|
[<Obsolete("replace this")>]
|
||||||
let insert<'TDoc> (conn: SqliteConnection) ser table (doc: 'TDoc) = backgroundTask {
|
let insert<'TDoc> (conn: SqliteConnection) ser table (doc: 'TDoc) = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- Query.insert table
|
cmd.CommandText <- QueryOld.insert table
|
||||||
addDocParam<'TDoc> cmd doc ser
|
addDocParam<'TDoc> cmd doc ser
|
||||||
do! write cmd
|
do! write cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update (replace) a document by its ID
|
/// Update (replace) a document by its ID
|
||||||
|
[<Obsolete("replace this")>]
|
||||||
let update<'TKey, 'TDoc> (conn: SqliteConnection) ser table (key: 'TKey) (doc: 'TDoc) = backgroundTask {
|
let update<'TKey, 'TDoc> (conn: SqliteConnection) ser table (key: 'TKey) (doc: 'TDoc) = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- Query.updateById table
|
cmd.CommandText <- QueryOld.updateById table
|
||||||
addDocId cmd key
|
addDocId cmd key
|
||||||
addDocParam<'TDoc> cmd doc ser
|
addDocParam<'TDoc> cmd doc ser
|
||||||
do! write cmd
|
do! write cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a field in a document by its ID
|
/// Update a field in a document by its ID
|
||||||
|
[<Obsolete("replace this")>]
|
||||||
let updateField<'TKey, 'TValue> (conn: SqliteConnection) ser table (key: 'TKey) jsonField
|
let updateField<'TKey, 'TValue> (conn: SqliteConnection) ser table (key: 'TKey) jsonField
|
||||||
(value: 'TValue) = backgroundTask {
|
(value: 'TValue) = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
$"UPDATE %s{table} SET data = json_set(data, '$.{jsonField}', json(@it)) WHERE {Query.whereById}"
|
$"UPDATE %s{table} SET data = json_set(data, '$.{jsonField}', json(@it)) WHERE {QueryOld.whereById}"
|
||||||
addDocId cmd key
|
addDocId cmd key
|
||||||
addParam cmd "@it" (Utils.serialize ser value)
|
addParam cmd "@it" (Utils.serialize ser value)
|
||||||
do! write cmd
|
do! write cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a document by its ID
|
/// Delete a document by its ID
|
||||||
|
[<Obsolete("replace this")>]
|
||||||
let delete<'TKey> (conn: SqliteConnection) table (key: 'TKey) = backgroundTask {
|
let delete<'TKey> (conn: SqliteConnection) table (key: 'TKey) = backgroundTask {
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- Query.deleteById table
|
cmd.CommandText <- QueryOld.deleteById table
|
||||||
addDocId cmd key
|
addDocId cmd key
|
||||||
do! write cmd
|
do! write cmd
|
||||||
}
|
}
|
||||||
@ -386,29 +403,24 @@ module Document =
|
|||||||
module Revisions =
|
module Revisions =
|
||||||
|
|
||||||
/// Find all revisions for the given entity
|
/// Find all revisions for the given entity
|
||||||
let findByEntityId<'TKey> (conn: SqliteConnection) revTable entityTable (key: 'TKey) = backgroundTask {
|
let findByEntityId<'TKey> revTable entityTable (key: 'TKey) conn =
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <-
|
|
||||||
$"SELECT as_of, revision_text FROM %s{revTable} WHERE %s{entityTable}_id = @id ORDER BY as_of DESC"
|
$"SELECT as_of, revision_text FROM %s{revTable} WHERE %s{entityTable}_id = @id ORDER BY as_of DESC"
|
||||||
addDocId cmd key
|
[ idParam key ]
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
Map.toRevision
|
||||||
return toList Map.toRevision rdr
|
conn
|
||||||
}
|
|
||||||
|
|
||||||
/// Find all revisions for all posts for the given web log
|
/// Find all revisions for all posts for the given web log
|
||||||
let findByWebLog<'TKey> (conn: SqliteConnection) revTable entityTable (keyFunc: string -> 'TKey)
|
let findByWebLog<'TKey> revTable entityTable (keyFunc: string -> 'TKey) webLogId conn =
|
||||||
webLogId = backgroundTask {
|
Custom.list
|
||||||
use cmd = conn.CreateCommand()
|
|
||||||
cmd.CommandText <-
|
|
||||||
$"SELECT pr.*
|
$"SELECT pr.*
|
||||||
FROM %s{revTable} pr
|
FROM %s{revTable} pr
|
||||||
INNER JOIN %s{entityTable} p ON p.data ->> 'Id' = pr.{entityTable}_id
|
INNER JOIN %s{entityTable} p ON p.data ->> 'Id' = pr.{entityTable}_id
|
||||||
WHERE p.{Query.whereByWebLog}
|
WHERE p.{Document.Query.whereByWebLog}
|
||||||
ORDER BY as_of DESC"
|
ORDER BY as_of DESC"
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId ]
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
(fun rdr -> keyFunc (Map.getString $"{entityTable}_id" rdr), Map.toRevision rdr)
|
||||||
return toList (fun rdr -> keyFunc (Map.getString $"{entityTable}_id" rdr), Map.toRevision rdr) rdr
|
conn
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters for a revision INSERT statement
|
/// Parameters for a revision INSERT statement
|
||||||
let revParams<'TKey> (key: 'TKey) rev =
|
let revParams<'TKey> (key: 'TKey) rev =
|
||||||
@ -416,26 +428,15 @@ module Revisions =
|
|||||||
SqliteParameter("@id", string key)
|
SqliteParameter("@id", string key)
|
||||||
SqliteParameter("@text", rev.Text) ]
|
SqliteParameter("@text", rev.Text) ]
|
||||||
|
|
||||||
/// The SQL statement to insert a revision
|
|
||||||
let insertSql table =
|
|
||||||
$"INSERT INTO %s{table} VALUES (@id, @asOf, @text)"
|
|
||||||
|
|
||||||
/// Update a page or post's revisions
|
/// Update a page or post's revisions
|
||||||
let update<'TKey> (conn: SqliteConnection) revTable entityTable (key: 'TKey) oldRevs newRevs = backgroundTask {
|
let update<'TKey> revTable entityTable (key: 'TKey) oldRevs newRevs conn = backgroundTask {
|
||||||
let toDelete, toAdd = Utils.diffRevisions oldRevs newRevs
|
let toDelete, toAdd = Utils.diffRevisions oldRevs newRevs
|
||||||
if not (List.isEmpty toDelete) || not (List.isEmpty toAdd) then
|
if not (List.isEmpty toDelete) || not (List.isEmpty toAdd) then
|
||||||
use cmd = conn.CreateCommand()
|
for delRev in toDelete do
|
||||||
if not (List.isEmpty toDelete) then
|
do! Custom.nonQuery
|
||||||
cmd.CommandText <- $"DELETE FROM %s{revTable} WHERE %s{entityTable}_id = @id AND as_of = @asOf"
|
$"DELETE FROM %s{revTable} WHERE %s{entityTable}_id = @id AND as_of = @asOf"
|
||||||
for delRev in toDelete do
|
[ idParam key; SqliteParameter("@asOf", instantParam delRev.AsOf) ]
|
||||||
cmd.Parameters.Clear()
|
conn
|
||||||
addDocId cmd key
|
for addRev in toAdd do
|
||||||
addParam cmd "@asOf" delRev.AsOf
|
do! Custom.nonQuery $"INSERT INTO {revTable} VALUES (@id, @asOf, @text)" (revParams key addRev) conn
|
||||||
do! write cmd
|
|
||||||
if not (List.isEmpty toAdd) then
|
|
||||||
cmd.CommandText <- insertSql revTable
|
|
||||||
for addRev in toAdd do
|
|
||||||
cmd.Parameters.Clear()
|
|
||||||
cmd.Parameters.AddRange(revParams key addRev)
|
|
||||||
do! write cmd
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
namespace MyWebLog.Data.SQLite
|
namespace MyWebLog.Data.SQLite
|
||||||
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open MyWebLog
|
open MyWebLog
|
||||||
@ -13,29 +15,24 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||||||
/// The name of the parent ID field
|
/// The name of the parent ID field
|
||||||
let parentIdField = nameof Category.Empty.ParentId
|
let parentIdField = nameof Category.Empty.ParentId
|
||||||
|
|
||||||
/// Add a category
|
|
||||||
let add (cat: Category) =
|
|
||||||
log.LogTrace "Category.add"
|
|
||||||
Document.insert conn ser Table.Category cat
|
|
||||||
|
|
||||||
/// Count all categories for the given web log
|
/// Count all categories for the given web log
|
||||||
let countAll webLogId =
|
let countAll webLogId =
|
||||||
log.LogTrace "Category.countAll"
|
log.LogTrace "Category.countAll"
|
||||||
Document.countByWebLog conn Table.Category webLogId
|
Document.countByWebLog Table.Category webLogId conn
|
||||||
|
|
||||||
/// Count all top-level categories for the given web log
|
/// Count all top-level categories for the given web log
|
||||||
let countTopLevel webLogId = backgroundTask {
|
let countTopLevel webLogId =
|
||||||
log.LogTrace "Category.countTopLevel"
|
log.LogTrace "Category.countTopLevel"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.scalar
|
||||||
cmd.CommandText <- $"{Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL"
|
$"{Document.Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL"
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId ]
|
||||||
return! count cmd
|
(fun rdr -> int (rdr.GetInt64(0)))
|
||||||
}
|
conn
|
||||||
|
|
||||||
/// Find all categories for the given web log
|
/// Find all categories for the given web log
|
||||||
let findByWebLog webLogId =
|
let findByWebLog webLogId =
|
||||||
log.LogTrace "Category.findByWebLog"
|
log.LogTrace "Category.findByWebLog"
|
||||||
Document.findByWebLog<Category> conn ser Table.Category webLogId
|
Document.findByWebLog<Category> Table.Category webLogId conn
|
||||||
|
|
||||||
/// Retrieve all categories for the given web log in a DotLiquid-friendly format
|
/// Retrieve all categories for the given web log in a DotLiquid-friendly format
|
||||||
let findAllForView webLogId = backgroundTask {
|
let findAllForView webLogId = backgroundTask {
|
||||||
@ -53,104 +50,74 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||||||
|> Seq.append (Seq.singleton it.Id)
|
|> Seq.append (Seq.singleton it.Id)
|
||||||
|> List.ofSeq
|
|> List.ofSeq
|
||||||
|> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
|
|> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
|
||||||
use cmd = conn.CreateCommand()
|
let query = $"""
|
||||||
cmd.CommandText <- $"
|
|
||||||
SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}')
|
SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}')
|
||||||
FROM {Table.Post}
|
FROM {Table.Post}
|
||||||
WHERE {Query.whereByWebLog}
|
WHERE {Document.Query.whereByWebLog}
|
||||||
AND data ->> '{nameof Post.Empty.Status}' = '{string Published}'
|
AND {Query.whereFieldEquals (nameof Post.Empty.Status) $"'{string Published}'"}
|
||||||
AND {catSql}"
|
AND {catSql}"""
|
||||||
addWebLogId cmd webLogId
|
let! postCount = Custom.scalar query (webLogParam webLogId :: catParams) (_.GetInt64(0)) conn
|
||||||
cmd.Parameters.AddRange catParams
|
return it.Id, int postCount
|
||||||
let! postCount = count cmd
|
|
||||||
return it.Id, postCount
|
|
||||||
})
|
})
|
||||||
|> Task.WhenAll
|
|> Task.WhenAll
|
||||||
return
|
return
|
||||||
ordered
|
ordered
|
||||||
|> Seq.map (fun cat ->
|
|> Seq.map (fun cat ->
|
||||||
{ cat with
|
{ cat with
|
||||||
PostCount =
|
PostCount = defaultArg (counts |> Array.tryFind (fun c -> fst c = cat.Id) |> Option.map snd) 0
|
||||||
counts
|
})
|
||||||
|> Array.tryFind (fun c -> fst c = cat.Id)
|
|
||||||
|> Option.map snd
|
|
||||||
|> Option.defaultValue 0 })
|
|
||||||
|> Array.ofSeq
|
|> Array.ofSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a category by its ID for the given web log
|
/// Find a category by its ID for the given web log
|
||||||
let findById catId webLogId =
|
let findById catId webLogId =
|
||||||
log.LogTrace "Category.findById"
|
log.LogTrace "Category.findById"
|
||||||
Document.findByIdAndWebLog<CategoryId, Category> conn ser Table.Category catId webLogId
|
Document.findByIdAndWebLog<CategoryId, Category> Table.Category catId webLogId conn
|
||||||
|
|
||||||
/// Delete a category
|
/// Delete a category
|
||||||
let delete catId webLogId = backgroundTask {
|
let delete catId webLogId = backgroundTask {
|
||||||
log.LogTrace "Category.delete"
|
log.LogTrace "Category.delete"
|
||||||
match! findById catId webLogId with
|
match! findById catId webLogId with
|
||||||
| Some cat ->
|
| Some cat ->
|
||||||
use cmd = conn.CreateCommand()
|
|
||||||
// Reassign any children to the category's parent category
|
// Reassign any children to the category's parent category
|
||||||
cmd.CommandText <- $"SELECT COUNT(*) FROM {Table.Category} WHERE data ->> '{parentIdField}' = @parentId"
|
let! children = Count.byFieldEquals Table.Category parentIdField catId conn
|
||||||
addParam cmd "@parentId" (string catId)
|
|
||||||
let! children = count cmd
|
|
||||||
if children > 0 then
|
if children > 0 then
|
||||||
cmd.CommandText <- $"
|
do! Update.partialByFieldEquals Table.Category parentIdField catId {| ParentId = cat.ParentId |} conn
|
||||||
UPDATE {Table.Category}
|
|
||||||
SET data = json_set(data, '$.{parentIdField}', @newParentId)
|
|
||||||
WHERE data ->> '{parentIdField}' = @parentId"
|
|
||||||
addParam cmd "@newParentId" (maybe (cat.ParentId |> Option.map string))
|
|
||||||
do! write cmd
|
|
||||||
// Delete the category off all posts where it is assigned, and the category itself
|
// Delete the category off all posts where it is assigned, and the category itself
|
||||||
let catIdField = Post.Empty.CategoryIds
|
let catIdField = Post.Empty.CategoryIds
|
||||||
cmd.CommandText <- $"
|
let! posts =
|
||||||
SELECT data ->> '{Post.Empty.Id}' AS id, data -> '{catIdField}' AS cat_ids
|
Custom.list
|
||||||
FROM {Table.Post}
|
$"SELECT data ->> '{Post.Empty.Id}', data -> '{catIdField}'
|
||||||
WHERE {Query.whereByWebLog}
|
FROM {Table.Post}
|
||||||
AND EXISTS
|
WHERE {Document.Query.whereByWebLog}
|
||||||
(SELECT 1 FROM json_each({Table.Post}.data -> '{catIdField}') WHERE json_each.value = @id)"
|
AND EXISTS
|
||||||
cmd.Parameters.Clear()
|
(SELECT 1
|
||||||
addDocId cmd catId
|
FROM json_each({Table.Post}.data -> '{catIdField}')
|
||||||
addWebLogId cmd webLogId
|
WHERE json_each.value = @id)"
|
||||||
use! postRdr = cmd.ExecuteReaderAsync()
|
[ idParam catId; webLogParam webLogId ]
|
||||||
if postRdr.HasRows then
|
(fun rdr -> rdr.GetString(0), Utils.deserialize<string list> ser (rdr.GetString(1)))
|
||||||
let postIdAndCats =
|
conn
|
||||||
toList
|
for postId, cats in posts do
|
||||||
(fun rdr ->
|
do! Update.partialById
|
||||||
Map.getString "id" rdr, Utils.deserialize<string list> ser (Map.getString "cat_ids" rdr))
|
Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |} conn
|
||||||
postRdr
|
|
||||||
do! postRdr.CloseAsync()
|
|
||||||
for postId, cats in postIdAndCats do
|
|
||||||
cmd.CommandText <- $"
|
|
||||||
UPDATE {Table.Post}
|
|
||||||
SET data = json_set(data, '$.{catIdField}', json(@catIds))
|
|
||||||
WHERE {Query.whereById}"
|
|
||||||
cmd.Parameters.Clear()
|
|
||||||
addDocId cmd postId
|
|
||||||
addParam cmd "@catIds" (cats |> List.filter (fun it -> it <> string catId) |> Utils.serialize ser)
|
|
||||||
do! write cmd
|
|
||||||
do! Document.delete conn Table.Category catId
|
do! Document.delete conn Table.Category catId
|
||||||
return if children = 0 then CategoryDeleted else ReassignedChildCategories
|
return if children = 0L then CategoryDeleted else ReassignedChildCategories
|
||||||
| None -> return CategoryNotFound
|
| None -> return CategoryNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save a category
|
||||||
|
let save cat =
|
||||||
|
log.LogTrace "Category.save"
|
||||||
|
save<Category> Table.Category cat conn
|
||||||
|
|
||||||
/// Restore categories from a backup
|
/// Restore categories from a backup
|
||||||
let restore cats = backgroundTask {
|
let restore cats = backgroundTask {
|
||||||
for cat in cats do
|
log.LogTrace "Category.restore"
|
||||||
do! add cat
|
for cat in cats do do! save cat
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a category
|
|
||||||
let update (cat: Category) = backgroundTask {
|
|
||||||
use cmd = conn.CreateCommand()
|
|
||||||
cmd.CommandText <- $"{Query.updateById} AND {Query.whereByWebLog}"
|
|
||||||
addDocId cmd cat.Id
|
|
||||||
addDocParam cmd cat ser
|
|
||||||
addWebLogId cmd cat.WebLogId
|
|
||||||
do! write cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICategoryData with
|
interface ICategoryData with
|
||||||
member _.Add cat = add cat
|
member _.Add cat = save cat
|
||||||
member _.CountAll webLogId = countAll webLogId
|
member _.CountAll webLogId = countAll webLogId
|
||||||
member _.CountTopLevel webLogId = countTopLevel webLogId
|
member _.CountTopLevel webLogId = countTopLevel webLogId
|
||||||
member _.FindAllForView webLogId = findAllForView webLogId
|
member _.FindAllForView webLogId = findAllForView webLogId
|
||||||
@ -158,4 +125,4 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||||||
member _.FindByWebLog webLogId = findByWebLog webLogId
|
member _.FindByWebLog webLogId = findByWebLog webLogId
|
||||||
member _.Delete catId webLogId = delete catId webLogId
|
member _.Delete catId webLogId = delete catId webLogId
|
||||||
member _.Restore cats = restore cats
|
member _.Restore cats = restore cats
|
||||||
member _.Update cat = update cat
|
member _.Update cat = save cat
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
namespace MyWebLog.Data.SQLite
|
namespace MyWebLog.Data.SQLite
|
||||||
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open MyWebLog
|
open MyWebLog
|
||||||
open MyWebLog.Data
|
open MyWebLog.Data
|
||||||
open Newtonsoft.Json
|
|
||||||
|
|
||||||
/// SQLite myWebLog page data implementation
|
/// SQLite myWebLog page data implementation
|
||||||
type SQLitePageData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
||||||
|
|
||||||
/// The JSON field for the permalink
|
/// The JSON field name for the permalink
|
||||||
let linkField = $"data ->> '{nameof Page.Empty.Permalink}'"
|
let linkName = nameof Page.Empty.Permalink
|
||||||
|
|
||||||
/// The JSON field for the "is in page list" flag
|
/// The JSON field name for the "is in page list" flag
|
||||||
let pgListField = $"data ->> '{nameof Page.Empty.IsInPageList}'"
|
let pgListName = nameof Page.Empty.IsInPageList
|
||||||
|
|
||||||
/// The JSON field for the title of the page
|
/// The JSON field for the title of the page
|
||||||
let titleField = $"data ->> '{nameof Page.Empty.Title}'"
|
let titleField = $"data ->> '{nameof Page.Empty.Title}'"
|
||||||
@ -24,57 +25,44 @@ type SQLitePageData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
/// Append revisions to a page
|
/// Append revisions to a page
|
||||||
let appendPageRevisions (page : Page) = backgroundTask {
|
let appendPageRevisions (page : Page) = backgroundTask {
|
||||||
log.LogTrace "Page.appendPageRevisions"
|
log.LogTrace "Page.appendPageRevisions"
|
||||||
let! revisions = Revisions.findByEntityId conn Table.PageRevision Table.Page page.Id
|
let! revisions = Revisions.findByEntityId Table.PageRevision Table.Page page.Id conn
|
||||||
return { page with Revisions = revisions }
|
return { page with Revisions = revisions }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a page with no text
|
|
||||||
let withoutText (page: Page) =
|
|
||||||
{ page with Text = "" }
|
|
||||||
|
|
||||||
/// Update a page's revisions
|
/// Update a page's revisions
|
||||||
let updatePageRevisions (pageId: PageId) oldRevs newRevs =
|
let updatePageRevisions (pageId: PageId) oldRevs newRevs =
|
||||||
log.LogTrace "Page.updatePageRevisions"
|
log.LogTrace "Page.updatePageRevisions"
|
||||||
Revisions.update conn Table.PageRevision Table.Page pageId oldRevs newRevs
|
Revisions.update Table.PageRevision Table.Page pageId oldRevs newRevs conn
|
||||||
|
|
||||||
// IMPLEMENTATION FUNCTIONS
|
// IMPLEMENTATION FUNCTIONS
|
||||||
|
|
||||||
/// Add a page
|
|
||||||
let add page = backgroundTask {
|
|
||||||
log.LogTrace "Page.add"
|
|
||||||
do! Document.insert<Page> conn ser Table.Page { page with Revisions = [] }
|
|
||||||
do! updatePageRevisions page.Id [] page.Revisions
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all pages for a web log (without text or revisions)
|
/// Get all pages for a web log (without text or revisions)
|
||||||
let all webLogId = backgroundTask {
|
let all webLogId =
|
||||||
log.LogTrace "Page.all"
|
log.LogTrace "Page.all"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <-
|
$"{Query.selectFromTable Table.Page} WHERE {Document.Query.whereByWebLog} ORDER BY LOWER({titleField})"
|
||||||
$"{Query.selectFromTable Table.Page} WHERE {Query.whereByWebLog} ORDER BY LOWER({titleField})"
|
[ webLogParam webLogId ]
|
||||||
addWebLogId cmd webLogId
|
(fun rdr -> { fromData<Page> rdr with Text = "" })
|
||||||
let! pages = cmdToList<Page> cmd ser
|
conn
|
||||||
return pages |> List.map withoutText
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count all pages for the given web log
|
/// Count all pages for the given web log
|
||||||
let countAll webLogId =
|
let countAll webLogId =
|
||||||
log.LogTrace "Page.countAll"
|
log.LogTrace "Page.countAll"
|
||||||
Document.countByWebLog conn Table.Page webLogId
|
Document.countByWebLog Table.Page webLogId conn
|
||||||
|
|
||||||
/// Count all pages shown in the page list for the given web log
|
/// Count all pages shown in the page list for the given web log
|
||||||
let countListed webLogId = backgroundTask {
|
let countListed webLogId =
|
||||||
log.LogTrace "Page.countListed"
|
log.LogTrace "Page.countListed"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.scalar
|
||||||
cmd.CommandText <- $"{Query.countByWebLog} AND {pgListField} = 'true'"
|
$"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals pgListName "'true'"}"""
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId ]
|
||||||
return! count cmd
|
(fun rdr -> int (rdr.GetInt64(0)))
|
||||||
}
|
conn
|
||||||
|
|
||||||
/// Find a page by its ID (without revisions)
|
/// Find a page by its ID (without revisions)
|
||||||
let findById pageId webLogId =
|
let findById pageId webLogId =
|
||||||
log.LogTrace "Page.findById"
|
log.LogTrace "Page.findById"
|
||||||
Document.findByIdAndWebLog<PageId, Page> conn ser Table.Page pageId webLogId
|
Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn
|
||||||
|
|
||||||
/// Find a complete page by its ID
|
/// Find a complete page by its ID
|
||||||
let findFullById pageId webLogId = backgroundTask {
|
let findFullById pageId webLogId = backgroundTask {
|
||||||
@ -92,93 +80,74 @@ type SQLitePageData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
log.LogTrace "Page.delete"
|
log.LogTrace "Page.delete"
|
||||||
match! findById pageId webLogId with
|
match! findById pageId webLogId with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
use cmd = conn.CreateCommand()
|
do! Custom.nonQuery
|
||||||
cmd.CommandText <- $"DELETE FROM {Table.PageRevision} WHERE page_id = @id; {Query.deleteById}"
|
$"DELETE FROM {Table.PageRevision} WHERE page_id = @id; {Query.Delete.byId Table.Page}"
|
||||||
addDocId cmd pageId
|
[ idParam pageId ]
|
||||||
do! write cmd
|
conn
|
||||||
return true
|
return true
|
||||||
| None -> return false
|
| None -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a page by its permalink for the given web log
|
/// Find a page by its permalink for the given web log
|
||||||
let findByPermalink (permalink: Permalink) webLogId = backgroundTask {
|
let findByPermalink (permalink: Permalink) webLogId =
|
||||||
log.LogTrace "Page.findByPermalink"
|
log.LogTrace "Page.findByPermalink"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.single
|
||||||
cmd.CommandText <- $" {Query.selectFromTable Table.Page} WHERE {Query.whereByWebLog} AND {linkField} = @link"
|
$"""{Document.Query.selectByWebLog} AND {Query.whereFieldEquals linkName "@link"}"""
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId; SqliteParameter("@link", string permalink) ]
|
||||||
addParam cmd "@link" (string permalink)
|
fromData<Page>
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
conn
|
||||||
let! isFound = rdr.ReadAsync()
|
|
||||||
return if isFound then Some (Map.fromDoc<Page> ser rdr) else None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the current permalink within a set of potential prior permalinks for the given web log
|
/// Find the current permalink within a set of potential prior permalinks for the given web log
|
||||||
let findCurrentPermalink (permalinks: Permalink list) webLogId = backgroundTask {
|
let findCurrentPermalink (permalinks: Permalink list) webLogId =
|
||||||
log.LogTrace "Page.findCurrentPermalink"
|
log.LogTrace "Page.findCurrentPermalink"
|
||||||
let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
|
let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
|
||||||
use cmd = conn.CreateCommand()
|
Custom.single
|
||||||
cmd.CommandText <-
|
$"SELECT data ->> '{linkName}' AS permalink
|
||||||
$"SELECT {linkField} AS permalink FROM {Table.Page} WHERE {Query.whereByWebLog} AND {linkSql}"
|
FROM {Table.Page}
|
||||||
addWebLogId cmd webLogId
|
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
|
||||||
cmd.Parameters.AddRange linkParams
|
(webLogParam webLogId :: linkParams)
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
Map.toPermalink
|
||||||
let! isFound = rdr.ReadAsync()
|
conn
|
||||||
return if isFound then Some (Map.toPermalink rdr) else None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all complete pages for the given web log
|
/// Get all complete pages for the given web log
|
||||||
let findFullByWebLog webLogId = backgroundTask {
|
let findFullByWebLog webLogId = backgroundTask {
|
||||||
log.LogTrace "Page.findFullByWebLog"
|
log.LogTrace "Page.findFullByWebLog"
|
||||||
let! pages = Document.findByWebLog<Page> conn ser Table.Page webLogId
|
let! pages = Document.findByWebLog<Page> Table.Page webLogId conn
|
||||||
let! withRevs =
|
let! withRevs = pages |> List.map appendPageRevisions |> Task.WhenAll
|
||||||
pages
|
|
||||||
|> List.map (fun page -> backgroundTask { return! appendPageRevisions page })
|
|
||||||
|> Task.WhenAll
|
|
||||||
return List.ofArray withRevs
|
return List.ofArray withRevs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all listed pages for the given web log (without revisions or text)
|
/// Get all listed pages for the given web log (without revisions or text)
|
||||||
let findListed webLogId = backgroundTask {
|
let findListed webLogId =
|
||||||
log.LogTrace "Page.findListed"
|
log.LogTrace "Page.findListed"
|
||||||
use cmd = conn.CreateCommand ()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereFieldEquals pgListName "'true'"}
|
||||||
{Query.selectFromTable Table.Page}
|
ORDER BY LOWER({titleField})"""
|
||||||
WHERE {Query.whereByWebLog}
|
[ webLogParam webLogId ]
|
||||||
AND {pgListField} = 'true'
|
(fun rdr -> { fromData<Page> rdr with Text = "" })
|
||||||
ORDER BY LOWER({titleField})"
|
conn
|
||||||
addWebLogId cmd webLogId
|
|
||||||
let! pages = cmdToList<Page> cmd ser
|
|
||||||
return pages |> List.map withoutText
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a page of pages for the given web log (without revisions)
|
/// Get a page of pages for the given web log (without revisions)
|
||||||
let findPageOfPages webLogId pageNbr =
|
let findPageOfPages webLogId pageNbr =
|
||||||
log.LogTrace "Page.findPageOfPages"
|
log.LogTrace "Page.findPageOfPages"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"{Document.Query.selectByWebLog Table.Page} ORDER BY LOWER({titleField}) LIMIT @pageSize OFFSET @toSkip"
|
||||||
{Query.selectFromTable Table.Page} WHERE {Query.whereByWebLog}
|
[ webLogParam webLogId; SqliteParameter("@pageSize", 26); SqliteParameter("@toSkip", (pageNbr - 1) * 25) ]
|
||||||
ORDER BY LOWER({titleField})
|
fromData<Page>
|
||||||
LIMIT @pageSize OFFSET @toSkip"
|
conn
|
||||||
addWebLogId cmd webLogId
|
|
||||||
addParam cmd "@pageSize" 26
|
/// Save a page
|
||||||
addParam cmd "@toSkip" ((pageNbr - 1) * 25)
|
let save (page: Page) = backgroundTask {
|
||||||
cmdToList<Page> cmd ser
|
log.LogTrace "Page.update"
|
||||||
|
let! oldPage = findFullById page.Id page.WebLogId
|
||||||
|
do! save Table.Page { page with Revisions = [] } conn
|
||||||
|
do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions
|
||||||
|
}
|
||||||
|
|
||||||
/// Restore pages from a backup
|
/// Restore pages from a backup
|
||||||
let restore pages = backgroundTask {
|
let restore pages = backgroundTask {
|
||||||
log.LogTrace "Page.restore"
|
log.LogTrace "Page.restore"
|
||||||
for page in pages do
|
for page in pages do do! save page
|
||||||
do! add page
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a page
|
|
||||||
let update (page: Page) = backgroundTask {
|
|
||||||
log.LogTrace "Page.update"
|
|
||||||
match! findFullById page.Id page.WebLogId with
|
|
||||||
| Some oldPage ->
|
|
||||||
do! Document.update conn ser Table.Page page.Id { page with Revisions = [] }
|
|
||||||
do! updatePageRevisions page.Id oldPage.Revisions page.Revisions
|
|
||||||
| None -> ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a page's prior permalinks
|
/// Update a page's prior permalinks
|
||||||
@ -186,13 +155,13 @@ type SQLitePageData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
log.LogTrace "Page.updatePriorPermalinks"
|
log.LogTrace "Page.updatePriorPermalinks"
|
||||||
match! findById pageId webLogId with
|
match! findById pageId webLogId with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
do! Document.updateField conn ser Table.Page pageId (nameof Page.Empty.PriorPermalinks) permalinks
|
do! Update.partialById Table.Page pageId {| PriorPermalinks = permalinks |} conn
|
||||||
return true
|
return true
|
||||||
| None -> return false
|
| None -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPageData with
|
interface IPageData with
|
||||||
member _.Add page = add page
|
member _.Add page = save page
|
||||||
member _.All webLogId = all webLogId
|
member _.All webLogId = all webLogId
|
||||||
member _.CountAll webLogId = countAll webLogId
|
member _.CountAll webLogId = countAll webLogId
|
||||||
member _.CountListed webLogId = countListed webLogId
|
member _.CountListed webLogId = countListed webLogId
|
||||||
@ -205,5 +174,5 @@ type SQLitePageData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
member _.FindListed webLogId = findListed webLogId
|
member _.FindListed webLogId = findListed webLogId
|
||||||
member _.FindPageOfPages webLogId pageNbr = findPageOfPages webLogId pageNbr
|
member _.FindPageOfPages webLogId pageNbr = findPageOfPages webLogId pageNbr
|
||||||
member _.Restore pages = restore pages
|
member _.Restore pages = restore pages
|
||||||
member _.Update page = update page
|
member _.Update page = save page
|
||||||
member _.UpdatePriorPermalinks pageId webLogId permalinks = updatePriorPermalinks pageId webLogId permalinks
|
member _.UpdatePriorPermalinks pageId webLogId permalinks = updatePriorPermalinks pageId webLogId permalinks
|
||||||
|
@ -1,84 +1,70 @@
|
|||||||
namespace MyWebLog.Data.SQLite
|
namespace MyWebLog.Data.SQLite
|
||||||
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open MyWebLog
|
open MyWebLog
|
||||||
open MyWebLog.Data
|
open MyWebLog.Data
|
||||||
open Newtonsoft.Json
|
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
/// SQLite myWebLog post data implementation
|
/// SQLite myWebLog post data implementation
|
||||||
type SQLitePostData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
||||||
|
|
||||||
/// The JSON field for the post's permalink
|
/// The name of the JSON field for the post's permalink
|
||||||
let linkField = $"data ->> '{nameof Post.Empty.Permalink}'"
|
let linkName = nameof Post.Empty.Permalink
|
||||||
|
|
||||||
/// The JSON field for when the post was published
|
/// The JSON field for when the post was published
|
||||||
let publishField = $"data ->> '{nameof Post.Empty.PublishedOn}'"
|
let publishField = $"data ->> '{nameof Post.Empty.PublishedOn}'"
|
||||||
|
|
||||||
/// The JSON field for post status
|
/// The name of the JSON field for the post's status
|
||||||
let statField = $"data ->> '{nameof Post.Empty.Status}'"
|
let statName = nameof Post.Empty.Status
|
||||||
|
|
||||||
// SUPPORT FUNCTIONS
|
// SUPPORT FUNCTIONS
|
||||||
|
|
||||||
/// Append revisions to a post
|
/// Append revisions to a post
|
||||||
let appendPostRevisions (post: Post) = backgroundTask {
|
let appendPostRevisions (post: Post) = backgroundTask {
|
||||||
log.LogTrace "Post.appendPostRevisions"
|
log.LogTrace "Post.appendPostRevisions"
|
||||||
let! revisions = Revisions.findByEntityId conn Table.PostRevision Table.Post post.Id
|
let! revisions = Revisions.findByEntityId Table.PostRevision Table.Post post.Id conn
|
||||||
return { post with Revisions = revisions }
|
return { post with Revisions = revisions }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The SELECT statement to retrieve posts with a web log ID parameter
|
/// The SELECT statement to retrieve posts with a web log ID parameter
|
||||||
let postByWebLog = $"{Query.selectFromTable Table.Post} WHERE {Query.whereByWebLog}"
|
let postByWebLog = Document.Query.selectByWebLog Table.Post
|
||||||
|
|
||||||
/// The SELECT statement to retrieve published posts with a web log ID parameter
|
/// The SELECT statement to retrieve published posts with a web log ID parameter
|
||||||
let publishedPostByWebLog = $"{postByWebLog} AND {statField} = '{string Published}'"
|
let publishedPostByWebLog = $"""{postByWebLog} AND {Query.whereFieldEquals statName $"'{string Published}'"}"""
|
||||||
|
|
||||||
/// Remove the text from a post
|
|
||||||
let withoutText (post: Post) =
|
|
||||||
{ post with Text = "" }
|
|
||||||
|
|
||||||
/// Update a post's revisions
|
/// Update a post's revisions
|
||||||
let updatePostRevisions (postId: PostId) oldRevs newRevs =
|
let updatePostRevisions (postId: PostId) oldRevs newRevs =
|
||||||
log.LogTrace "Post.updatePostRevisions"
|
log.LogTrace "Post.updatePostRevisions"
|
||||||
Revisions.update conn Table.PostRevision Table.Post postId oldRevs newRevs
|
Revisions.update Table.PostRevision Table.Post postId oldRevs newRevs conn
|
||||||
|
|
||||||
// IMPLEMENTATION FUNCTIONS
|
// IMPLEMENTATION FUNCTIONS
|
||||||
|
|
||||||
/// Add a post
|
|
||||||
let add (post: Post) = backgroundTask {
|
|
||||||
log.LogTrace "Post.add"
|
|
||||||
do! Document.insert conn ser Table.Post { post with Revisions = [] }
|
|
||||||
do! updatePostRevisions post.Id [] post.Revisions
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count posts in a status for the given web log
|
/// Count posts in a status for the given web log
|
||||||
let countByStatus (status: PostStatus) webLogId = backgroundTask {
|
let countByStatus (status: PostStatus) webLogId =
|
||||||
log.LogTrace "Post.countByStatus"
|
log.LogTrace "Post.countByStatus"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.scalar
|
||||||
cmd.CommandText <- $"{Query.countByWebLog Table.Post} AND {statField} = @status"
|
$"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals statName "@status"}"""
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId; SqliteParameter("@status", string status) ]
|
||||||
addParam cmd "@status" (string status)
|
(fun rdr -> int (rdr.GetInt64(0)))
|
||||||
return! count cmd
|
conn
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a post by its ID for the given web log (excluding revisions and prior permalinks
|
/// Find a post by its ID for the given web log (excluding revisions)
|
||||||
let findById postId webLogId =
|
let findById postId webLogId =
|
||||||
log.LogTrace "Post.findById"
|
log.LogTrace "Post.findById"
|
||||||
Document.findByIdAndWebLog<PostId, Post> conn ser Table.Post postId webLogId
|
Document.findByIdAndWebLog<PostId, Post> Table.Post postId webLogId conn
|
||||||
|
|
||||||
/// Find a post by its permalink for the given web log (excluding revisions and prior permalinks)
|
/// Find a post by its permalink for the given web log (excluding revisions)
|
||||||
let findByPermalink (permalink: Permalink) webLogId = backgroundTask {
|
let findByPermalink (permalink: Permalink) webLogId =
|
||||||
log.LogTrace "Post.findByPermalink"
|
log.LogTrace "Post.findByPermalink"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.single
|
||||||
cmd.CommandText <- $"{Query.selectFromTable Table.Post} WHERE {Query.whereByWebLog} AND {linkField} = @link"
|
$"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereFieldEquals linkName "@link"}"""
|
||||||
addWebLogId cmd webLogId
|
[ webLogParam webLogId; SqliteParameter("@link", string permalink) ]
|
||||||
addParam cmd "@link" (string permalink)
|
fromData<Post>
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
conn
|
||||||
let! isFound = rdr.ReadAsync()
|
|
||||||
return if isFound then Some (Map.fromDoc<Post> ser rdr) else None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find a complete post by its ID for the given web log
|
/// Find a complete post by its ID for the given web log
|
||||||
let findFullById postId webLogId = backgroundTask {
|
let findFullById postId webLogId = backgroundTask {
|
||||||
@ -95,39 +81,34 @@ type SQLitePostData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
log.LogTrace "Post.delete"
|
log.LogTrace "Post.delete"
|
||||||
match! findById postId webLogId with
|
match! findById postId webLogId with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
use cmd = conn.CreateCommand()
|
do! Custom.nonQuery
|
||||||
cmd.CommandText <- $"
|
$"""DELETE FROM {Table.PostRevision} WHERE post_id = @id;
|
||||||
DELETE FROM {Table.PostRevision} WHERE post_id = @id;
|
DELETE FROM {Table.PostComment}
|
||||||
DELETE FROM {Table.PostComment} WHERE data ->> '{nameof Comment.Empty.PostId}' = @id;
|
WHERE {Query.whereFieldEquals (nameof Comment.Empty.PostId) "@id"};
|
||||||
DELETE FROM {Table.Post} WHERE {Query.whereById}"
|
{Query.Delete.byId Table.Post}"""
|
||||||
addDocId cmd postId
|
[ idParam postId ]
|
||||||
do! write cmd
|
conn
|
||||||
return true
|
return true
|
||||||
| None -> return false
|
| None -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the current permalink from a list of potential prior permalinks for the given web log
|
/// Find the current permalink from a list of potential prior permalinks for the given web log
|
||||||
let findCurrentPermalink (permalinks: Permalink list) webLogId = backgroundTask {
|
let findCurrentPermalink (permalinks: Permalink list) webLogId =
|
||||||
log.LogTrace "Post.findCurrentPermalink"
|
log.LogTrace "Post.findCurrentPermalink"
|
||||||
let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
|
let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
|
||||||
use cmd = conn.CreateCommand()
|
Custom.single
|
||||||
cmd.CommandText <-
|
$"SELECT data ->> '{linkName}'
|
||||||
$"SELECT {linkField} AS permalink FROM {Table.Post} WHERE {Query.whereByWebLog} AND {linkSql}"
|
FROM {Table.Post}
|
||||||
addWebLogId cmd webLogId
|
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
|
||||||
cmd.Parameters.AddRange linkParams
|
(webLogParam webLogId :: linkParams)
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
Map.toPermalink
|
||||||
let! isFound = rdr.ReadAsync()
|
conn
|
||||||
return if isFound then Some (Map.toPermalink rdr) else None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all complete posts for the given web log
|
/// Get all complete posts for the given web log
|
||||||
let findFullByWebLog webLogId = backgroundTask {
|
let findFullByWebLog webLogId = backgroundTask {
|
||||||
log.LogTrace "Post.findFullByWebLog"
|
log.LogTrace "Post.findFullByWebLog"
|
||||||
let! posts = Document.findByWebLog<Post> conn ser Table.Post webLogId
|
let! posts = Document.findByWebLog<Post> Table.Post webLogId conn
|
||||||
let! withRevs =
|
let! withRevs = posts |> List.map appendPostRevisions |> Task.WhenAll
|
||||||
posts
|
|
||||||
|> List.map (fun post -> backgroundTask { return! appendPostRevisions post })
|
|
||||||
|> Task.WhenAll
|
|
||||||
return List.ofArray withRevs
|
return List.ofArray withRevs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,102 +116,91 @@ type SQLitePostData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
let findPageOfCategorizedPosts webLogId (categoryIds: CategoryId list) pageNbr postsPerPage =
|
let findPageOfCategorizedPosts webLogId (categoryIds: CategoryId list) pageNbr postsPerPage =
|
||||||
log.LogTrace "Post.findPageOfCategorizedPosts"
|
log.LogTrace "Post.findPageOfCategorizedPosts"
|
||||||
let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds
|
let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds
|
||||||
use cmd = conn.CreateCommand ()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"{publishedPostByWebLog} AND {catSql}
|
||||||
{publishedPostByWebLog} AND {catSql}
|
ORDER BY {publishField} DESC
|
||||||
ORDER BY {publishField} DESC
|
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
(webLogParam webLogId :: catParams)
|
||||||
addWebLogId cmd webLogId
|
fromData<Post>
|
||||||
cmd.Parameters.AddRange catParams
|
conn
|
||||||
cmdToList<Post> cmd ser
|
|
||||||
|
|
||||||
/// Get a page of posts for the given web log (excludes revisions)
|
/// Get a page of posts for the given web log (excludes text and revisions)
|
||||||
let findPageOfPosts webLogId pageNbr postsPerPage = backgroundTask {
|
let findPageOfPosts webLogId pageNbr postsPerPage =
|
||||||
log.LogTrace "Post.findPageOfPosts"
|
log.LogTrace "Post.findPageOfPosts"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"{postByWebLog}
|
||||||
{postByWebLog}
|
ORDER BY {publishField} DESC NULLS FIRST, data ->> '{nameof Post.Empty.UpdatedOn}'
|
||||||
ORDER BY {publishField} DESC NULLS FIRST, data ->> '{nameof Post.Empty.UpdatedOn}'
|
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
[ webLogParam webLogId ]
|
||||||
addWebLogId cmd webLogId
|
(fun rdr -> { fromData<Post> rdr with Text = "" })
|
||||||
let! posts = cmdToList<Post> cmd ser
|
conn
|
||||||
return posts |> List.map withoutText
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a page of published posts for the given web log (excludes revisions)
|
/// Get a page of published posts for the given web log (excludes revisions)
|
||||||
let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
|
let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
|
||||||
log.LogTrace "Post.findPageOfPublishedPosts"
|
log.LogTrace "Post.findPageOfPublishedPosts"
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"{publishedPostByWebLog}
|
||||||
{publishedPostByWebLog}
|
ORDER BY {publishField} DESC
|
||||||
ORDER BY {publishField} DESC
|
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
[ webLogParam webLogId ]
|
||||||
addWebLogId cmd webLogId
|
fromData<Post>
|
||||||
cmdToList<Post> cmd ser
|
conn
|
||||||
|
|
||||||
/// Get a page of tagged posts for the given web log (excludes revisions)
|
/// Get a page of tagged posts for the given web log (excludes revisions)
|
||||||
let findPageOfTaggedPosts webLogId (tag : string) pageNbr postsPerPage =
|
let findPageOfTaggedPosts webLogId (tag : string) pageNbr postsPerPage =
|
||||||
log.LogTrace "Post.findPageOfTaggedPosts"
|
log.LogTrace "Post.findPageOfTaggedPosts"
|
||||||
let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ]
|
let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ]
|
||||||
use cmd = conn.CreateCommand()
|
Custom.list
|
||||||
cmd.CommandText <- $"
|
$"{publishedPostByWebLog} AND {tagSql}
|
||||||
{publishedPostByWebLog} AND {tagSql}
|
ORDER BY p.published_on DESC
|
||||||
ORDER BY p.published_on DESC
|
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
(webLogParam webLogId :: tagParams)
|
||||||
addWebLogId cmd webLogId
|
fromData<Post>
|
||||||
cmd.Parameters.AddRange tagParams
|
conn
|
||||||
cmdToList<Post> cmd ser
|
|
||||||
|
|
||||||
/// Find the next newest and oldest post from a publish date for the given web log
|
/// Find the next newest and oldest post from a publish date for the given web log
|
||||||
let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask {
|
let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask {
|
||||||
log.LogTrace "Post.findSurroundingPosts"
|
log.LogTrace "Post.findSurroundingPosts"
|
||||||
use cmd = conn.CreateCommand ()
|
let! older =
|
||||||
addWebLogId cmd webLogId
|
Custom.single
|
||||||
addParam cmd "@publishedOn" (instantParam publishedOn)
|
$"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
|
||||||
|
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
|
||||||
cmd.CommandText <-
|
fromData<Post>
|
||||||
$"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
|
conn
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
let! newer =
|
||||||
let! isFound = rdr.ReadAsync()
|
Custom.single
|
||||||
let older = if isFound then Some (Map.fromDoc<Post> ser rdr) else None
|
$"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
|
||||||
do! rdr.CloseAsync ()
|
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
|
||||||
|
fromData<Post>
|
||||||
cmd.CommandText <-
|
conn
|
||||||
$"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
|
|
||||||
use! rdr = cmd.ExecuteReaderAsync ()
|
|
||||||
let! isFound = rdr.ReadAsync()
|
|
||||||
let newer = if isFound then Some (Map.fromDoc<Post> ser rdr) else None
|
|
||||||
|
|
||||||
return older, newer
|
return older, newer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save a post
|
||||||
|
let save (post: Post) = backgroundTask {
|
||||||
|
log.LogTrace "Post.save"
|
||||||
|
let! oldPost = findFullById post.Id post.WebLogId
|
||||||
|
do! save Table.Post { post with Revisions = [] } conn
|
||||||
|
do! updatePostRevisions post.Id (match oldPost with Some p -> p.Revisions | None -> []) post.Revisions
|
||||||
|
}
|
||||||
|
|
||||||
/// Restore posts from a backup
|
/// Restore posts from a backup
|
||||||
let restore posts = backgroundTask {
|
let restore posts = backgroundTask {
|
||||||
log.LogTrace "Post.restore"
|
log.LogTrace "Post.restore"
|
||||||
for post in posts do
|
for post in posts do do! save post
|
||||||
do! add post
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a post
|
|
||||||
let update (post: Post) = backgroundTask {
|
|
||||||
match! findFullById post.Id post.WebLogId with
|
|
||||||
| Some oldPost ->
|
|
||||||
do! Document.update conn ser Table.Post post.Id { post with Revisions = [] }
|
|
||||||
do! updatePostRevisions post.Id oldPost.Revisions post.Revisions
|
|
||||||
| None -> return ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update prior permalinks for a post
|
/// Update prior permalinks for a post
|
||||||
let updatePriorPermalinks postId webLogId (permalinks: Permalink list) = backgroundTask {
|
let updatePriorPermalinks postId webLogId (permalinks: Permalink list) = backgroundTask {
|
||||||
match! findById postId webLogId with
|
match! findById postId webLogId with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
do! Document.updateField conn ser Table.Post postId (nameof Post.Empty.PriorPermalinks) permalinks
|
do! Update.partialById Table.Post postId {| PriorPermalinks = permalinks |} conn
|
||||||
return true
|
return true
|
||||||
| None -> return false
|
| None -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPostData with
|
interface IPostData with
|
||||||
member _.Add post = add post
|
member _.Add post = save post
|
||||||
member _.CountByStatus status webLogId = countByStatus status webLogId
|
member _.CountByStatus status webLogId = countByStatus status webLogId
|
||||||
member _.Delete postId webLogId = delete postId webLogId
|
member _.Delete postId webLogId = delete postId webLogId
|
||||||
member _.FindById postId webLogId = findById postId webLogId
|
member _.FindById postId webLogId = findById postId webLogId
|
||||||
@ -247,5 +217,5 @@ type SQLitePostData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) =
|
|||||||
findPageOfTaggedPosts webLogId tag pageNbr postsPerPage
|
findPageOfTaggedPosts webLogId tag pageNbr postsPerPage
|
||||||
member _.FindSurroundingPosts webLogId publishedOn = findSurroundingPosts webLogId publishedOn
|
member _.FindSurroundingPosts webLogId publishedOn = findSurroundingPosts webLogId publishedOn
|
||||||
member _.Restore posts = restore posts
|
member _.Restore posts = restore posts
|
||||||
member _.Update post = update post
|
member _.Update post = save post
|
||||||
member _.UpdatePriorPermalinks postId webLogId permalinks = updatePriorPermalinks postId webLogId permalinks
|
member _.UpdatePriorPermalinks postId webLogId permalinks = updatePriorPermalinks postId webLogId permalinks
|
||||||
|
@ -29,8 +29,8 @@ type SQLiteTagMapData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
log.LogTrace "TagMap.findByUrlValue"
|
log.LogTrace "TagMap.findByUrlValue"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- $"
|
cmd.CommandText <- $"
|
||||||
{Query.selectFromTable Table.TagMap}
|
{QueryOld.selectFromTable Table.TagMap}
|
||||||
WHERE {Query.whereByWebLog}
|
WHERE {QueryOld.whereByWebLog}
|
||||||
AND data ->> '{nameof TagMap.Empty.UrlValue}' = @urlValue"
|
AND data ->> '{nameof TagMap.Empty.UrlValue}' = @urlValue"
|
||||||
addWebLogId cmd webLogId
|
addWebLogId cmd webLogId
|
||||||
addParam cmd "@urlValue" urlValue
|
addParam cmd "@urlValue" urlValue
|
||||||
@ -49,7 +49,7 @@ type SQLiteTagMapData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
log.LogTrace "TagMap.findMappingForTags"
|
log.LogTrace "TagMap.findMappingForTags"
|
||||||
use cmd = conn.CreateCommand ()
|
use cmd = conn.CreateCommand ()
|
||||||
let mapSql, mapParams = inClause $"AND data ->> '{nameof TagMap.Empty.Tag}'" "tag" id tags
|
let mapSql, mapParams = inClause $"AND data ->> '{nameof TagMap.Empty.Tag}'" "tag" id tags
|
||||||
cmd.CommandText <- $"{Query.selectFromTable Table.TagMap} WHERE {Query.whereByWebLog} {mapSql}"
|
cmd.CommandText <- $"{QueryOld.selectFromTable Table.TagMap} WHERE {QueryOld.whereByWebLog} {mapSql}"
|
||||||
addWebLogId cmd webLogId
|
addWebLogId cmd webLogId
|
||||||
cmd.Parameters.AddRange mapParams
|
cmd.Parameters.AddRange mapParams
|
||||||
cmdToList<TagMap> cmd ser
|
cmdToList<TagMap> cmd ser
|
||||||
|
@ -20,7 +20,7 @@ type SQLiteThemeData(conn : SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
let all () = backgroundTask {
|
let all () = backgroundTask {
|
||||||
log.LogTrace "Theme.all"
|
log.LogTrace "Theme.all"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- $"{Query.selectFromTable Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}"
|
cmd.CommandText <- $"{QueryOld.selectFromTable Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}"
|
||||||
let! themes = cmdToList<Theme> cmd ser
|
let! themes = cmdToList<Theme> cmd ser
|
||||||
return themes |> List.map withoutTemplateText
|
return themes |> List.map withoutTemplateText
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ type SQLiteThemeData(conn : SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- $"
|
cmd.CommandText <- $"
|
||||||
DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id;
|
DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id;
|
||||||
DELETE FROM {Table.Theme} WHERE {Query.whereById}"
|
DELETE FROM {Table.Theme} WHERE {QueryOld.whereById}"
|
||||||
addDocId cmd themeId
|
addDocId cmd themeId
|
||||||
do! write cmd
|
do! write cmd
|
||||||
return true
|
return true
|
||||||
|
@ -19,7 +19,7 @@ type SQLiteWebLogData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
let all () =
|
let all () =
|
||||||
log.LogTrace "WebLog.all"
|
log.LogTrace "WebLog.all"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- Query.selectFromTable Table.WebLog
|
cmd.CommandText <- QueryOld.selectFromTable Table.WebLog
|
||||||
cmdToList<WebLog> cmd ser
|
cmdToList<WebLog> cmd ser
|
||||||
|
|
||||||
/// Delete a web log by its ID
|
/// Delete a web log by its ID
|
||||||
@ -48,7 +48,7 @@ type SQLiteWebLogData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger)
|
|||||||
log.LogTrace "WebLog.findByHost"
|
log.LogTrace "WebLog.findByHost"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <-
|
cmd.CommandText <-
|
||||||
$"{Query.selectFromTable Table.WebLog} WHERE data ->> '{nameof WebLog.Empty.UrlBase}' = @urlBase"
|
$"{QueryOld.selectFromTable Table.WebLog} WHERE data ->> '{nameof WebLog.Empty.UrlBase}' = @urlBase"
|
||||||
addParam cmd "@urlBase" url
|
addParam cmd "@urlBase" url
|
||||||
use! rdr = cmd.ExecuteReaderAsync()
|
use! rdr = cmd.ExecuteReaderAsync()
|
||||||
let! isFound = rdr.ReadAsync()
|
let! isFound = rdr.ReadAsync()
|
||||||
|
@ -62,8 +62,8 @@ type SQLiteWebLogUserData(conn: SqliteConnection, ser: JsonSerializer, log: ILog
|
|||||||
log.LogTrace "WebLogUser.findByEmail"
|
log.LogTrace "WebLogUser.findByEmail"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- $"
|
cmd.CommandText <- $"
|
||||||
{Query.selectFromTable Table.WebLogUser}
|
{QueryOld.selectFromTable Table.WebLogUser}
|
||||||
WHERE {Query.whereByWebLog}
|
WHERE {QueryOld.whereByWebLog}
|
||||||
AND data ->> '{nameof WebLogUser.Empty.Email}' = @email"
|
AND data ->> '{nameof WebLogUser.Empty.Email}' = @email"
|
||||||
addWebLogId cmd webLogId
|
addWebLogId cmd webLogId
|
||||||
addParam cmd "@email" email
|
addParam cmd "@email" email
|
||||||
@ -84,7 +84,7 @@ type SQLiteWebLogUserData(conn: SqliteConnection, ser: JsonSerializer, log: ILog
|
|||||||
log.LogTrace "WebLogUser.findNames"
|
log.LogTrace "WebLogUser.findNames"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
let nameSql, nameParams = inClause "AND data ->> 'Id'" "id" string userIds
|
let nameSql, nameParams = inClause "AND data ->> 'Id'" "id" string userIds
|
||||||
cmd.CommandText <- $"{Query.selectFromTable Table.WebLogUser} WHERE {Query.whereByWebLog} {nameSql}"
|
cmd.CommandText <- $"{QueryOld.selectFromTable Table.WebLogUser} WHERE {QueryOld.whereByWebLog} {nameSql}"
|
||||||
addWebLogId cmd webLogId
|
addWebLogId cmd webLogId
|
||||||
cmd.Parameters.AddRange nameParams
|
cmd.Parameters.AddRange nameParams
|
||||||
let! users = cmdToList<WebLogUser> cmd ser
|
let! users = cmdToList<WebLogUser> cmd ser
|
||||||
@ -105,8 +105,8 @@ type SQLiteWebLogUserData(conn: SqliteConnection, ser: JsonSerializer, log: ILog
|
|||||||
cmd.CommandText <- $"
|
cmd.CommandText <- $"
|
||||||
UPDATE {Table.WebLogUser}
|
UPDATE {Table.WebLogUser}
|
||||||
SET data = json_set(data, '$.{nameof WebLogUser.Empty.LastSeenOn}', @lastSeenOn)
|
SET data = json_set(data, '$.{nameof WebLogUser.Empty.LastSeenOn}', @lastSeenOn)
|
||||||
WHERE {Query.whereById}
|
WHERE {QueryOld.whereById}
|
||||||
AND {Query.whereByWebLog}"
|
AND {QueryOld.whereByWebLog}"
|
||||||
addDocId cmd userId
|
addDocId cmd userId
|
||||||
addWebLogId cmd webLogId
|
addWebLogId cmd webLogId
|
||||||
addParam cmd "@lastSeenOn" (instantParam (Noda.now ()))
|
addParam cmd "@lastSeenOn" (instantParam (Noda.now ()))
|
||||||
|
@ -2,6 +2,7 @@ namespace MyWebLog.Data
|
|||||||
|
|
||||||
open System.Threading.Tasks
|
open System.Threading.Tasks
|
||||||
open BitBadger.Sqlite.FSharp.Documents
|
open BitBadger.Sqlite.FSharp.Documents
|
||||||
|
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
open MyWebLog
|
open MyWebLog
|
||||||
@ -12,9 +13,10 @@ open NodaTime
|
|||||||
/// SQLite myWebLog data implementation
|
/// SQLite myWebLog data implementation
|
||||||
type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSerializer) =
|
type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSerializer) =
|
||||||
|
|
||||||
|
/// Create tables (and their associated indexes) if they do not exist
|
||||||
let ensureTables () = backgroundTask {
|
let ensureTables () = backgroundTask {
|
||||||
|
|
||||||
let! tables = Custom.list<string> "SELECT name FROM sqlite_master WHERE type = 'table'" None _.GetString(0)
|
let! tables = Custom.list<string> "SELECT name FROM sqlite_master WHERE type = 'table'" [] (_.GetString(0)) conn
|
||||||
|
|
||||||
let needsTable table =
|
let needsTable table =
|
||||||
not (List.contains table tables)
|
not (List.contains table tables)
|
||||||
@ -102,19 +104,16 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||||||
}
|
}
|
||||||
|> Seq.map (fun sql ->
|
|> Seq.map (fun sql ->
|
||||||
log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table..."""
|
log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table..."""
|
||||||
Custom.nonQuery sql None)
|
Custom.nonQuery sql [] conn)
|
||||||
|
|
||||||
let! _ = Task.WhenAll tasks
|
let! _ = Task.WhenAll tasks
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the database version to the specified version
|
/// Set the database version to the specified version
|
||||||
let setDbVersion version = backgroundTask {
|
let setDbVersion version =
|
||||||
use cmd = conn.CreateCommand()
|
Custom.nonQuery $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')" [] conn
|
||||||
cmd.CommandText <- $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')"
|
|
||||||
do! write cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement the changes between v2-rc1 and v2-rc2
|
/// Implement the changes between v2-rc1 and v2-rc2
|
||||||
let migrateV2Rc1ToV2Rc2 () = backgroundTask {
|
let migrateV2Rc1ToV2Rc2 () = backgroundTask {
|
||||||
let logStep = Utils.logMigrationStep log "v2-rc1 to v2-rc2"
|
let logStep = Utils.logMigrationStep log "v2-rc1 to v2-rc2"
|
||||||
@ -418,6 +417,7 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||||||
|
|
||||||
/// Migrate from v2 to v2.1
|
/// Migrate from v2 to v2.1
|
||||||
let migrateV2ToV2point1 () = backgroundTask {
|
let migrateV2ToV2point1 () = backgroundTask {
|
||||||
|
// FIXME: This will be a backup/restore scenario, as we're changing to documents for most tables
|
||||||
Utils.logMigrationStep log "v2 to v2.1" "Adding redirect rules to web_log table"
|
Utils.logMigrationStep log "v2 to v2.1" "Adding redirect rules to web_log table"
|
||||||
use cmd = conn.CreateCommand()
|
use cmd = conn.CreateCommand()
|
||||||
cmd.CommandText <- "ALTER TABLE web_log ADD COLUMN redirect_rules TEXT NOT NULL DEFAULT '[]'"
|
cmd.CommandText <- "ALTER TABLE web_log ADD COLUMN redirect_rules TEXT NOT NULL DEFAULT '[]'"
|
||||||
@ -454,8 +454,8 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||||||
interface IData with
|
interface IData with
|
||||||
|
|
||||||
member _.Category = SQLiteCategoryData (conn, ser, log)
|
member _.Category = SQLiteCategoryData (conn, ser, log)
|
||||||
member _.Page = SQLitePageData (conn, ser, log)
|
member _.Page = SQLitePageData (conn, log)
|
||||||
member _.Post = SQLitePostData (conn, ser, log)
|
member _.Post = SQLitePostData (conn, log)
|
||||||
member _.TagMap = SQLiteTagMapData (conn, ser, log)
|
member _.TagMap = SQLiteTagMapData (conn, ser, log)
|
||||||
member _.Theme = SQLiteThemeData (conn, ser, log)
|
member _.Theme = SQLiteThemeData (conn, ser, log)
|
||||||
member _.ThemeAsset = SQLiteThemeAssetData (conn, log)
|
member _.ThemeAsset = SQLiteThemeAssetData (conn, log)
|
||||||
@ -467,6 +467,6 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||||||
|
|
||||||
member _.StartUp () = backgroundTask {
|
member _.StartUp () = backgroundTask {
|
||||||
do! ensureTables ()
|
do! ensureTables ()
|
||||||
let! version = Custom.single<string> $"SELECT id FROM {Table.DbVersion}" None _.GetString(0)
|
let! version = Custom.single<string> $"SELECT id FROM {Table.DbVersion}" [] (_.GetString(0)) conn
|
||||||
do! migrate version
|
do! migrate version
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user