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…
Reference in New Issue
Block a user