Migrate Postgres data to new library
This commit is contained in:
		
							parent
							
								
									7fb68d5583
								
							
						
					
					
						commit
						e8d63793ba
					
				@ -9,7 +9,7 @@
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
	
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BitBadger.Npgsql.FSharp.Documents" Version="2.0.0" />
 | 
			
		||||
		<PackageReference Include="BitBadger.Documents.Postgres" Version="3.0.0-rc-1" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open System.Threading
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Caching.Distributed
 | 
			
		||||
open NodaTime
 | 
			
		||||
 | 
			
		||||
@ -48,9 +48,11 @@ type DistributedCache () =
 | 
			
		||||
        task {
 | 
			
		||||
            let! exists =
 | 
			
		||||
                Custom.scalar
 | 
			
		||||
                    $"SELECT EXISTS
 | 
			
		||||
                    "SELECT EXISTS
 | 
			
		||||
                       (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'session')
 | 
			
		||||
                      AS {existsName}" [] Map.toExists
 | 
			
		||||
                     AS it"
 | 
			
		||||
                    []
 | 
			
		||||
                    toExists
 | 
			
		||||
            if not exists then
 | 
			
		||||
                do! Custom.nonQuery
 | 
			
		||||
                        "CREATE TABLE session (
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -41,13 +42,12 @@ type PostgresCategoryData(log: ILogger) =
 | 
			
		||||
                    |> arrayContains (nameof Post.Empty.CategoryIds) id
 | 
			
		||||
                let postCount =
 | 
			
		||||
                    Custom.scalar
 | 
			
		||||
                        $"""SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}') AS {countName}
 | 
			
		||||
                        $"""SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}') AS it
 | 
			
		||||
                              FROM {Table.Post}
 | 
			
		||||
                             WHERE {Query.whereDataContains "@criteria"}
 | 
			
		||||
                               AND {catIdSql}"""
 | 
			
		||||
                        [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = Published |}
 | 
			
		||||
                          catIdParams ]
 | 
			
		||||
                        Map.toCount
 | 
			
		||||
                        [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catIdParams ]
 | 
			
		||||
                        toCount
 | 
			
		||||
                    |> Async.AwaitTask
 | 
			
		||||
                    |> Async.RunSynchronously
 | 
			
		||||
                it.Id, postCount)
 | 
			
		||||
@ -85,33 +85,32 @@ type PostgresCategoryData(log: ILogger) =
 | 
			
		||||
                    Configuration.dataSource ()
 | 
			
		||||
                    |> Sql.fromDataSource
 | 
			
		||||
                    |> Sql.executeTransactionAsync
 | 
			
		||||
                        [ Query.Update.partialById Table.Category,
 | 
			
		||||
                        [ Query.Patch.byId Table.Category,
 | 
			
		||||
                          children
 | 
			
		||||
                          |> List.map (fun child ->
 | 
			
		||||
                              [ "@id",   Sql.string (string child.Id)
 | 
			
		||||
                                "@data", Query.jsonbDocParam {| ParentId = cat.ParentId |} ]) ]
 | 
			
		||||
                              [ idParam child.Id; jsonParam "@data" {| ParentId = cat.ParentId |} ]) ]
 | 
			
		||||
                ()
 | 
			
		||||
            // Delete the category off all posts where it is assigned
 | 
			
		||||
            let! posts =
 | 
			
		||||
                Custom.list
 | 
			
		||||
                    $"SELECT data FROM {Table.Post} WHERE data -> '{nameof Post.Empty.CategoryIds}' @> @id"
 | 
			
		||||
                    [ "@id", Query.jsonbDocParam [| string catId |] ]
 | 
			
		||||
                    [ jsonParam "@id" [| string catId |] ]
 | 
			
		||||
                    fromData<Post>
 | 
			
		||||
            if not (List.isEmpty posts) then
 | 
			
		||||
                let! _ =
 | 
			
		||||
                    Configuration.dataSource ()
 | 
			
		||||
                    |> Sql.fromDataSource
 | 
			
		||||
                    |> Sql.executeTransactionAsync
 | 
			
		||||
                        [ Query.Update.partialById Table.Post,
 | 
			
		||||
                        [ Query.Patch.byId Table.Post,
 | 
			
		||||
                          posts
 | 
			
		||||
                          |> List.map (fun post ->
 | 
			
		||||
                              [ "@id",   Sql.string (string post.Id)
 | 
			
		||||
                                "@data", Query.jsonbDocParam
 | 
			
		||||
                                           {| CategoryIds = post.CategoryIds
 | 
			
		||||
                                                            |> List.filter (fun cat -> cat <> catId) |} ]) ]
 | 
			
		||||
                              [ idParam post.Id
 | 
			
		||||
                                jsonParam
 | 
			
		||||
                                    "@data"
 | 
			
		||||
                                    {| CategoryIds = post.CategoryIds |> List.filter (fun cat -> cat <> catId) |} ]) ]
 | 
			
		||||
                ()
 | 
			
		||||
            // Delete the category itself
 | 
			
		||||
            do! Delete.byId Table.Category (string catId)
 | 
			
		||||
            do! Delete.byId Table.Category catId
 | 
			
		||||
            return if hasChildren then ReassignedChildCategories else CategoryDeleted
 | 
			
		||||
        | None -> return CategoryNotFound
 | 
			
		||||
    }
 | 
			
		||||
@ -129,7 +128,7 @@ type PostgresCategoryData(log: ILogger) =
 | 
			
		||||
            Configuration.dataSource ()
 | 
			
		||||
            |> Sql.fromDataSource
 | 
			
		||||
            |> Sql.executeTransactionAsync [
 | 
			
		||||
                Query.insert Table.Category, cats |> List.map (fun c -> [ "@data", Query.jsonbDocParam c ])
 | 
			
		||||
                Query.insert Table.Category, cats |> List.map (fun c -> [ jsonParam "@data" c ])
 | 
			
		||||
            ]
 | 
			
		||||
        ()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,8 @@ module Table =
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open System.Threading.Tasks
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
open NodaTime
 | 
			
		||||
@ -78,13 +79,7 @@ let webLogDoc (webLogId: WebLogId) =
 | 
			
		||||
 | 
			
		||||
/// Create a parameter for a web log document-contains query
 | 
			
		||||
let webLogContains webLogId =
 | 
			
		||||
    "@criteria", Query.jsonbDocParam (webLogDoc webLogId)
 | 
			
		||||
 | 
			
		||||
/// The name of the field to select to be able to use Map.toCount
 | 
			
		||||
let countName = "the_count"
 | 
			
		||||
 | 
			
		||||
/// The name of the field to select to be able to use Map.toExists
 | 
			
		||||
let existsName = "does_exist"
 | 
			
		||||
    jsonParam "@criteria" (webLogDoc webLogId)
 | 
			
		||||
 | 
			
		||||
/// A SQL string to select data from a table with the given JSON document contains criteria
 | 
			
		||||
let selectWithCriteria tableName =
 | 
			
		||||
@ -129,14 +124,6 @@ let optParam<'T> name (it: 'T option) =
 | 
			
		||||
/// Mapping functions for SQL queries
 | 
			
		||||
module Map =
 | 
			
		||||
    
 | 
			
		||||
    /// Get a count from a row
 | 
			
		||||
    let toCount (row: RowReader) =
 | 
			
		||||
        row.int countName
 | 
			
		||||
    
 | 
			
		||||
    /// Get a true/false value as to whether an item exists
 | 
			
		||||
    let toExists (row: RowReader) =
 | 
			
		||||
        row.bool existsName
 | 
			
		||||
    
 | 
			
		||||
    /// Create a permalink from the current row
 | 
			
		||||
    let toPermalink (row: RowReader) =
 | 
			
		||||
        Permalink (row.string "permalink")
 | 
			
		||||
@ -168,9 +155,9 @@ module Document =
 | 
			
		||||
        Custom.scalar
 | 
			
		||||
            $"""SELECT EXISTS (
 | 
			
		||||
                    SELECT 1 FROM %s{table} WHERE {Query.whereById "@id"} AND {Query.whereDataContains "@criteria"}
 | 
			
		||||
                    ) AS {existsName}"""
 | 
			
		||||
                    ) AS it"""
 | 
			
		||||
            [ "@id", Sql.string (string key); webLogContains webLogId ]
 | 
			
		||||
            Map.toExists
 | 
			
		||||
            toExists
 | 
			
		||||
    
 | 
			
		||||
    /// Find a document by its ID for the given web log
 | 
			
		||||
    let findByIdAndWebLog<'TKey, 'TDoc> table (key: 'TKey) webLogId =
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -76,7 +77,7 @@ type PostgresPageData(log: ILogger) =
 | 
			
		||||
            do! Custom.nonQuery
 | 
			
		||||
                    $"""DELETE FROM {Table.PageRevision} WHERE page_id = @id;
 | 
			
		||||
                        DELETE FROM {Table.Page}         WHERE {Query.whereById "@id"}"""
 | 
			
		||||
                    [ "@id", Sql.string (string pageId) ]
 | 
			
		||||
                    [ idParam pageId ]
 | 
			
		||||
            return true
 | 
			
		||||
        | false -> return false
 | 
			
		||||
    }
 | 
			
		||||
@ -119,7 +120,7 @@ type PostgresPageData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "Page.findListed"
 | 
			
		||||
        Custom.list
 | 
			
		||||
            $"{selectWithCriteria Table.Page} ORDER BY LOWER(data ->> '{nameof Page.Empty.Title}')"
 | 
			
		||||
            [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with IsInPageList = true |} ]
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with IsInPageList = true |} ]
 | 
			
		||||
            pageWithoutText
 | 
			
		||||
    
 | 
			
		||||
    /// Get a page of pages for the given web log (without revisions)
 | 
			
		||||
@ -141,7 +142,7 @@ type PostgresPageData(log: ILogger) =
 | 
			
		||||
            |> Sql.fromDataSource
 | 
			
		||||
            |> Sql.executeTransactionAsync
 | 
			
		||||
                [ Query.insert Table.Page,
 | 
			
		||||
                    pages |> List.map (fun page -> [ "@data", Query.jsonbDocParam { page with Revisions = [] } ])
 | 
			
		||||
                    pages |> List.map (fun page -> [ jsonParam "@data" { page with Revisions = [] } ])
 | 
			
		||||
                  Revisions.insertSql Table.PageRevision,
 | 
			
		||||
                    revisions |> List.map (fun (pageId, rev) -> Revisions.revParams pageId rev) ]
 | 
			
		||||
        ()
 | 
			
		||||
@ -161,7 +162,7 @@ type PostgresPageData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "Page.updatePriorPermalinks"
 | 
			
		||||
        match! pageExists pageId webLogId with
 | 
			
		||||
        | true ->
 | 
			
		||||
            do! Update.partialById Table.Page (string pageId) {| PriorPermalinks = permalinks |}
 | 
			
		||||
            do! Patch.byId Table.Page pageId {| PriorPermalinks = permalinks |}
 | 
			
		||||
            return true
 | 
			
		||||
        | false -> return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -50,7 +51,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "Post.findByPermalink"
 | 
			
		||||
        Custom.single
 | 
			
		||||
            (selectWithCriteria Table.Post)
 | 
			
		||||
            [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Permalink = permalink |} ]
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Permalink = permalink |} ]
 | 
			
		||||
            fromData<Post>
 | 
			
		||||
    
 | 
			
		||||
    /// Find a complete post by its ID for the given web log
 | 
			
		||||
@ -72,7 +73,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
                    $"""DELETE FROM {Table.PostComment}  WHERE {Query.whereDataContains "@criteria"};
 | 
			
		||||
                        DELETE FROM {Table.PostRevision} WHERE post_id = @id;
 | 
			
		||||
                        DELETE FROM {Table.Post}         WHERE {Query.whereById "@id"}"""
 | 
			
		||||
                    [ "@id", Sql.string (string postId); "@criteria", Query.jsonbDocParam {| PostId = postId |} ]
 | 
			
		||||
                    [ idParam postId; jsonParam "@criteria" {| PostId = postId |} ]
 | 
			
		||||
            return true
 | 
			
		||||
        | false -> return false
 | 
			
		||||
    }
 | 
			
		||||
@ -113,7 +114,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
                 AND {catSql}
 | 
			
		||||
               ORDER BY data ->> '{nameof Post.Empty.PublishedOn}' DESC
 | 
			
		||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
			
		||||
            [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = Published |}; catParam ]
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catParam ]
 | 
			
		||||
            fromData<Post>
 | 
			
		||||
    
 | 
			
		||||
    /// Get a page of posts for the given web log (excludes text and revisions)
 | 
			
		||||
@ -134,7 +135,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
            $"{selectWithCriteria Table.Post}
 | 
			
		||||
               ORDER BY data ->> '{nameof Post.Empty.PublishedOn}' DESC
 | 
			
		||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
			
		||||
            [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = Published |} ]
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ]
 | 
			
		||||
            fromData<Post>
 | 
			
		||||
    
 | 
			
		||||
    /// Get a page of tagged posts for the given web log (excludes revisions and prior permalinks)
 | 
			
		||||
@ -145,15 +146,14 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
                 AND data['{nameof Post.Empty.Tags}'] @> @tag
 | 
			
		||||
               ORDER BY data ->> '{nameof Post.Empty.PublishedOn}' DESC
 | 
			
		||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
			
		||||
            [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = Published |}
 | 
			
		||||
              "@tag",      Query.jsonbDocParam [| tag |] ]
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; jsonParam "@tag" [| tag |] ]
 | 
			
		||||
            fromData<Post>
 | 
			
		||||
    
 | 
			
		||||
    /// Find the next newest and oldest post from a publish date for the given web log
 | 
			
		||||
    let findSurroundingPosts webLogId publishedOn = backgroundTask {
 | 
			
		||||
        log.LogTrace "Post.findSurroundingPosts"
 | 
			
		||||
        let queryParams () =
 | 
			
		||||
            [ "@criteria",    Query.jsonbDocParam {| webLogDoc webLogId with Status = Published |}
 | 
			
		||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}
 | 
			
		||||
              "@publishedOn", Sql.string ((InstantPattern.General.Format publishedOn)[..19]) ]
 | 
			
		||||
        let pubField = nameof Post.Empty.PublishedOn
 | 
			
		||||
        let! older =
 | 
			
		||||
@ -192,7 +192,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
            |> Sql.fromDataSource
 | 
			
		||||
            |> Sql.executeTransactionAsync
 | 
			
		||||
                [ Query.insert Table.Post,
 | 
			
		||||
                    posts |> List.map (fun post -> [ "@data", Query.jsonbDocParam { post with Revisions = [] } ])
 | 
			
		||||
                    posts |> List.map (fun post -> [ jsonParam "@data" { post with Revisions = [] } ])
 | 
			
		||||
                  Revisions.insertSql Table.PostRevision,
 | 
			
		||||
                    revisions |> List.map (fun (postId, rev) -> Revisions.revParams postId rev) ]
 | 
			
		||||
        ()
 | 
			
		||||
@ -203,7 +203,7 @@ type PostgresPostData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "Post.updatePriorPermalinks"
 | 
			
		||||
        match! postExists postId webLogId with
 | 
			
		||||
        | true ->
 | 
			
		||||
            do! Update.partialById Table.Post (string postId) {| PriorPermalinks = permalinks |}
 | 
			
		||||
            do! Patch.byId Table.Post postId {| PriorPermalinks = permalinks |}
 | 
			
		||||
            return true
 | 
			
		||||
        | false -> return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -19,7 +20,7 @@ type PostgresTagMapData(log: ILogger) =
 | 
			
		||||
        log.LogTrace "TagMap.delete"
 | 
			
		||||
        let! exists = Document.existsByWebLog Table.TagMap tagMapId webLogId
 | 
			
		||||
        if exists then
 | 
			
		||||
            do! Delete.byId Table.TagMap (string tagMapId)
 | 
			
		||||
            do! Delete.byId Table.TagMap tagMapId
 | 
			
		||||
            return true
 | 
			
		||||
        else return false
 | 
			
		||||
    }
 | 
			
		||||
@ -58,7 +59,7 @@ type PostgresTagMapData(log: ILogger) =
 | 
			
		||||
            |> Sql.fromDataSource
 | 
			
		||||
            |> Sql.executeTransactionAsync
 | 
			
		||||
                [ Query.insert Table.TagMap,
 | 
			
		||||
                    tagMaps |> List.map (fun tagMap -> [ "@data", Query.jsonbDocParam tagMap ]) ]
 | 
			
		||||
                    tagMaps |> List.map (fun tagMap -> [ jsonParam "@data" tagMap ]) ]
 | 
			
		||||
        ()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
open Npgsql.FSharp
 | 
			
		||||
 | 
			
		||||
/// PostreSQL myWebLog theme data implementation
 | 
			
		||||
type PostgresThemeData(log: ILogger) =
 | 
			
		||||
@ -25,17 +25,17 @@ type PostgresThemeData(log: ILogger) =
 | 
			
		||||
    /// Does a given theme exist?
 | 
			
		||||
    let exists (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "Theme.exists"
 | 
			
		||||
        Exists.byId Table.Theme (string themeId)
 | 
			
		||||
        Exists.byId Table.Theme themeId
 | 
			
		||||
    
 | 
			
		||||
    /// Find a theme by its ID
 | 
			
		||||
    let findById (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "Theme.findById"
 | 
			
		||||
        Find.byId<Theme> Table.Theme (string themeId)
 | 
			
		||||
        Find.byId<ThemeId, Theme> Table.Theme themeId
 | 
			
		||||
    
 | 
			
		||||
    /// Find a theme by its ID (excludes the text of templates)
 | 
			
		||||
    let findByIdWithoutText (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "Theme.findByIdWithoutText"
 | 
			
		||||
        Custom.single (Query.Find.byId Table.Theme) [ "@id", Sql.string (string themeId) ] withoutTemplateText
 | 
			
		||||
        Custom.single (Query.Find.byId Table.Theme) [ idParam themeId ] withoutTemplateText
 | 
			
		||||
    
 | 
			
		||||
    /// Delete a theme by its ID
 | 
			
		||||
    let delete themeId = backgroundTask {
 | 
			
		||||
@ -45,7 +45,7 @@ type PostgresThemeData(log: ILogger) =
 | 
			
		||||
            do! Custom.nonQuery
 | 
			
		||||
                    $"""DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id;
 | 
			
		||||
                        DELETE FROM {Table.Theme}      WHERE {Query.whereById "@id"}"""
 | 
			
		||||
                    [ "@id", Sql.string (string themeId) ]
 | 
			
		||||
                    [ idParam themeId ]
 | 
			
		||||
            return true
 | 
			
		||||
        | false -> return false
 | 
			
		||||
    }
 | 
			
		||||
@ -75,32 +75,29 @@ type PostgresThemeAssetData(log: ILogger) =
 | 
			
		||||
    /// Delete all assets for the given theme
 | 
			
		||||
    let deleteByTheme (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "ThemeAsset.deleteByTheme"
 | 
			
		||||
        Custom.nonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ "@id", Sql.string (string themeId) ]
 | 
			
		||||
        Custom.nonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ]
 | 
			
		||||
    
 | 
			
		||||
    /// Find a theme asset by its ID
 | 
			
		||||
    let findById assetId =
 | 
			
		||||
        log.LogTrace "ThemeAsset.findById"
 | 
			
		||||
        let (ThemeAssetId (ThemeId themeId, path)) = assetId
 | 
			
		||||
        Custom.single
 | 
			
		||||
            $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @themeId AND path = @path"
 | 
			
		||||
            [ "@themeId", Sql.string themeId; "@path", Sql.string path ]
 | 
			
		||||
            $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @id AND path = @path"
 | 
			
		||||
            [ idParam themeId; "@path", Sql.string path ]
 | 
			
		||||
            (Map.toThemeAsset true)
 | 
			
		||||
    
 | 
			
		||||
    /// Get theme assets for the given theme (excludes data)
 | 
			
		||||
    let findByTheme (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "ThemeAsset.findByTheme"
 | 
			
		||||
        Custom.list
 | 
			
		||||
            $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset} WHERE theme_id = @themeId"
 | 
			
		||||
            [ "@themeId", Sql.string (string themeId) ]
 | 
			
		||||
            $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset} WHERE theme_id = @id"
 | 
			
		||||
            [ idParam themeId ]
 | 
			
		||||
            (Map.toThemeAsset false)
 | 
			
		||||
    
 | 
			
		||||
    /// Get theme assets for the given theme
 | 
			
		||||
    let findByThemeWithData (themeId: ThemeId) =
 | 
			
		||||
        log.LogTrace "ThemeAsset.findByThemeWithData"
 | 
			
		||||
        Custom.list
 | 
			
		||||
            $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @themeId"
 | 
			
		||||
            [ "@themeId", Sql.string (string themeId) ]
 | 
			
		||||
            (Map.toThemeAsset true)
 | 
			
		||||
        Custom.list $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] (Map.toThemeAsset true)
 | 
			
		||||
    
 | 
			
		||||
    /// Save a theme asset
 | 
			
		||||
    let save (asset: ThemeAsset) =
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -21,7 +22,7 @@ type PostgresUploadData(log: ILogger) =
 | 
			
		||||
    let upParams (upload: Upload) =
 | 
			
		||||
        [ webLogIdParam upload.WebLogId
 | 
			
		||||
          typedParam "updatedOn" upload.UpdatedOn
 | 
			
		||||
          "@id",   Sql.string (string upload.Id)
 | 
			
		||||
          idParam upload.Id
 | 
			
		||||
          "@path", Sql.string (string upload.Path)
 | 
			
		||||
          "@data", Sql.bytea  upload.Data ]
 | 
			
		||||
    
 | 
			
		||||
@ -33,7 +34,7 @@ type PostgresUploadData(log: ILogger) =
 | 
			
		||||
    /// Delete an uploaded file by its ID
 | 
			
		||||
    let delete uploadId webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "Upload.delete"
 | 
			
		||||
        let idParam = [ "@id", Sql.string (string uploadId) ]
 | 
			
		||||
        let idParam = [ idParam uploadId ]
 | 
			
		||||
        let! path =
 | 
			
		||||
            Custom.single
 | 
			
		||||
                $"SELECT path FROM {Table.Upload} WHERE id = @id AND web_log_id = @webLogId"
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -46,13 +47,13 @@ type PostgresWebLogData(log: ILogger) =
 | 
			
		||||
    /// Find a web log by its ID
 | 
			
		||||
    let findById (webLogId: WebLogId) = 
 | 
			
		||||
        log.LogTrace "WebLog.findById"
 | 
			
		||||
        Find.byId<WebLog> Table.WebLog (string webLogId)
 | 
			
		||||
        Find.byId<WebLogId, WebLog> Table.WebLog webLogId
 | 
			
		||||
    
 | 
			
		||||
    /// Update redirect rules for a web log
 | 
			
		||||
    let updateRedirectRules (webLog: WebLog) = backgroundTask {
 | 
			
		||||
        log.LogTrace "WebLog.updateRedirectRules"
 | 
			
		||||
        match! findById webLog.Id with
 | 
			
		||||
        | Some _ -> do! Update.partialById Table.WebLog (string webLog.Id) {| RedirectRules = webLog.RedirectRules |}
 | 
			
		||||
        | Some _ -> do! Patch.byId Table.WebLog webLog.Id {| RedirectRules = webLog.RedirectRules |}
 | 
			
		||||
        | None -> ()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@ -60,14 +61,14 @@ type PostgresWebLogData(log: ILogger) =
 | 
			
		||||
    let updateRssOptions (webLog: WebLog) = backgroundTask {
 | 
			
		||||
        log.LogTrace "WebLog.updateRssOptions"
 | 
			
		||||
        match! findById webLog.Id with
 | 
			
		||||
        | Some _ -> do! Update.partialById Table.WebLog (string webLog.Id) {| Rss = webLog.Rss |}
 | 
			
		||||
        | Some _ -> do! Patch.byId Table.WebLog webLog.Id {| Rss = webLog.Rss |}
 | 
			
		||||
        | None -> ()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Update settings for a web log
 | 
			
		||||
    let updateSettings (webLog: WebLog) =
 | 
			
		||||
        log.LogTrace "WebLog.updateSettings"
 | 
			
		||||
        Update.full Table.WebLog (string webLog.Id) webLog
 | 
			
		||||
        Update.byId Table.WebLog webLog.Id webLog
 | 
			
		||||
    
 | 
			
		||||
    interface IWebLogData with
 | 
			
		||||
        member _.Add webLog = add webLog
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data.Postgres
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
@ -24,13 +25,13 @@ type PostgresWebLogUserData(log: ILogger) =
 | 
			
		||||
                Custom.scalar
 | 
			
		||||
                    $" SELECT (   EXISTS (SELECT 1 FROM {Table.Page} WHERE {criteria})
 | 
			
		||||
                               OR EXISTS (SELECT 1 FROM {Table.Post} WHERE {criteria})
 | 
			
		||||
                              ) AS {existsName}"
 | 
			
		||||
                    [ "@criteria", Query.jsonbDocParam {| AuthorId = userId |} ]
 | 
			
		||||
                    Map.toExists
 | 
			
		||||
                              ) AS it"
 | 
			
		||||
                    [ jsonParam "@criteria" {| AuthorId = userId |} ]
 | 
			
		||||
                    toExists
 | 
			
		||||
            if isAuthor then
 | 
			
		||||
                return Error "User has pages or posts; cannot delete"
 | 
			
		||||
            else
 | 
			
		||||
                do! Delete.byId Table.WebLogUser (string userId)
 | 
			
		||||
                do! Delete.byId Table.WebLogUser userId
 | 
			
		||||
                return Ok true
 | 
			
		||||
        | None -> return Error "User does not exist"
 | 
			
		||||
    }
 | 
			
		||||
@ -67,8 +68,7 @@ type PostgresWebLogUserData(log: ILogger) =
 | 
			
		||||
            Configuration.dataSource ()
 | 
			
		||||
            |> Sql.fromDataSource
 | 
			
		||||
            |> Sql.executeTransactionAsync
 | 
			
		||||
                [ Query.insert Table.WebLogUser,
 | 
			
		||||
                    users |> List.map (fun user -> Query.docParameters (string user.Id) user) ]
 | 
			
		||||
                [ Query.insert Table.WebLogUser, users |> List.map (fun user -> [ jsonParam "@data" user ]) ]
 | 
			
		||||
        ()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@ -76,7 +76,7 @@ type PostgresWebLogUserData(log: ILogger) =
 | 
			
		||||
    let setLastSeen (userId: WebLogUserId) webLogId = backgroundTask {
 | 
			
		||||
        log.LogTrace "WebLogUser.setLastSeen"
 | 
			
		||||
        match! Document.existsByWebLog Table.WebLogUser userId webLogId with
 | 
			
		||||
        | true -> do! Update.partialById Table.WebLogUser (string userId) {| LastSeenOn = Some (Noda.now ()) |}
 | 
			
		||||
        | true -> do! Patch.byId Table.WebLogUser userId {| LastSeenOn = Some (Noda.now ()) |}
 | 
			
		||||
        | false -> ()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
namespace MyWebLog.Data
 | 
			
		||||
 | 
			
		||||
open BitBadger.Npgsql.Documents
 | 
			
		||||
open BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open BitBadger.Documents.Postgres
 | 
			
		||||
open Microsoft.Extensions.Logging
 | 
			
		||||
open MyWebLog
 | 
			
		||||
open MyWebLog.Data.Postgres
 | 
			
		||||
@ -31,8 +31,8 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
			
		||||
            // Theme tables
 | 
			
		||||
            if needsTable Table.Theme then
 | 
			
		||||
                isNew <- true
 | 
			
		||||
                Definition.createTable Table.Theme
 | 
			
		||||
                Definition.createKey   Table.Theme
 | 
			
		||||
                Query.Definition.ensureTable Table.Theme
 | 
			
		||||
                Query.Definition.ensureKey   Table.Theme
 | 
			
		||||
            if needsTable Table.ThemeAsset then
 | 
			
		||||
                $"CREATE TABLE {Table.ThemeAsset} (
 | 
			
		||||
                    theme_id    TEXT        NOT NULL,
 | 
			
		||||
@ -43,30 +43,29 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
			
		||||
            
 | 
			
		||||
            // Web log table
 | 
			
		||||
            if needsTable Table.WebLog then
 | 
			
		||||
                Definition.createTable Table.WebLog
 | 
			
		||||
                Definition.createKey   Table.WebLog
 | 
			
		||||
                Definition.createIndex Table.WebLog Optimized
 | 
			
		||||
                Query.Definition.ensureTable         Table.WebLog
 | 
			
		||||
                Query.Definition.ensureKey           Table.WebLog
 | 
			
		||||
                Query.Definition.ensureDocumentIndex Table.WebLog Optimized
 | 
			
		||||
            
 | 
			
		||||
            // Category table
 | 
			
		||||
            if needsTable Table.Category then
 | 
			
		||||
                Definition.createTable Table.Category
 | 
			
		||||
                Definition.createKey   Table.Category
 | 
			
		||||
                Definition.createIndex Table.Category Optimized
 | 
			
		||||
                Query.Definition.ensureTable         Table.Category
 | 
			
		||||
                Query.Definition.ensureKey           Table.Category
 | 
			
		||||
                Query.Definition.ensureDocumentIndex Table.Category Optimized
 | 
			
		||||
            
 | 
			
		||||
            // Web log user table
 | 
			
		||||
            if needsTable Table.WebLogUser then
 | 
			
		||||
                Definition.createTable Table.WebLogUser
 | 
			
		||||
                Definition.createKey   Table.WebLogUser
 | 
			
		||||
                Definition.createIndex Table.WebLogUser Optimized
 | 
			
		||||
                Query.Definition.ensureTable         Table.WebLogUser
 | 
			
		||||
                Query.Definition.ensureKey           Table.WebLogUser
 | 
			
		||||
                Query.Definition.ensureDocumentIndex Table.WebLogUser Optimized
 | 
			
		||||
            
 | 
			
		||||
            // Page tables
 | 
			
		||||
            if needsTable Table.Page then
 | 
			
		||||
                Definition.createTable Table.Page
 | 
			
		||||
                Definition.createKey   Table.Page
 | 
			
		||||
                $"CREATE INDEX page_web_log_idx   ON {Table.Page} ((data ->> '{nameof Page.Empty.WebLogId}'))"
 | 
			
		||||
                $"CREATE INDEX page_author_idx    ON {Table.Page} ((data ->> '{nameof Page.Empty.AuthorId}'))"
 | 
			
		||||
                $"CREATE INDEX page_permalink_idx ON {Table.Page}
 | 
			
		||||
                    ((data ->> '{nameof Page.Empty.WebLogId}'), (data ->> '{nameof Page.Empty.Permalink}'))"
 | 
			
		||||
                Query.Definition.ensureTable   Table.Page
 | 
			
		||||
                Query.Definition.ensureKey     Table.Page
 | 
			
		||||
                Query.Definition.ensureIndexOn Table.Page "author" [ nameof Page.Empty.AuthorId ]
 | 
			
		||||
                Query.Definition.ensureIndexOn
 | 
			
		||||
                    Table.Page "permalink" [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]
 | 
			
		||||
            if needsTable Table.PageRevision then
 | 
			
		||||
                $"CREATE TABLE {Table.PageRevision} (
 | 
			
		||||
                    page_id        TEXT        NOT NULL,
 | 
			
		||||
@ -76,15 +75,15 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
			
		||||
            
 | 
			
		||||
            // Post tables
 | 
			
		||||
            if needsTable Table.Post then
 | 
			
		||||
                Definition.createTable Table.Post
 | 
			
		||||
                Definition.createKey   Table.Post
 | 
			
		||||
                $"CREATE INDEX post_web_log_idx   ON {Table.Post} ((data ->> '{nameof Post.Empty.WebLogId}'))"
 | 
			
		||||
                $"CREATE INDEX post_author_idx    ON {Table.Post} ((data ->> '{nameof Post.Empty.AuthorId}'))"
 | 
			
		||||
                $"CREATE INDEX post_status_idx    ON {Table.Post}
 | 
			
		||||
                    ((data ->> '{nameof Post.Empty.WebLogId}'), (data ->> '{nameof Post.Empty.Status}'),
 | 
			
		||||
                     (data ->> '{nameof Post.Empty.UpdatedOn}'))"
 | 
			
		||||
                $"CREATE INDEX post_permalink_idx ON {Table.Post}
 | 
			
		||||
                    ((data ->> '{nameof Post.Empty.WebLogId}'), (data ->> '{nameof Post.Empty.Permalink}'))"
 | 
			
		||||
                Query.Definition.ensureTable   Table.Post
 | 
			
		||||
                Query.Definition.ensureKey     Table.Post
 | 
			
		||||
                Query.Definition.ensureIndexOn Table.Post "author" [ nameof Post.Empty.AuthorId ]
 | 
			
		||||
                Query.Definition.ensureIndexOn
 | 
			
		||||
                    Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ]
 | 
			
		||||
                Query.Definition.ensureIndexOn
 | 
			
		||||
                    Table.Post
 | 
			
		||||
                    "status"
 | 
			
		||||
                    [ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]
 | 
			
		||||
                $"CREATE INDEX post_category_idx  ON {Table.Post} USING GIN ((data['{nameof Post.Empty.CategoryIds}']))"
 | 
			
		||||
                $"CREATE INDEX post_tag_idx       ON {Table.Post} USING GIN ((data['{nameof Post.Empty.Tags}']))"
 | 
			
		||||
            if needsTable Table.PostRevision then
 | 
			
		||||
@ -94,16 +93,15 @@ type PostgresData(log: ILogger<PostgresData>, ser: JsonSerializer) =
 | 
			
		||||
                    revision_text  TEXT        NOT NULL,
 | 
			
		||||
                    PRIMARY KEY (post_id, as_of))"
 | 
			
		||||
            if needsTable Table.PostComment then
 | 
			
		||||
                Definition.createTable Table.PostComment
 | 
			
		||||
                Definition.createKey   Table.PostComment
 | 
			
		||||
                $"CREATE INDEX post_comment_post_idx ON {Table.PostComment}
 | 
			
		||||
                    ((data ->> '{nameof Comment.Empty.PostId}'))"
 | 
			
		||||
                Query.Definition.ensureTable   Table.PostComment
 | 
			
		||||
                Query.Definition.ensureKey     Table.PostComment
 | 
			
		||||
                Query.Definition.ensureIndexOn Table.PostComment "post" [ nameof Comment.Empty.PostId ]
 | 
			
		||||
            
 | 
			
		||||
            // Tag map table
 | 
			
		||||
            if needsTable Table.TagMap then
 | 
			
		||||
                Definition.createTable Table.TagMap
 | 
			
		||||
                Definition.createKey   Table.TagMap
 | 
			
		||||
                Definition.createIndex Table.TagMap Optimized
 | 
			
		||||
                Query.Definition.ensureTable         Table.TagMap
 | 
			
		||||
                Query.Definition.ensureKey           Table.TagMap
 | 
			
		||||
                Query.Definition.ensureDocumentIndex Table.TagMap Optimized
 | 
			
		||||
            
 | 
			
		||||
            // Uploaded file table
 | 
			
		||||
            if needsTable Table.Upload then
 | 
			
		||||
 | 
			
		||||
@ -50,14 +50,12 @@ type RedirectRuleMiddleware(next: RequestDelegate, log: ILogger<RedirectRuleMidd
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
open System
 | 
			
		||||
open BitBadger.Documents
 | 
			
		||||
open Microsoft.Extensions.DependencyInjection
 | 
			
		||||
open MyWebLog.Data
 | 
			
		||||
open Newtonsoft.Json
 | 
			
		||||
open Npgsql
 | 
			
		||||
 | 
			
		||||
// The PostgreSQL document library
 | 
			
		||||
module Postgres = BitBadger.Npgsql.FSharp.Documents
 | 
			
		||||
 | 
			
		||||
// The SQLite document library
 | 
			
		||||
module Sqlite = BitBadger.Sqlite.FSharp.Documents
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user