Integrate v4 document library for SQLite
- Eliminate warnings for PostgreSQL
This commit is contained in:
		
							parent
							
								
									cd450a05e5
								
							
						
					
					
						commit
						fbc4e891bd
					
				@ -2,7 +2,6 @@
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open BitBadger.Documents.Postgres.Compat
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -21,7 +20,7 @@ type PostgresCategoryData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "Category.countTopLevel"
 | 
			
		||||
        Custom.scalar
 | 
			
		||||
            $"""{Query.byContains (Query.count Table.Category)}
 | 
			
		||||
                  AND {Query.whereByField (Field.NEX (nameof Category.Empty.ParentId)) ""}"""
 | 
			
		||||
                  AND {Query.whereByFields Any [ Field.NEX (nameof Category.Empty.ParentId) ]}"""
 | 
			
		||||
            [ webLogContains webLogId ]
 | 
			
		||||
            toCount
 | 
			
		||||
    
 | 
			
		||||
@ -95,7 +94,7 @@ type PostgresCategoryData(log: ILogger) =
 | 
			
		||||
                        Query.byId (Query.removeFields Table.Category) "",
 | 
			
		||||
                        children
 | 
			
		||||
                        |> List.map (fun child ->
 | 
			
		||||
                            [ idParam child.Id; fieldNameParam [ nameof Category.Empty.ParentId ] ])
 | 
			
		||||
                            [ idParam child.Id; fieldNameParams [ nameof Category.Empty.ParentId ] ])
 | 
			
		||||
                let! _ =
 | 
			
		||||
                    Configuration.dataSource ()
 | 
			
		||||
                    |> Sql.fromDataSource
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -24,15 +23,14 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
 | 
			
		||||
    /// Count all top-level categories for the given web log
 | 
			
		||||
    let countTopLevel webLogId =
 | 
			
		||||
        log.LogTrace "Category.countTopLevel"
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.NEX parentIdField ]
 | 
			
		||||
        conn.customScalar
 | 
			
		||||
            $"{Document.Query.countByWebLog Table.Category} AND data ->> '{parentIdField}' IS NULL"
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
            (toCount >> int)
 | 
			
		||||
            (Query.byFields (Query.count Table.Category) All fields) (addFieldParams fields []) (toCount >> int)
 | 
			
		||||
    
 | 
			
		||||
    /// Find all categories for the given web log
 | 
			
		||||
    let findByWebLog webLogId =
 | 
			
		||||
        log.LogTrace "Category.findByWebLog"
 | 
			
		||||
        Document.findByWebLog<Category> Table.Category webLogId conn
 | 
			
		||||
        conn.findByFields<Category> Table.Category Any [ webLogField webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Retrieve all categories for the given web log in a DotLiquid-friendly format
 | 
			
		||||
    let findAllForView webLogId = backgroundTask {
 | 
			
		||||
@ -51,10 +49,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
 | 
			
		||||
                    |> List.ofSeq
 | 
			
		||||
                    |> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
 | 
			
		||||
                let query = $"""
 | 
			
		||||
                    SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}')
 | 
			
		||||
                    SELECT COUNT(DISTINCT data->>'{nameof Post.Empty.Id}')
 | 
			
		||||
                      FROM {Table.Post}
 | 
			
		||||
                     WHERE {Document.Query.whereByWebLog}
 | 
			
		||||
                       AND {Query.whereByField (Field.EQ (nameof Post.Empty.Status) "") $"'{string Published}'"}
 | 
			
		||||
                       AND data->>'{nameof Post.Empty.Status}' = '{string Published}'
 | 
			
		||||
                       AND {catSql}"""
 | 
			
		||||
                let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount
 | 
			
		||||
                return it.Id, int postCount
 | 
			
		||||
@ -70,9 +68,9 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Find a category by its ID for the given web log
 | 
			
		||||
    let findById catId webLogId =
 | 
			
		||||
    let findById (catId: CategoryId) webLogId =
 | 
			
		||||
        log.LogTrace "Category.findById"
 | 
			
		||||
        Document.findByIdAndWebLog<CategoryId, Category> Table.Category catId webLogId conn
 | 
			
		||||
        conn.findFirstByFields<Category> Table.Category All [ idField catId; webLogField webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Delete a category
 | 
			
		||||
    let delete catId webLogId = backgroundTask {
 | 
			
		||||
@ -80,22 +78,22 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
 | 
			
		||||
        match! findById catId webLogId with
 | 
			
		||||
        | Some cat ->
 | 
			
		||||
            // Reassign any children to the category's parent category
 | 
			
		||||
            let! children = conn.countByField Table.Category (Field.EQ parentIdField (string catId))
 | 
			
		||||
            let! children = conn.countByFields Table.Category Any [ Field.EQ parentIdField (string catId) ]
 | 
			
		||||
            if children > 0L then
 | 
			
		||||
                let parent = Field.EQ parentIdField (string catId)
 | 
			
		||||
                let parent = [ Field.EQ parentIdField (string catId) ]
 | 
			
		||||
                match cat.ParentId with
 | 
			
		||||
                | Some _ -> do! conn.patchByField Table.Category parent {| ParentId = cat.ParentId |}
 | 
			
		||||
                | None -> do! conn.removeFieldsByField Table.Category parent [ parentIdField ]
 | 
			
		||||
                | Some _ -> do! conn.patchByFields Table.Category Any parent {| ParentId = cat.ParentId |}
 | 
			
		||||
                | None -> do! conn.removeFieldsByFields Table.Category Any parent [ parentIdField ]
 | 
			
		||||
            // Delete the category off all posts where it is assigned, and the category itself
 | 
			
		||||
            let catIdField = nameof Post.Empty.CategoryIds
 | 
			
		||||
            let! posts =
 | 
			
		||||
                conn.customList
 | 
			
		||||
                    $"SELECT data ->> '{nameof Post.Empty.Id}', data -> '{catIdField}'
 | 
			
		||||
                    $"SELECT data->>'{nameof Post.Empty.Id}', data->'{catIdField}'
 | 
			
		||||
                        FROM {Table.Post}
 | 
			
		||||
                       WHERE {Document.Query.whereByWebLog}
 | 
			
		||||
                         AND EXISTS
 | 
			
		||||
                               (SELECT 1
 | 
			
		||||
                                  FROM json_each({Table.Post}.data -> '{catIdField}')
 | 
			
		||||
                                  FROM json_each({Table.Post}.data->'{catIdField}')
 | 
			
		||||
                                 WHERE json_each.value = @id)"
 | 
			
		||||
                    [ idParam catId; webLogParam webLogId ]
 | 
			
		||||
                    (fun rdr -> rdr.GetString 0, Utils.deserialize<string list> ser (rdr.GetString 1))
 | 
			
		||||
 | 
			
		||||
@ -218,6 +218,10 @@ module Map =
 | 
			
		||||
          Data      = data }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.WithConn
 | 
			
		||||
 | 
			
		||||
/// Create a named parameter
 | 
			
		||||
let sqlParam name (value: obj) =
 | 
			
		||||
    SqliteParameter(name, value)
 | 
			
		||||
@ -226,11 +230,13 @@ let sqlParam name (value: obj) =
 | 
			
		||||
let webLogParam (webLogId: WebLogId) =
 | 
			
		||||
    sqlParam "@webLogId" (string webLogId)
 | 
			
		||||
 | 
			
		||||
/// Create a field for an ID value
 | 
			
		||||
let idField<'T> (idValue: 'T) =
 | 
			
		||||
    { Field.EQ "Id" (string idValue) with ParameterName = Some "@id" }
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open BitBadger.Documents.Sqlite.WithConn
 | 
			
		||||
/// Create a web log field
 | 
			
		||||
let webLogField (webLogId: WebLogId) =
 | 
			
		||||
    { Field.EQ "WebLogId" (string webLogId) with ParameterName = Some "@webLogId" }
 | 
			
		||||
 | 
			
		||||
/// Functions for manipulating documents
 | 
			
		||||
module Document =
 | 
			
		||||
@ -240,33 +246,17 @@ module Document =
 | 
			
		||||
        
 | 
			
		||||
        /// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId)
 | 
			
		||||
        let whereByWebLog =
 | 
			
		||||
            Query.whereByField (Field.EQ "WebLogId" "") "@webLogId"
 | 
			
		||||
        
 | 
			
		||||
        /// A SELECT query to count documents for a given web log ID
 | 
			
		||||
        let countByWebLog table =
 | 
			
		||||
            Query.statementWhere (Query.count table) whereByWebLog
 | 
			
		||||
        
 | 
			
		||||
        /// A query to select from a table by the document's ID and its web log ID
 | 
			
		||||
        let selectByIdAndWebLog table =
 | 
			
		||||
            Query.statementWhere (Query.find table) $"""{Query.whereById "@id"} AND {whereByWebLog}"""
 | 
			
		||||
            Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
 | 
			
		||||
        
 | 
			
		||||
        /// A query to select from a table by its web log ID
 | 
			
		||||
        let selectByWebLog table =
 | 
			
		||||
            $"{Query.selectFromTable table} WHERE {whereByWebLog}"
 | 
			
		||||
            Query.statementWhere (Query.find table) whereByWebLog
 | 
			
		||||
    
 | 
			
		||||
    /// Count documents for the given web log ID
 | 
			
		||||
    let countByWebLog table (webLogId: WebLogId) conn = backgroundTask {
 | 
			
		||||
        let! count = Count.byFields table Any [ Field.EQ "WebLogId" (string webLogId) ] conn
 | 
			
		||||
        let! count = Count.byFields table Any [ webLogField webLogId ] conn
 | 
			
		||||
        return int count
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Find a document by its ID and web log ID
 | 
			
		||||
    let findByIdAndWebLog<'TKey, 'TDoc> table (key: 'TKey) webLogId conn =
 | 
			
		||||
        Custom.single (Query.selectByIdAndWebLog table) [ idParam key; webLogParam webLogId ] fromData<'TDoc> conn
 | 
			
		||||
    
 | 
			
		||||
    /// Find documents for the given web log
 | 
			
		||||
    let findByWebLog<'TDoc> table (webLogId: WebLogId) conn =
 | 
			
		||||
        Find.byFields<'TDoc> table Any [ Field.EQ "WebLogId" (string webLogId) ] conn
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Functions to support revisions
 | 
			
		||||
@ -285,7 +275,7 @@ module Revisions =
 | 
			
		||||
        Custom.list
 | 
			
		||||
            $"SELECT 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.{Document.Query.whereByWebLog}
 | 
			
		||||
               ORDER BY as_of DESC"
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -19,7 +18,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    let pgListName = nameof Page.Empty.IsInPageList
 | 
			
		||||
    
 | 
			
		||||
    /// The JSON field for the title of the page
 | 
			
		||||
    let titleField = $"data ->> '{nameof Page.Empty.Title}'"
 | 
			
		||||
    let titleField = $"data->>'{nameof Page.Empty.Title}'"
 | 
			
		||||
    
 | 
			
		||||
    // SUPPORT FUNCTIONS
 | 
			
		||||
    
 | 
			
		||||
@ -51,9 +50,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Get all pages for a web log (without text, metadata, revisions, or prior permalinks)
 | 
			
		||||
    let all webLogId =
 | 
			
		||||
        log.LogTrace "Page.all"
 | 
			
		||||
        let field = [ webLogField webLogId ]
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"{Query.selectFromTable Table.Page} WHERE {Document.Query.whereByWebLog} ORDER BY LOWER({titleField})"
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
            $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})"
 | 
			
		||||
            (addFieldParams field [])
 | 
			
		||||
            (fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] })
 | 
			
		||||
    
 | 
			
		||||
    /// Count all pages for the given web log
 | 
			
		||||
@ -64,23 +64,22 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Count all pages shown in the page list for the given web log
 | 
			
		||||
    let countListed webLogId =
 | 
			
		||||
        log.LogTrace "Page.countListed"
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.EQ pgListName true ]
 | 
			
		||||
        conn.customScalar
 | 
			
		||||
            $"""{Document.Query.countByWebLog Table.Page} AND {Query.whereByField (Field.EQ pgListName "") "true"}"""
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
            (toCount >> int)
 | 
			
		||||
            (Query.byFields (Query.count Table.Page) All fields) (addFieldParams fields []) (toCount >> int)
 | 
			
		||||
    
 | 
			
		||||
    /// Find a page by its ID (without revisions and prior permalinks)
 | 
			
		||||
    let findById pageId webLogId = backgroundTask {
 | 
			
		||||
    let findById (pageId: PageId) webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Page.findById"
 | 
			
		||||
        match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with
 | 
			
		||||
        match! conn.findFirstByFields<Page> Table.Page All [ idField pageId; webLogField webLogId ] with
 | 
			
		||||
        | Some page -> return Some { page with PriorPermalinks = [] }
 | 
			
		||||
        | None -> return None
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Find a complete page by its ID
 | 
			
		||||
    let findFullById pageId webLogId = backgroundTask {
 | 
			
		||||
    let findFullById (pageId: PageId) webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Page.findFullById"
 | 
			
		||||
        match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with
 | 
			
		||||
        match! conn.findFirstByFields<Page> Table.Page All [ idField pageId; webLogField webLogId ] with
 | 
			
		||||
        | Some page ->
 | 
			
		||||
            let! page = appendPageRevisions page
 | 
			
		||||
            return Some page
 | 
			
		||||
@ -94,7 +93,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
        match! findById pageId webLogId with
 | 
			
		||||
        | Some _ ->
 | 
			
		||||
            do! conn.customNonQuery
 | 
			
		||||
                    $"DELETE FROM {Table.PageRevision} WHERE page_id = @id;
 | 
			
		||||
                    $"{Query.delete Table.PageRevision} WHERE page_id = @id;
 | 
			
		||||
                      {Query.byId (Query.delete Table.Page) (string pageId)}"
 | 
			
		||||
                    [ idParam pageId ]
 | 
			
		||||
            return true
 | 
			
		||||
@ -104,18 +103,16 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Find a page by its permalink for the given web log
 | 
			
		||||
    let findByPermalink (permalink: Permalink) webLogId =
 | 
			
		||||
        log.LogTrace "Page.findByPermalink"
 | 
			
		||||
        let linkParam = Field.EQ linkName (string permalink)
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField linkParam "@link"}"""
 | 
			
		||||
            (addFieldParam "@link" linkParam [ webLogParam webLogId ])
 | 
			
		||||
            pageWithoutLinks
 | 
			
		||||
            (Query.byFields (Query.find Table.Page) All fields) (addFieldParams fields []) pageWithoutLinks
 | 
			
		||||
    
 | 
			
		||||
    /// Find the current permalink within a set of potential prior permalinks for the given web log
 | 
			
		||||
    let findCurrentPermalink (permalinks: Permalink list) webLogId =
 | 
			
		||||
        log.LogTrace "Page.findCurrentPermalink"
 | 
			
		||||
        let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"SELECT data ->> '{linkName}' AS permalink
 | 
			
		||||
            $"SELECT data->>'{linkName}' AS permalink
 | 
			
		||||
                FROM {Table.Page}
 | 
			
		||||
               WHERE {Document.Query.whereByWebLog} AND {linkSql}"
 | 
			
		||||
            (webLogParam webLogId :: linkParams)
 | 
			
		||||
@ -124,7 +121,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Get all complete pages for the given web log
 | 
			
		||||
    let findFullByWebLog webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Page.findFullByWebLog"
 | 
			
		||||
        let! pages    = Document.findByWebLog<Page> Table.Page webLogId conn
 | 
			
		||||
        let! pages    = conn.findByFields<Page> Table.Page Any [ webLogField webLogId ]
 | 
			
		||||
        let! withRevs = pages |> List.map appendPageRevisions |> Task.WhenAll
 | 
			
		||||
        return List.ofArray withRevs
 | 
			
		||||
    }
 | 
			
		||||
@ -132,18 +129,20 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Get all listed pages for the given web log (without revisions or text)
 | 
			
		||||
    let findListed webLogId =
 | 
			
		||||
        log.LogTrace "Page.findListed"
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.EQ pgListName true ]
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField (Field.EQ pgListName "") "true"}
 | 
			
		||||
                ORDER BY LOWER({titleField})"""
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
            $"{Query.byFields (Query.find Table.Page) All fields} ORDER BY LOWER({titleField})"
 | 
			
		||||
            (addFieldParams fields [])
 | 
			
		||||
            (fun rdr -> { fromData<Page> rdr with Text = "" })
 | 
			
		||||
    
 | 
			
		||||
    /// Get a page of pages for the given web log (without revisions)
 | 
			
		||||
    let findPageOfPages webLogId pageNbr =
 | 
			
		||||
        log.LogTrace "Page.findPageOfPages"
 | 
			
		||||
        let field = [ webLogField webLogId ]
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"{Document.Query.selectByWebLog Table.Page} ORDER BY LOWER({titleField}) LIMIT @pageSize OFFSET @toSkip"
 | 
			
		||||
            [ webLogParam webLogId; SqliteParameter("@pageSize", 26); SqliteParameter("@toSkip", (pageNbr - 1) * 25) ]
 | 
			
		||||
            $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})
 | 
			
		||||
                LIMIT @pageSize OFFSET @toSkip"
 | 
			
		||||
            (addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ])
 | 
			
		||||
            (fun rdr -> { pageWithoutLinks rdr with Metadata = [] })
 | 
			
		||||
    
 | 
			
		||||
    /// Update a page
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -17,7 +16,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    let linkName = nameof Post.Empty.Permalink
 | 
			
		||||
    
 | 
			
		||||
    /// The JSON field for when the post was published
 | 
			
		||||
    let publishField = $"data ->> '{nameof Post.Empty.PublishedOn}'"
 | 
			
		||||
    let publishField = $"data->>'{nameof Post.Empty.PublishedOn}'"
 | 
			
		||||
    
 | 
			
		||||
    /// The name of the JSON field for the post's status
 | 
			
		||||
    let statName = nameof Post.Empty.Status
 | 
			
		||||
@ -44,7 +43,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    
 | 
			
		||||
    /// The SELECT statement to retrieve published posts with a web log ID parameter
 | 
			
		||||
    let publishedPostByWebLog =
 | 
			
		||||
        $"""{postByWebLog} AND {Query.whereByField (Field.EQ statName "") $"'{string Published}'"}"""
 | 
			
		||||
        $"{postByWebLog} AND data->>'{statName}' = '{string Published}'"
 | 
			
		||||
    
 | 
			
		||||
    /// Update a post's revisions
 | 
			
		||||
    let updatePostRevisions (postId: PostId) oldRevs newRevs =
 | 
			
		||||
@ -63,16 +62,14 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Count posts in a status for the given web log
 | 
			
		||||
    let countByStatus (status: PostStatus) webLogId =
 | 
			
		||||
        log.LogTrace "Post.countByStatus"
 | 
			
		||||
        let statParam = Field.EQ statName (string status)
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.EQ statName (string status) ]
 | 
			
		||||
        conn.customScalar
 | 
			
		||||
            $"""{Document.Query.countByWebLog Table.Post} AND {Query.whereByField statParam "@status"}"""
 | 
			
		||||
            (addFieldParam "@status" statParam [ webLogParam webLogId ])
 | 
			
		||||
            (toCount >> int)
 | 
			
		||||
            (Query.byFields (Query.count Table.Post) All fields) (addFieldParams fields []) (toCount >> int)
 | 
			
		||||
    
 | 
			
		||||
    /// Find a post by its ID for the given web log (excluding revisions)
 | 
			
		||||
    let findById postId webLogId = backgroundTask {
 | 
			
		||||
    let findById (postId: PostId) webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Post.findById"
 | 
			
		||||
        match! Document.findByIdAndWebLog<PostId, Post> Table.Post postId webLogId conn with
 | 
			
		||||
        match! conn.findFirstByFields<Post> Table.Post All [ idField postId; webLogField webLogId ] with
 | 
			
		||||
        | Some post -> return Some { post with PriorPermalinks = [] }
 | 
			
		||||
        | None -> return None
 | 
			
		||||
    }
 | 
			
		||||
@ -80,16 +77,14 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Find a post by its permalink for the given web log (excluding revisions)
 | 
			
		||||
    let findByPermalink (permalink: Permalink) webLogId =
 | 
			
		||||
        log.LogTrace "Post.findByPermalink"
 | 
			
		||||
        let linkParam = Field.EQ linkName (string permalink)
 | 
			
		||||
        let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereByField linkParam "@link"}"""
 | 
			
		||||
            (addFieldParam "@link" linkParam [ webLogParam webLogId ])
 | 
			
		||||
            postWithoutLinks
 | 
			
		||||
            (Query.byFields (Query.find Table.Post) All fields) (addFieldParams fields []) postWithoutLinks
 | 
			
		||||
    
 | 
			
		||||
    /// Find a complete post by its ID for the given web log
 | 
			
		||||
    let findFullById postId webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Post.findFullById"
 | 
			
		||||
        match! Document.findByIdAndWebLog<PostId, Post> Table.Post postId webLogId conn with
 | 
			
		||||
        match! conn.findFirstByFields<Post> Table.Post All [ idField postId; webLogField webLogId ] with
 | 
			
		||||
        | Some post ->
 | 
			
		||||
            let! post = appendPostRevisions post
 | 
			
		||||
            return Some post
 | 
			
		||||
@ -102,9 +97,11 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
        match! findById postId webLogId with
 | 
			
		||||
        | Some _ ->
 | 
			
		||||
            do! conn.customNonQuery
 | 
			
		||||
                    $"""DELETE FROM {Table.PostRevision} WHERE post_id = @id;
 | 
			
		||||
                        DELETE FROM {Table.PostComment}
 | 
			
		||||
                         WHERE {Query.whereByField (Field.EQ (nameof Comment.Empty.PostId) "") "@id"};
 | 
			
		||||
                    $"""{Query.delete Table.PostRevision} WHERE post_id = @id;
 | 
			
		||||
                        {Query.byFields
 | 
			
		||||
                             (Query.delete Table.PostComment)
 | 
			
		||||
                             Any
 | 
			
		||||
                             [ { Field.EQ (nameof Comment.Empty.PostId) postId with ParameterName = Some "@id" }]};
 | 
			
		||||
                        {Query.byId (Query.delete Table.Post) (string postId)}"""
 | 
			
		||||
                    [ idParam postId ]
 | 
			
		||||
            return true
 | 
			
		||||
@ -116,7 +113,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
        log.LogTrace "Post.findCurrentPermalink"
 | 
			
		||||
        let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"SELECT data ->> '{linkName}' AS permalink
 | 
			
		||||
            $"SELECT data->>'{linkName}' AS permalink
 | 
			
		||||
                FROM {Table.Post}
 | 
			
		||||
               WHERE {Document.Query.whereByWebLog} AND {linkSql}"
 | 
			
		||||
            (webLogParam webLogId :: linkParams)
 | 
			
		||||
@ -125,7 +122,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Get all complete posts for the given web log
 | 
			
		||||
    let findFullByWebLog webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Post.findFullByWebLog"
 | 
			
		||||
        let! posts    = Document.findByWebLog<Post> Table.Post webLogId conn
 | 
			
		||||
        let! posts    = conn.findByFields<Post> Table.Post Any [ webLogField webLogId ]
 | 
			
		||||
        let! withRevs = posts |> List.map appendPostRevisions |> Task.WhenAll
 | 
			
		||||
        return List.ofArray withRevs
 | 
			
		||||
    }
 | 
			
		||||
@ -146,7 +143,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
        log.LogTrace "Post.findPageOfPosts"
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"{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}"
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
            postWithoutText
 | 
			
		||||
@ -175,15 +172,16 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Find the next newest and oldest post from a publish date for the given web log
 | 
			
		||||
    let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask {
 | 
			
		||||
        log.LogTrace "Post.findSurroundingPosts"
 | 
			
		||||
        let parameters = [ webLogParam webLogId; sqlParam "@publishedOn" (instantParam publishedOn) ]
 | 
			
		||||
        let! older =
 | 
			
		||||
            conn.customSingle
 | 
			
		||||
                $"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
 | 
			
		||||
                [ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
 | 
			
		||||
                parameters
 | 
			
		||||
                postWithoutLinks
 | 
			
		||||
        let! newer =
 | 
			
		||||
            conn.customSingle
 | 
			
		||||
                $"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
 | 
			
		||||
                [ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
 | 
			
		||||
                parameters
 | 
			
		||||
                postWithoutLinks
 | 
			
		||||
        return older, newer
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -12,9 +11,9 @@ open MyWebLog.Data
 | 
			
		||||
type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
 | 
			
		||||
    /// Find a tag mapping by its ID for the given web log
 | 
			
		||||
    let findById tagMapId webLogId =
 | 
			
		||||
    let findById (tagMapId: TagMapId) webLogId =
 | 
			
		||||
        log.LogTrace "TagMap.findById"
 | 
			
		||||
        Document.findByIdAndWebLog<TagMapId, TagMap> Table.TagMap tagMapId webLogId conn
 | 
			
		||||
        conn.findFirstByFields<TagMap> Table.TagMap All [ idField tagMapId; webLogField webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Delete a tag mapping for the given web log
 | 
			
		||||
    let delete tagMapId webLogId = backgroundTask {
 | 
			
		||||
@ -29,21 +28,18 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Find a tag mapping by its URL value for the given web log
 | 
			
		||||
    let findByUrlValue (urlValue: string) webLogId =
 | 
			
		||||
        log.LogTrace "TagMap.findByUrlValue"
 | 
			
		||||
        let urlParam = Field.EQ (nameof TagMap.Empty.UrlValue) urlValue
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"""{Document.Query.selectByWebLog Table.TagMap} AND {Query.whereByField urlParam "@urlValue"}"""
 | 
			
		||||
            (addFieldParam "@urlValue" urlParam [ webLogParam webLogId ])
 | 
			
		||||
            fromData<TagMap>
 | 
			
		||||
        conn.findFirstByFields<TagMap>
 | 
			
		||||
            Table.TagMap All [ webLogField webLogId; Field.EQ (nameof TagMap.Empty.UrlValue) urlValue ]
 | 
			
		||||
    
 | 
			
		||||
    /// Get all tag mappings for the given web log
 | 
			
		||||
    let findByWebLog webLogId =
 | 
			
		||||
        log.LogTrace "TagMap.findByWebLog"
 | 
			
		||||
        Document.findByWebLog<TagMap> Table.TagMap webLogId conn
 | 
			
		||||
        conn.findByFields<TagMap> Table.TagMap Any [ webLogField webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Find any tag mappings in a list of tags for the given web log
 | 
			
		||||
    let findMappingForTags (tags: string list) webLogId =
 | 
			
		||||
        log.LogTrace "TagMap.findMappingForTags"
 | 
			
		||||
        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
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"{Document.Query.selectByWebLog Table.TagMap} {mapSql}"
 | 
			
		||||
            (webLogParam webLogId :: mapParams)
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ open MyWebLog.Data
 | 
			
		||||
type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
 | 
			
		||||
    
 | 
			
		||||
    /// The JSON field for the theme ID
 | 
			
		||||
    let idField = $"data ->> '{nameof Theme.Empty.Id}'"
 | 
			
		||||
    let idField = $"data->>'{nameof Theme.Empty.Id}'"
 | 
			
		||||
    
 | 
			
		||||
    /// Convert a document to a theme with no template text
 | 
			
		||||
    let withoutTemplateText (rdr: SqliteDataReader) =
 | 
			
		||||
@ -48,7 +48,7 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
 | 
			
		||||
        match! findByIdWithoutText themeId with
 | 
			
		||||
        | Some _ ->
 | 
			
		||||
            do! conn.customNonQuery
 | 
			
		||||
                    $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id;
 | 
			
		||||
                    $"{Query.delete Table.ThemeAsset} WHERE theme_id = @id;
 | 
			
		||||
                      {Query.byId (Query.delete Table.Theme) (string themeId)}"
 | 
			
		||||
                    [ idParam themeId ]
 | 
			
		||||
            return true
 | 
			
		||||
@ -87,7 +87,7 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Delete all assets for the given theme
 | 
			
		||||
    let deleteByTheme (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "ThemeAsset.deleteByTheme"
 | 
			
		||||
        conn.customNonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ]
 | 
			
		||||
        conn.customNonQuery $"{Query.delete Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Find a theme asset by its ID
 | 
			
		||||
    let findById assetId =
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -24,25 +23,25 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Delete a web log by its ID
 | 
			
		||||
    let delete webLogId =
 | 
			
		||||
        log.LogTrace "WebLog.delete"
 | 
			
		||||
        let webLogMatches = Query.whereByField (Field.EQ "WebLogId" "") "@webLogId"
 | 
			
		||||
        let subQuery table = $"(SELECT data ->> 'Id' FROM {table} WHERE {webLogMatches})"
 | 
			
		||||
        let webLogMatches = Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
 | 
			
		||||
        let subQuery table = $"(SELECT data->>'Id' FROM {table} WHERE {webLogMatches})"
 | 
			
		||||
        Custom.nonQuery
 | 
			
		||||
            $"""DELETE FROM {Table.PostComment}  WHERE data ->> 'PostId' IN {subQuery Table.Post};
 | 
			
		||||
                DELETE FROM {Table.PostRevision} WHERE post_id           IN {subQuery Table.Post};
 | 
			
		||||
                DELETE FROM {Table.PageRevision} WHERE page_id           IN {subQuery Table.Page};
 | 
			
		||||
                DELETE FROM {Table.Post}         WHERE {webLogMatches};
 | 
			
		||||
                DELETE FROM {Table.Page}         WHERE {webLogMatches};
 | 
			
		||||
                DELETE FROM {Table.Category}     WHERE {webLogMatches};
 | 
			
		||||
                DELETE FROM {Table.TagMap}       WHERE {webLogMatches};
 | 
			
		||||
                DELETE FROM {Table.Upload}       WHERE web_log_id = @webLogId;
 | 
			
		||||
                DELETE FROM {Table.WebLogUser}   WHERE {webLogMatches};
 | 
			
		||||
                DELETE FROM {Table.WebLog}       WHERE {Query.whereById "@webLogId"}"""
 | 
			
		||||
            $"""{Query.delete Table.PostComment}  WHERE data ->> 'PostId' IN {subQuery Table.Post};
 | 
			
		||||
                {Query.delete Table.PostRevision} WHERE post_id           IN {subQuery Table.Post};
 | 
			
		||||
                {Query.delete Table.PageRevision} WHERE page_id           IN {subQuery Table.Page};
 | 
			
		||||
                {Query.delete Table.Post}         WHERE {webLogMatches};
 | 
			
		||||
                {Query.delete Table.Page}         WHERE {webLogMatches};
 | 
			
		||||
                {Query.delete Table.Category}     WHERE {webLogMatches};
 | 
			
		||||
                {Query.delete Table.TagMap}       WHERE {webLogMatches};
 | 
			
		||||
                {Query.delete Table.Upload}       WHERE web_log_id = @webLogId;
 | 
			
		||||
                {Query.delete Table.WebLogUser}   WHERE {webLogMatches};
 | 
			
		||||
                {Query.delete Table.WebLog}       WHERE {Query.whereById "@webLogId"}"""
 | 
			
		||||
            [ webLogParam webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Find a web log by its host (URL base)
 | 
			
		||||
    let findByHost (url: string) =
 | 
			
		||||
        log.LogTrace "WebLog.findByHost"
 | 
			
		||||
        conn.findFirstByField<WebLog> Table.WebLog (Field.EQ (nameof WebLog.Empty.UrlBase) url)
 | 
			
		||||
        conn.findFirstByFields<WebLog> Table.WebLog Any [ Field.EQ (nameof WebLog.Empty.UrlBase) url ]
 | 
			
		||||
    
 | 
			
		||||
    /// Find a web log by its ID
 | 
			
		||||
    let findById webLogId =
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
 | 
			
		||||
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Sqlite
 | 
			
		||||
open BitBadger.Documents.Sqlite.Compat
 | 
			
		||||
open Microsoft.Data.Sqlite
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
@ -17,17 +16,18 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
        conn.insert<WebLogUser> Table.WebLogUser user
 | 
			
		||||
    
 | 
			
		||||
    /// Find a user by their ID for the given web log
 | 
			
		||||
    let findById userId webLogId =
 | 
			
		||||
    let findById (userId: WebLogUserId) webLogId =
 | 
			
		||||
        log.LogTrace "WebLogUser.findById"
 | 
			
		||||
        Document.findByIdAndWebLog<WebLogUserId, WebLogUser> Table.WebLogUser userId webLogId conn
 | 
			
		||||
        conn.findFirstByFields<WebLogUser> Table.WebLogUser All [ idField userId; webLogField webLogId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Delete a user if they have no posts or pages
 | 
			
		||||
    let delete userId webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "WebLogUser.delete"
 | 
			
		||||
        match! findById userId webLogId with
 | 
			
		||||
        | Some _ ->
 | 
			
		||||
            let! pageCount = conn.countByField Table.Page (Field.EQ (nameof Page.Empty.AuthorId) (string userId))
 | 
			
		||||
            let! postCount = conn.countByField Table.Post (Field.EQ (nameof Post.Empty.AuthorId) (string userId))
 | 
			
		||||
            let  author    = [ Field.EQ (nameof Page.Empty.AuthorId) (string userId) ]
 | 
			
		||||
            let! pageCount = conn.countByFields Table.Page Any author
 | 
			
		||||
            let! postCount = conn.countByFields Table.Post Any author
 | 
			
		||||
            if pageCount + postCount > 0 then
 | 
			
		||||
                return Error "User has pages or posts; cannot delete"
 | 
			
		||||
            else
 | 
			
		||||
@ -39,24 +39,20 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
 | 
			
		||||
    /// Find a user by their e-mail address for the given web log
 | 
			
		||||
    let findByEmail (email: string) webLogId =
 | 
			
		||||
        log.LogTrace "WebLogUser.findByEmail"
 | 
			
		||||
        let emailParam = Field.EQ (nameof WebLogUser.Empty.Email) email
 | 
			
		||||
        conn.customSingle
 | 
			
		||||
            $"""{Document.Query.selectByWebLog Table.WebLogUser}
 | 
			
		||||
                  AND {Query.whereByField emailParam "@email"}"""
 | 
			
		||||
            (addFieldParam "@email" emailParam [ webLogParam webLogId ])
 | 
			
		||||
            fromData<WebLogUser>
 | 
			
		||||
        conn.findFirstByFields
 | 
			
		||||
            Table.WebLogUser All [ webLogField webLogId; Field.EQ (nameof WebLogUser.Empty.Email) email ]
 | 
			
		||||
    
 | 
			
		||||
    /// Get all users for the given web log
 | 
			
		||||
    let findByWebLog webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "WebLogUser.findByWebLog"
 | 
			
		||||
        let! users = Document.findByWebLog<WebLogUser> Table.WebLogUser webLogId conn
 | 
			
		||||
        let! users = conn.findByFields<WebLogUser> Table.WebLogUser Any [ webLogField webLogId ]
 | 
			
		||||
        return users |> List.sortBy _.PreferredName.ToLowerInvariant()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Find the names of users by their IDs for the given web log
 | 
			
		||||
    let findNames webLogId (userIds: WebLogUserId list) =
 | 
			
		||||
        log.LogTrace "WebLogUser.findNames"
 | 
			
		||||
        let nameSql, nameParams = inClause $"AND data ->> '{nameof WebLogUser.Empty.Id}'" "id" string userIds 
 | 
			
		||||
        let nameSql, nameParams = inClause $"AND data->>'{nameof WebLogUser.Empty.Id}'" "id" string userIds 
 | 
			
		||||
        conn.customList
 | 
			
		||||
            $"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}"
 | 
			
		||||
            (webLogParam webLogId :: nameParams)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user