Incorporate doc lib v4 ordering
This commit is contained in:
		
							parent
							
								
									d4c0e4e26c
								
							
						
					
					
						commit
						cc3e41ddc5
					
				@ -5,9 +5,9 @@
 | 
				
			|||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
		<PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0-rc1" />
 | 
							<PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0-rc3" />
 | 
				
			||||||
		<PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0-rc1" />
 | 
							<PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0-rc3" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
 | 
							<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
 | 
							<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
 | 
							<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
 | 
				
			||||||
		<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
 | 
							<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
 | 
				
			||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
		<PackageReference Include="Npgsql.NodaTime" Version="8.0.3" />
 | 
							<PackageReference Include="Npgsql.NodaTime" Version="8.0.3" />
 | 
				
			||||||
		<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
 | 
							<PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
 | 
				
			||||||
		<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
 | 
							<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
 | 
				
			||||||
		<PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
							<PackageReference Update="FSharp.Core" Version="8.0.400" />
 | 
				
			||||||
	</ItemGroup>
 | 
						</ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ItemGroup>
 | 
						<ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -83,7 +83,7 @@ let webLogContains webLogId =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// A SQL string to select data from a table with the given JSON document contains criteria
 | 
					/// A SQL string to select data from a table with the given JSON document contains criteria
 | 
				
			||||||
let selectWithCriteria tableName =
 | 
					let selectWithCriteria tableName =
 | 
				
			||||||
    $"""{Query.find tableName} WHERE {Query.whereDataContains "@criteria"}"""
 | 
					    Query.byContains (Query.find tableName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Create the SQL and parameters for an IN clause
 | 
					/// Create the SQL and parameters for an IN clause
 | 
				
			||||||
let inClause<'T> colNameAndPrefix paramName (items: 'T list) =
 | 
					let inClause<'T> colNameAndPrefix paramName (items: 'T list) =
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,10 @@ type PostgresPageData(log: ILogger) =
 | 
				
			|||||||
        log.LogTrace "Page.pageExists"
 | 
					        log.LogTrace "Page.pageExists"
 | 
				
			||||||
        Document.existsByWebLog Table.Page pageId webLogId
 | 
					        Document.existsByWebLog Table.Page pageId webLogId
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    /// The query to get all pages ordered by title
 | 
				
			||||||
 | 
					    let sortedPages =
 | 
				
			||||||
 | 
					        selectWithCriteria Table.Page + Query.orderBy [ Field.Named $"i:{nameof Page.Empty.Title}" ] PostgreSQL
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
    // IMPLEMENTATION FUNCTIONS
 | 
					    // IMPLEMENTATION FUNCTIONS
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Add a page
 | 
					    /// Add a page
 | 
				
			||||||
@ -47,7 +51,7 @@ type PostgresPageData(log: ILogger) =
 | 
				
			|||||||
    let all webLogId =
 | 
					    let all webLogId =
 | 
				
			||||||
        log.LogTrace "Page.all"
 | 
					        log.LogTrace "Page.all"
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')"
 | 
					            sortedPages
 | 
				
			||||||
            [ webLogContains webLogId ]
 | 
					            [ webLogContains webLogId ]
 | 
				
			||||||
            (fun row -> { fromData<Page> row with Text = ""; Metadata = []; PriorPermalinks = [] }) 
 | 
					            (fun row -> { fromData<Page> row with Text = ""; Metadata = []; PriorPermalinks = [] }) 
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -133,17 +137,13 @@ type PostgresPageData(log: ILogger) =
 | 
				
			|||||||
    let findListed webLogId =
 | 
					    let findListed webLogId =
 | 
				
			||||||
        log.LogTrace "Page.findListed"
 | 
					        log.LogTrace "Page.findListed"
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')"
 | 
					            sortedPages [ jsonParam "@criteria" {| webLogDoc webLogId with IsInPageList = true |} ] pageWithoutText
 | 
				
			||||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with IsInPageList = true |} ]
 | 
					 | 
				
			||||||
            pageWithoutText
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// 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"
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Page}
 | 
					            $"{sortedPages} LIMIT @pageSize OFFSET @toSkip"
 | 
				
			||||||
               ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')
 | 
					 | 
				
			||||||
               LIMIT @pageSize OFFSET @toSkip"
 | 
					 | 
				
			||||||
            [ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ]
 | 
					            [ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ]
 | 
				
			||||||
            (fun row -> { fromData<Page> row with Metadata = []; PriorPermalinks = [] })
 | 
					            (fun row -> { fromData<Page> row with Metadata = []; PriorPermalinks = [] })
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
				
			|||||||
@ -124,21 +124,24 @@ type PostgresPostData(log: ILogger) =
 | 
				
			|||||||
        log.LogTrace "Post.findPageOfCategorizedPosts"
 | 
					        log.LogTrace "Post.findPageOfCategorizedPosts"
 | 
				
			||||||
        let catSql, catParam = arrayContains (nameof Post.Empty.CategoryIds) string categoryIds
 | 
					        let catSql, catParam = arrayContains (nameof Post.Empty.CategoryIds) string categoryIds
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Post}
 | 
					            $"""{selectWithCriteria Table.Post}
 | 
				
			||||||
                 AND {catSql}
 | 
					                  AND {catSql}
 | 
				
			||||||
               ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC
 | 
					                {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catParam ]
 | 
					            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catParam ]
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Get a page of posts for the given web log (excludes text and revisions)
 | 
					    /// Get a page of posts for the given web log (excludes text and revisions)
 | 
				
			||||||
    let findPageOfPosts webLogId pageNbr postsPerPage =
 | 
					    let findPageOfPosts webLogId pageNbr postsPerPage =
 | 
				
			||||||
        log.LogTrace "Post.findPageOfPosts"
 | 
					        log.LogTrace "Post.findPageOfPosts"
 | 
				
			||||||
 | 
					        let order =
 | 
				
			||||||
 | 
					            Query.orderBy
 | 
				
			||||||
 | 
					                [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC NULLS FIRST"
 | 
				
			||||||
 | 
					                  Field.Named (nameof Post.Empty.UpdatedOn) ]
 | 
				
			||||||
 | 
					                PostgreSQL
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Post}
 | 
					            $"{selectWithCriteria Table.Post}{order}
 | 
				
			||||||
               ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC NULLS FIRST,
 | 
					              LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
				
			||||||
                        data->>'{nameof Post.Empty.UpdatedOn}'
 | 
					 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					 | 
				
			||||||
            [ webLogContains webLogId ]
 | 
					            [ webLogContains webLogId ]
 | 
				
			||||||
            postWithoutText
 | 
					            postWithoutText
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -146,9 +149,9 @@ type PostgresPostData(log: ILogger) =
 | 
				
			|||||||
    let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
 | 
					    let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
 | 
				
			||||||
        log.LogTrace "Post.findPageOfPublishedPosts"
 | 
					        log.LogTrace "Post.findPageOfPublishedPosts"
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Post}
 | 
					            $"""{selectWithCriteria Table.Post}
 | 
				
			||||||
               ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC
 | 
					                {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ]
 | 
					            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ]
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -156,10 +159,10 @@ type PostgresPostData(log: ILogger) =
 | 
				
			|||||||
    let findPageOfTaggedPosts webLogId (tag: string) pageNbr postsPerPage =
 | 
					    let findPageOfTaggedPosts webLogId (tag: string) pageNbr postsPerPage =
 | 
				
			||||||
        log.LogTrace "Post.findPageOfTaggedPosts"
 | 
					        log.LogTrace "Post.findPageOfTaggedPosts"
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{selectWithCriteria Table.Post}
 | 
					            $"""{selectWithCriteria Table.Post}
 | 
				
			||||||
                 AND data['{nameof Post.Empty.Tags}'] @> @tag
 | 
					                  AND data['{nameof Post.Empty.Tags}'] @> @tag
 | 
				
			||||||
               ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC
 | 
					                {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; jsonParam "@tag" [| tag |] ]
 | 
					            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; jsonParam "@tag" [| tag |] ]
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -170,10 +173,10 @@ type PostgresPostData(log: ILogger) =
 | 
				
			|||||||
            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}
 | 
					            [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}
 | 
				
			||||||
              "@publishedOn", Sql.timestamptz (publishedOn.ToDateTimeOffset()) ]
 | 
					              "@publishedOn", Sql.timestamptz (publishedOn.ToDateTimeOffset()) ]
 | 
				
			||||||
        let query op direction =
 | 
					        let query op direction =
 | 
				
			||||||
            $"{selectWithCriteria Table.Post}
 | 
					            $"""{selectWithCriteria Table.Post}
 | 
				
			||||||
                 AND (data->>'{nameof Post.Empty.PublishedOn}')::timestamp with time zone %s{op} @publishedOn
 | 
					                  AND (data->>'{nameof Post.Empty.PublishedOn}')::timestamp with time zone %s{op} @publishedOn
 | 
				
			||||||
               ORDER BY data->>'{nameof Post.Empty.PublishedOn}' %s{direction}
 | 
					                {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} %s{direction}" ] PostgreSQL} 
 | 
				
			||||||
               LIMIT 1"
 | 
					                LIMIT 1"""
 | 
				
			||||||
        let! older = Custom.list (query "<" "DESC") (queryParams ()) postWithoutLinks
 | 
					        let! older = Custom.list (query "<" "DESC") (queryParams ()) postWithoutLinks
 | 
				
			||||||
        let! newer = Custom.list (query ">" "")     (queryParams ()) postWithoutLinks
 | 
					        let! newer = Custom.list (query ">" "")     (queryParams ()) postWithoutLinks
 | 
				
			||||||
        return List.tryHead older, List.tryHead newer
 | 
					        return List.tryHead older, List.tryHead newer
 | 
				
			||||||
 | 
				
			|||||||
@ -17,11 +17,11 @@ type PostgresThemeData(log: ILogger) =
 | 
				
			|||||||
    /// Retrieve all themes (except 'admin'; excludes template text)
 | 
					    /// Retrieve all themes (except 'admin'; excludes template text)
 | 
				
			||||||
    let all () =
 | 
					    let all () =
 | 
				
			||||||
        log.LogTrace "Theme.all"
 | 
					        log.LogTrace "Theme.all"
 | 
				
			||||||
 | 
					        let fields = [ Field.NE (nameof Theme.Empty.Id) "admin" ]
 | 
				
			||||||
        Custom.list
 | 
					        Custom.list
 | 
				
			||||||
            $"{Query.find Table.Theme}
 | 
					            (Query.byFields (Query.find Table.Theme) Any fields
 | 
				
			||||||
               WHERE data->>'{nameof Theme.Empty.Id}' <> 'admin'
 | 
					                + Query.orderBy [ Field.Named (nameof Theme.Empty.Id) ] PostgreSQL)
 | 
				
			||||||
               ORDER BY data->>'{nameof Theme.Empty.Id}'"
 | 
					            (addFieldParams fields [])
 | 
				
			||||||
            []
 | 
					 | 
				
			||||||
            withoutTemplateText
 | 
					            withoutTemplateText
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Does a given theme exist?
 | 
					    /// Does a given theme exist?
 | 
				
			||||||
 | 
				
			|||||||
@ -49,10 +49,8 @@ type PostgresWebLogUserData(log: ILogger) =
 | 
				
			|||||||
    /// Get all users for the given web log
 | 
					    /// Get all users for the given web log
 | 
				
			||||||
    let findByWebLog webLogId =
 | 
					    let findByWebLog webLogId =
 | 
				
			||||||
        log.LogTrace "WebLogUser.findByWebLog"
 | 
					        log.LogTrace "WebLogUser.findByWebLog"
 | 
				
			||||||
        Custom.list
 | 
					        Find.byContainsOrdered<WebLogUser>
 | 
				
			||||||
            $"{selectWithCriteria Table.WebLogUser} ORDER BY LOWER(data->>'{nameof WebLogUser.Empty.PreferredName}')"
 | 
					            Table.WebLogUser (webLogDoc webLogId) [ Field.Named $"i:{nameof WebLogUser.Empty.PreferredName}" ]
 | 
				
			||||||
            [ webLogContains webLogId ]
 | 
					 | 
				
			||||||
            fromData<WebLogUser>
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Find the names of users by their IDs for the given web log
 | 
					    /// Find the names of users by their IDs for the given web log
 | 
				
			||||||
    let findNames webLogId (userIds: WebLogUserId list) = backgroundTask {
 | 
					    let findNames webLogId (userIds: WebLogUserId list) = backgroundTask {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,16 +16,18 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
 | 
				
			|||||||
    let parentIdField = nameof Category.Empty.ParentId
 | 
					    let parentIdField = nameof Category.Empty.ParentId
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Count all categories for the given web log
 | 
					    /// Count all categories for the given web log
 | 
				
			||||||
    let countAll webLogId =
 | 
					    let countAll webLogId = backgroundTask {
 | 
				
			||||||
        log.LogTrace "Category.countAll"
 | 
					        log.LogTrace "Category.countAll"
 | 
				
			||||||
        Document.countByWebLog Table.Category webLogId conn
 | 
					        let! count = conn.countByFields Table.Category Any [ webLogField webLogId ]
 | 
				
			||||||
 | 
					        return int count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Count all top-level categories for the given web log
 | 
					    /// Count all top-level categories for the given web log
 | 
				
			||||||
    let countTopLevel webLogId =
 | 
					    let countTopLevel webLogId = backgroundTask {
 | 
				
			||||||
        log.LogTrace "Category.countTopLevel"
 | 
					        log.LogTrace "Category.countTopLevel"
 | 
				
			||||||
        let fields = [ webLogField webLogId; Field.NEX parentIdField ]
 | 
					        let! count = conn.countByFields Table.Category All [ webLogField webLogId; Field.NEX parentIdField ]
 | 
				
			||||||
        conn.customScalar
 | 
					        return int count
 | 
				
			||||||
            (Query.byFields (Query.count Table.Category) All fields) (addFieldParams fields []) (toCount >> int)
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Find all categories for the given web log
 | 
					    /// Find all categories for the given web log
 | 
				
			||||||
    let findByWebLog webLogId =
 | 
					    let findByWebLog webLogId =
 | 
				
			||||||
 | 
				
			|||||||
@ -251,12 +251,6 @@ module Document =
 | 
				
			|||||||
        /// A query to select from a table by its web log ID
 | 
					        /// A query to select from a table by its web log ID
 | 
				
			||||||
        let selectByWebLog table =
 | 
					        let selectByWebLog table =
 | 
				
			||||||
            Query.statementWhere (Query.find table) 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 [ webLogField webLogId ] conn
 | 
					 | 
				
			||||||
        return int count
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Functions to support revisions
 | 
					/// Functions to support revisions
 | 
				
			||||||
 | 
				
			|||||||
@ -17,8 +17,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
    /// The JSON field name for the "is in page list" flag
 | 
					    /// The JSON field name for the "is in page list" flag
 | 
				
			||||||
    let pgListName = nameof Page.Empty.IsInPageList
 | 
					    let pgListName = nameof Page.Empty.IsInPageList
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// The JSON field for the title of the page
 | 
					    /// Query to return pages sorted by title
 | 
				
			||||||
    let titleField = $"data->>'{nameof Page.Empty.Title}'"
 | 
					    let sortedPages fields =
 | 
				
			||||||
 | 
					        Query.byFields (Query.find Table.Page) All fields
 | 
				
			||||||
 | 
					            + Query.orderBy [ Field.Named $"i:{nameof Page.Empty.Title}" ] SQLite
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // SUPPORT FUNCTIONS
 | 
					    // SUPPORT FUNCTIONS
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -52,21 +54,23 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
        log.LogTrace "Page.all"
 | 
					        log.LogTrace "Page.all"
 | 
				
			||||||
        let field = [ webLogField webLogId ]
 | 
					        let field = [ webLogField webLogId ]
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})"
 | 
					            (sortedPages field)
 | 
				
			||||||
            (addFieldParams field [])
 | 
					            (addFieldParams field [])
 | 
				
			||||||
            (fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] })
 | 
					            (fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] })
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Count all pages for the given web log
 | 
					    /// Count all pages for the given web log
 | 
				
			||||||
    let countAll webLogId =
 | 
					    let countAll webLogId = backgroundTask {
 | 
				
			||||||
        log.LogTrace "Page.countAll"
 | 
					        log.LogTrace "Page.countAll"
 | 
				
			||||||
        Document.countByWebLog Table.Page webLogId conn
 | 
					        let! count = conn.countByFields Table.Page Any [ webLogField webLogId ]
 | 
				
			||||||
 | 
					        return int count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// 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 =
 | 
					    let countListed webLogId = backgroundTask {
 | 
				
			||||||
        log.LogTrace "Page.countListed"
 | 
					        log.LogTrace "Page.countListed"
 | 
				
			||||||
        let fields = [ webLogField webLogId; Field.EQ pgListName true ]
 | 
					        let! count = conn.countByFields Table.Page All [ webLogField webLogId; Field.EQ pgListName true ]
 | 
				
			||||||
        conn.customScalar
 | 
					        return int count
 | 
				
			||||||
            (Query.byFields (Query.count Table.Page) All fields) (addFieldParams fields []) (toCount >> int)
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Find a page by its ID (without revisions and prior permalinks)
 | 
					    /// Find a page by its ID (without revisions and prior permalinks)
 | 
				
			||||||
    let findById (pageId: PageId) webLogId = backgroundTask {
 | 
					    let findById (pageId: PageId) webLogId = backgroundTask {
 | 
				
			||||||
@ -131,17 +135,14 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
        log.LogTrace "Page.findListed"
 | 
					        log.LogTrace "Page.findListed"
 | 
				
			||||||
        let fields = [ webLogField webLogId; Field.EQ pgListName true ]
 | 
					        let fields = [ webLogField webLogId; Field.EQ pgListName true ]
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{Query.byFields (Query.find Table.Page) All fields} ORDER BY LOWER({titleField})"
 | 
					            (sortedPages fields) (addFieldParams fields []) (fun rdr -> { fromData<Page> rdr with Text = "" })
 | 
				
			||||||
            (addFieldParams fields [])
 | 
					 | 
				
			||||||
            (fun rdr -> { fromData<Page> rdr with Text = "" })
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// 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"
 | 
				
			||||||
        let field = [ webLogField webLogId ]
 | 
					        let field = [ webLogField webLogId ]
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})
 | 
					            $"{sortedPages field} LIMIT @pageSize OFFSET @toSkip"
 | 
				
			||||||
                LIMIT @pageSize OFFSET @toSkip"
 | 
					 | 
				
			||||||
            (addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ])
 | 
					            (addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ])
 | 
				
			||||||
            (fun rdr -> { pageWithoutLinks rdr with Metadata = [] })
 | 
					            (fun rdr -> { pageWithoutLinks rdr with Metadata = [] })
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
    let linkName = 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 publishName = nameof Post.Empty.PublishedOn
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// The name of the JSON field for the post's status
 | 
					    /// The name of the JSON field for the post's status
 | 
				
			||||||
    let statName = nameof Post.Empty.Status
 | 
					    let statName = nameof Post.Empty.Status
 | 
				
			||||||
@ -60,11 +60,11 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// 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 =
 | 
					    let countByStatus (status: PostStatus) webLogId = backgroundTask {
 | 
				
			||||||
        log.LogTrace "Post.countByStatus"
 | 
					        log.LogTrace "Post.countByStatus"
 | 
				
			||||||
        let fields = [ webLogField webLogId; Field.EQ statName (string status) ]
 | 
					        let! count = conn.countByFields Table.Post All [ webLogField webLogId; Field.EQ statName (string status) ]
 | 
				
			||||||
        conn.customScalar
 | 
					        return int count
 | 
				
			||||||
            (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)
 | 
					    /// Find a post by its ID for the given web log (excluding revisions)
 | 
				
			||||||
    let findById (postId: PostId) webLogId = backgroundTask {
 | 
					    let findById (postId: PostId) webLogId = backgroundTask {
 | 
				
			||||||
@ -132,19 +132,20 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
        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
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{publishedPostByWebLog} AND {catSql}
 | 
					            $"""{publishedPostByWebLog} AND {catSql}
 | 
				
			||||||
               ORDER BY {publishField} DESC
 | 
					                {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            (webLogParam webLogId :: catParams)
 | 
					            (webLogParam webLogId :: catParams)
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Get a page of posts for the given web log (excludes text and revisions)
 | 
					    /// Get a page of posts for the given web log (excludes text and revisions)
 | 
				
			||||||
    let findPageOfPosts webLogId pageNbr postsPerPage =
 | 
					    let findPageOfPosts webLogId pageNbr postsPerPage =
 | 
				
			||||||
        log.LogTrace "Post.findPageOfPosts"
 | 
					        log.LogTrace "Post.findPageOfPosts"
 | 
				
			||||||
 | 
					        let order =
 | 
				
			||||||
 | 
					            Query.orderBy
 | 
				
			||||||
 | 
					                [ Field.Named $"{publishName} DESC NULLS FIRST"; Field.Named (nameof Post.Empty.UpdatedOn) ] SQLite
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{postByWebLog}
 | 
					            $"{postByWebLog}{order} LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
				
			||||||
               ORDER BY {publishField} DESC NULLS FIRST, data->>'{nameof Post.Empty.UpdatedOn}'
 | 
					 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					 | 
				
			||||||
            [ webLogParam webLogId ]
 | 
					            [ webLogParam webLogId ]
 | 
				
			||||||
            postWithoutText
 | 
					            postWithoutText
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -152,9 +153,9 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
    let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
 | 
					    let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
 | 
				
			||||||
        log.LogTrace "Post.findPageOfPublishedPosts"
 | 
					        log.LogTrace "Post.findPageOfPublishedPosts"
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{publishedPostByWebLog}
 | 
					            $"""{publishedPostByWebLog}
 | 
				
			||||||
               ORDER BY {publishField} DESC
 | 
					                {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            [ webLogParam webLogId ]
 | 
					            [ webLogParam webLogId ]
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -163,26 +164,28 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
 | 
				
			|||||||
        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 ]
 | 
				
			||||||
        conn.customList
 | 
					        conn.customList
 | 
				
			||||||
            $"{publishedPostByWebLog} AND {tagSql}
 | 
					            $"""{publishedPostByWebLog} AND {tagSql}
 | 
				
			||||||
               ORDER BY {publishField} DESC
 | 
					                {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
 | 
				
			||||||
               LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
 | 
					                LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
 | 
				
			||||||
            (webLogParam webLogId :: tagParams)
 | 
					            (webLogParam webLogId :: tagParams)
 | 
				
			||||||
            postWithoutLinks
 | 
					            postWithoutLinks
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// 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"
 | 
				
			||||||
        let parameters = [ webLogParam webLogId; sqlParam "@publishedOn" (instantParam publishedOn) ]
 | 
					        let adjacent op order =
 | 
				
			||||||
        let! older =
 | 
					            let fields = [
 | 
				
			||||||
 | 
					                webLogField webLogId
 | 
				
			||||||
 | 
					                Field.EQ (nameof Post.Empty.Status) (string Published)
 | 
				
			||||||
 | 
					                (if op = "<" then Field.LT else Field.GT) publishName (instantParam publishedOn)
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
            conn.customSingle
 | 
					            conn.customSingle
 | 
				
			||||||
                $"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
 | 
					                (Query.byFields (Query.find Table.Post) All fields
 | 
				
			||||||
                parameters
 | 
					                    + Query.orderBy [ Field.Named (publishName + order) ] SQLite + " LIMIT 1")
 | 
				
			||||||
                postWithoutLinks
 | 
					                (addFieldParams fields [])
 | 
				
			||||||
        let! newer =
 | 
					 | 
				
			||||||
            conn.customSingle
 | 
					 | 
				
			||||||
                $"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
 | 
					 | 
				
			||||||
                parameters
 | 
					 | 
				
			||||||
                postWithoutLinks
 | 
					                postWithoutLinks
 | 
				
			||||||
 | 
					        let! older = adjacent "<" " DESC"
 | 
				
			||||||
 | 
					        let! newer = adjacent ">" ""
 | 
				
			||||||
        return older, newer
 | 
					        return older, newer
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
				
			|||||||
@ -10,8 +10,8 @@ open MyWebLog.Data
 | 
				
			|||||||
/// SQLite myWebLog theme data implementation
 | 
					/// SQLite myWebLog theme data implementation
 | 
				
			||||||
type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
 | 
					type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// The JSON field for the theme ID
 | 
					    /// The name of the theme ID field
 | 
				
			||||||
    let idField = $"data->>'{nameof Theme.Empty.Id}'"
 | 
					    let idName = nameof Theme.Empty.Id
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Convert a document to a theme with no template text
 | 
					    /// Convert a document to a theme with no template text
 | 
				
			||||||
    let withoutTemplateText (rdr: SqliteDataReader) =
 | 
					    let withoutTemplateText (rdr: SqliteDataReader) =
 | 
				
			||||||
@ -25,7 +25,11 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
 | 
				
			|||||||
    /// Retrieve all themes (except 'admin'; excludes template text)
 | 
					    /// Retrieve all themes (except 'admin'; excludes template text)
 | 
				
			||||||
    let all () =
 | 
					    let all () =
 | 
				
			||||||
        log.LogTrace "Theme.all"
 | 
					        log.LogTrace "Theme.all"
 | 
				
			||||||
        conn.customList $"{Query.find Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}" [] withoutTemplateText
 | 
					        let fields = [ Field.NE idName "admin" ]
 | 
				
			||||||
 | 
					        conn.customList
 | 
				
			||||||
 | 
					            (Query.byFields (Query.find Table.Theme) Any fields + Query.orderBy [ Field.Named idName ] SQLite)
 | 
				
			||||||
 | 
					            (addFieldParams fields [])
 | 
				
			||||||
 | 
					            withoutTemplateText
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Does a given theme exist?
 | 
					    /// Does a given theme exist?
 | 
				
			||||||
    let exists (themeId: ThemeId) =
 | 
					    let exists (themeId: ThemeId) =
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
namespace MyWebLog.Data
 | 
					namespace MyWebLog.Data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
open System
 | 
					open System
 | 
				
			||||||
open System.Threading.Tasks
 | 
					 | 
				
			||||||
open BitBadger.Documents
 | 
					open BitBadger.Documents
 | 
				
			||||||
open BitBadger.Documents.Sqlite
 | 
					open BitBadger.Documents.Sqlite
 | 
				
			||||||
open Microsoft.Data.Sqlite
 | 
					open Microsoft.Data.Sqlite
 | 
				
			||||||
@ -24,108 +23,107 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
 | 
				
			|||||||
        let needsTable table =
 | 
					        let needsTable table =
 | 
				
			||||||
            not (List.contains table tables)
 | 
					            not (List.contains table tables)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let jsonTable table =
 | 
					        let creatingTable = "Creating {Table} table..."
 | 
				
			||||||
            $"{Query.Definition.ensureTable table}; {Query.Definition.ensureKey table SQLite}"
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let tasks =
 | 
					        // Theme tables
 | 
				
			||||||
            seq {
 | 
					        if needsTable Table.Theme then
 | 
				
			||||||
                // Theme tables
 | 
					            log.LogInformation(creatingTable, Table.Theme)
 | 
				
			||||||
                if needsTable Table.Theme then jsonTable Table.Theme
 | 
					            do! conn.ensureTable Table.Theme
 | 
				
			||||||
                if needsTable Table.ThemeAsset then
 | 
					            
 | 
				
			||||||
 | 
					        if needsTable Table.ThemeAsset then
 | 
				
			||||||
 | 
					            log.LogInformation(creatingTable, Table.ThemeAsset)
 | 
				
			||||||
 | 
					            do! conn.customNonQuery
 | 
				
			||||||
                    $"CREATE TABLE {Table.ThemeAsset} (
 | 
					                    $"CREATE TABLE {Table.ThemeAsset} (
 | 
				
			||||||
                        theme_id    TEXT NOT NULL,
 | 
					                        theme_id    TEXT NOT NULL,
 | 
				
			||||||
                        path        TEXT NOT NULL,
 | 
					                        path        TEXT NOT NULL,
 | 
				
			||||||
                        updated_on  TEXT NOT NULL,
 | 
					                        updated_on  TEXT NOT NULL,
 | 
				
			||||||
                        data        BLOB NOT NULL,
 | 
					                        data        BLOB NOT NULL,
 | 
				
			||||||
                        PRIMARY KEY (theme_id, path))"
 | 
					                        PRIMARY KEY (theme_id, path))" []
 | 
				
			||||||
                
 | 
					        
 | 
				
			||||||
                // Web log table
 | 
					        // Web log table
 | 
				
			||||||
                if needsTable Table.WebLog then jsonTable Table.WebLog
 | 
					        if needsTable Table.WebLog then
 | 
				
			||||||
                
 | 
					            log.LogInformation(creatingTable, Table.WebLog)
 | 
				
			||||||
                // Category table
 | 
					            do! conn.ensureTable Table.WebLog
 | 
				
			||||||
                if needsTable Table.Category then
 | 
					        
 | 
				
			||||||
                    $"""{jsonTable Table.Category};
 | 
					        // Category table
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					        if needsTable Table.Category then
 | 
				
			||||||
                            Table.Category "web_log" [ nameof Category.Empty.WebLogId ] SQLite}"""
 | 
					            log.LogInformation(creatingTable, Table.Category)
 | 
				
			||||||
                
 | 
					            do! conn.ensureTable Table.Category
 | 
				
			||||||
                // Web log user table
 | 
					            do! conn.ensureFieldIndex Table.Category "web_log" [ nameof Category.Empty.WebLogId ]
 | 
				
			||||||
                if needsTable Table.WebLogUser then
 | 
					        
 | 
				
			||||||
                    $"""{jsonTable Table.WebLogUser};
 | 
					        // Web log user table
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					        if needsTable Table.WebLogUser then
 | 
				
			||||||
                             Table.WebLogUser
 | 
					            log.LogInformation(creatingTable, Table.WebLogUser)
 | 
				
			||||||
                             "email"
 | 
					            do! conn.ensureTable Table.WebLogUser
 | 
				
			||||||
                             [ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]
 | 
					            do! conn.ensureFieldIndex
 | 
				
			||||||
                             SQLite}"""
 | 
					                    Table.WebLogUser "email" [ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]
 | 
				
			||||||
                
 | 
					        
 | 
				
			||||||
                // Page tables
 | 
					        // Page tables
 | 
				
			||||||
                if needsTable Table.Page then
 | 
					        if needsTable Table.Page then
 | 
				
			||||||
                    $"""{jsonTable Table.Page};
 | 
					            log.LogInformation(creatingTable, Table.Page)
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn Table.Page "author" [ nameof Page.Empty.AuthorId ] SQLite};
 | 
					            do! conn.ensureTable Table.Page
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					            do! conn.ensureFieldIndex Table.Page "author" [ nameof Page.Empty.AuthorId ]
 | 
				
			||||||
                             Table.Page
 | 
					            do! conn.ensureFieldIndex Table.Page "permalink" [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]
 | 
				
			||||||
                             "permalink"
 | 
					        
 | 
				
			||||||
                             [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]
 | 
					        if needsTable Table.PageRevision then
 | 
				
			||||||
                             SQLite}"""
 | 
					            log.LogInformation(creatingTable, Table.PageRevision)
 | 
				
			||||||
                if needsTable Table.PageRevision then
 | 
					            do! conn.customNonQuery
 | 
				
			||||||
                    $"CREATE TABLE {Table.PageRevision} (
 | 
					                    $"CREATE TABLE {Table.PageRevision} (
 | 
				
			||||||
                        page_id        TEXT NOT NULL,
 | 
					                        page_id        TEXT NOT NULL,
 | 
				
			||||||
                        as_of          TEXT NOT NULL,
 | 
					                        as_of          TEXT NOT NULL,
 | 
				
			||||||
                        revision_text  TEXT NOT NULL,
 | 
					                        revision_text  TEXT NOT NULL,
 | 
				
			||||||
                        PRIMARY KEY (page_id, as_of))"
 | 
					                        PRIMARY KEY (page_id, as_of))" []
 | 
				
			||||||
                
 | 
					        
 | 
				
			||||||
                // Post tables
 | 
					        // Post tables
 | 
				
			||||||
                if needsTable Table.Post then
 | 
					        if needsTable Table.Post then
 | 
				
			||||||
                    $"""{jsonTable Table.Post};
 | 
					            log.LogInformation(creatingTable, Table.Post)
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn Table.Post "author" [ nameof Post.Empty.AuthorId ] SQLite};
 | 
					            do! conn.ensureTable Table.Post
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					            do! conn.ensureFieldIndex Table.Post "author" [ nameof Post.Empty.AuthorId ]
 | 
				
			||||||
                             Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ] SQLite};
 | 
					            do! conn.ensureFieldIndex Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ]
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					            do! conn.ensureFieldIndex
 | 
				
			||||||
                             Table.Post
 | 
					                    Table.Post
 | 
				
			||||||
                             "status"
 | 
					                    "status"
 | 
				
			||||||
                             [ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]
 | 
					                    [ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]
 | 
				
			||||||
                             SQLite}"""
 | 
					            // TODO: index categories by post?
 | 
				
			||||||
                      // TODO: index categories by post?
 | 
					        
 | 
				
			||||||
                if needsTable Table.PostRevision then
 | 
					        if needsTable Table.PostRevision then
 | 
				
			||||||
 | 
					            log.LogInformation(creatingTable, Table.PostRevision)
 | 
				
			||||||
 | 
					            do! conn.customNonQuery
 | 
				
			||||||
                    $"CREATE TABLE {Table.PostRevision} (
 | 
					                    $"CREATE TABLE {Table.PostRevision} (
 | 
				
			||||||
                        post_id        TEXT NOT NULL,
 | 
					                        post_id        TEXT NOT NULL,
 | 
				
			||||||
                        as_of          TEXT NOT NULL,
 | 
					                        as_of          TEXT NOT NULL,
 | 
				
			||||||
                        revision_text  TEXT NOT NULL,
 | 
					                        revision_text  TEXT NOT NULL,
 | 
				
			||||||
                        PRIMARY KEY (post_id, as_of))"
 | 
					                        PRIMARY KEY (post_id, as_of))" []
 | 
				
			||||||
                if needsTable Table.PostComment then
 | 
					        
 | 
				
			||||||
                    $"""{jsonTable Table.PostComment};
 | 
					        if needsTable Table.PostComment then
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					            log.LogInformation(creatingTable, Table.PostComment)
 | 
				
			||||||
                            Table.PostComment "post" [ nameof Comment.Empty.PostId ] SQLite}"""
 | 
					            do! conn.ensureTable Table.PostComment
 | 
				
			||||||
                
 | 
					            do! conn.ensureFieldIndex Table.PostComment "post" [ nameof Comment.Empty.PostId ]
 | 
				
			||||||
                // Tag map table
 | 
					        
 | 
				
			||||||
                if needsTable Table.TagMap then
 | 
					        // Tag map table
 | 
				
			||||||
                    $"""{jsonTable Table.TagMap};
 | 
					        if needsTable Table.TagMap then
 | 
				
			||||||
                        {Query.Definition.ensureIndexOn
 | 
					            log.LogInformation(creatingTable, Table.TagMap)
 | 
				
			||||||
                             Table.TagMap
 | 
					            do! conn.ensureTable Table.TagMap
 | 
				
			||||||
                             "url"
 | 
					            do! conn.ensureFieldIndex Table.TagMap "url" [ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]
 | 
				
			||||||
                             [ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]
 | 
					        
 | 
				
			||||||
                             SQLite}"""
 | 
					        // Uploaded file table
 | 
				
			||||||
                
 | 
					        if needsTable Table.Upload then
 | 
				
			||||||
                // Uploaded file table
 | 
					            log.LogInformation(creatingTable, Table.Upload)
 | 
				
			||||||
                if needsTable Table.Upload then
 | 
					            do! conn.customNonQuery
 | 
				
			||||||
                    $"CREATE TABLE {Table.Upload} (
 | 
					                    $"CREATE TABLE {Table.Upload} (
 | 
				
			||||||
                        id          TEXT PRIMARY KEY,
 | 
					                        id          TEXT PRIMARY KEY,
 | 
				
			||||||
                        web_log_id  TEXT NOT NULL,
 | 
					                        web_log_id  TEXT NOT NULL,
 | 
				
			||||||
                        path        TEXT NOT NULL,
 | 
					                        path        TEXT NOT NULL,
 | 
				
			||||||
                        updated_on  TEXT NOT NULL,
 | 
					                        updated_on  TEXT NOT NULL,
 | 
				
			||||||
                        data        BLOB NOT NULL);
 | 
					                        data        BLOB NOT NULL);
 | 
				
			||||||
                      CREATE INDEX idx_{Table.Upload}_path ON {Table.Upload} (web_log_id, path)"
 | 
					                      CREATE INDEX idx_{Table.Upload}_path ON {Table.Upload} (web_log_id, path)" []
 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Database version table
 | 
					 | 
				
			||||||
                if needsTable Table.DbVersion then
 | 
					 | 
				
			||||||
                    $"CREATE TABLE {Table.DbVersion} (id TEXT PRIMARY KEY);
 | 
					 | 
				
			||||||
                      INSERT INTO {Table.DbVersion} VALUES ('{Utils.Migration.currentDbVersion}')"
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            |> Seq.map (fun sql ->
 | 
					 | 
				
			||||||
                log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table..."""
 | 
					 | 
				
			||||||
                conn.customNonQuery sql [])
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let! _ = Task.WhenAll tasks
 | 
					        // Database version table
 | 
				
			||||||
        ()
 | 
					        if needsTable Table.DbVersion then
 | 
				
			||||||
 | 
					            log.LogInformation(creatingTable, Table.DbVersion)
 | 
				
			||||||
 | 
					            do! conn.customNonQuery
 | 
				
			||||||
 | 
					                    $"CREATE TABLE {Table.DbVersion} (id TEXT PRIMARY KEY);
 | 
				
			||||||
 | 
					                      INSERT INTO {Table.DbVersion} VALUES ('{Utils.Migration.currentDbVersion}')" []
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /// Set the database version to the specified version
 | 
					    /// Set the database version to the specified version
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
    <PackageReference Include="Markdown.ColorCode" Version="2.2.2" />
 | 
					    <PackageReference Include="Markdown.ColorCode" Version="2.2.2" />
 | 
				
			||||||
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 | 
					    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 | 
				
			||||||
    <PackageReference Include="NodaTime" Version="3.1.11" />
 | 
					    <PackageReference Include="NodaTime" Version="3.1.11" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.400" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="Expecto" Version="10.2.1" />
 | 
					    <PackageReference Include="Expecto" Version="10.2.1" />
 | 
				
			||||||
    <PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
 | 
					    <PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.400" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
@ -32,12 +32,12 @@
 | 
				
			|||||||
    <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
 | 
					    <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
 | 
				
			||||||
    <PackageReference Include="DotLiquid" Version="2.2.692" />
 | 
					    <PackageReference Include="DotLiquid" Version="2.2.692" />
 | 
				
			||||||
    <PackageReference Include="Giraffe" Version="6.4.0" />
 | 
					    <PackageReference Include="Giraffe" Version="6.4.0" />
 | 
				
			||||||
    <PackageReference Include="Giraffe.Htmx" Version="2.0.0" />
 | 
					    <PackageReference Include="Giraffe.Htmx" Version="2.0.2" />
 | 
				
			||||||
    <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.0" />
 | 
					    <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.2" />
 | 
				
			||||||
    <PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
 | 
					    <PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
 | 
					    <PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
 | 
				
			||||||
    <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
 | 
					    <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Update="FSharp.Core" Version="8.0.300" />
 | 
					    <PackageReference Update="FSharp.Core" Version="8.0.400" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user