154 lines
6.6 KiB
Forth
154 lines
6.6 KiB
Forth
namespace MyWebLog.Data.Postgres
|
|
|
|
open BitBadger.Documents
|
|
open BitBadger.Documents.Postgres
|
|
open Microsoft.Extensions.Logging
|
|
open MyWebLog
|
|
open MyWebLog.Data
|
|
open Npgsql.FSharp
|
|
|
|
/// PostgreSQL myWebLog category data implementation
|
|
type PostgresCategoryData(log: ILogger) =
|
|
|
|
/// Count all categories for the given web log
|
|
let countAll webLogId =
|
|
log.LogTrace "Category.countAll"
|
|
Count.byContains Table.Category (webLogDoc webLogId)
|
|
|
|
/// Count all top-level categories for the given web log
|
|
let countTopLevel webLogId =
|
|
log.LogTrace "Category.countTopLevel"
|
|
Custom.scalar
|
|
$"""{Query.byContains (Query.count Table.Category)}
|
|
AND {Query.whereByFields Any [ Field.NotExists (nameof Category.Empty.ParentId) ]}"""
|
|
[ webLogContains webLogId ]
|
|
toCount
|
|
|
|
/// Find all categories for the given web log
|
|
let findByWebLog webLogId =
|
|
log.LogTrace "Category.findByWebLog"
|
|
Find.byContains<Category> Table.Category (webLogDoc webLogId)
|
|
|
|
/// Retrieve all categories for the given web log in a DotLiquid-friendly format
|
|
let findAllForView webLogId = backgroundTask {
|
|
log.LogTrace "Category.findAllForView"
|
|
let! cats = findByWebLog webLogId
|
|
let ordered = Utils.orderByHierarchy (cats |> List.sortBy _.Name.ToLowerInvariant()) None None []
|
|
let counts =
|
|
ordered
|
|
|> Seq.map (fun it ->
|
|
// Parent category post counts include posts in subcategories
|
|
let catIdField =
|
|
ordered
|
|
|> Seq.filter (fun cat -> cat.ParentNames |> Array.contains it.Name)
|
|
|> Seq.map _.Id
|
|
|> Seq.append (Seq.singleton it.Id)
|
|
|> Field.InArray (nameof Post.Empty.CategoryIds) Table.Post
|
|
let query =
|
|
(Query.statementWhere
|
|
(Query.count Table.Post)
|
|
$"""{Query.whereDataContains "@criteria"} AND {Query.whereByFields All [ catIdField ]}""")
|
|
.Replace("(*)", $"(DISTINCT data->>'{nameof Post.Empty.Id}')")
|
|
let postCount =
|
|
Custom.scalar
|
|
query
|
|
(addFieldParams
|
|
[ catIdField ] [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ])
|
|
toCount
|
|
|> Async.AwaitTask
|
|
|> Async.RunSynchronously
|
|
it.Id, postCount)
|
|
|> List.ofSeq
|
|
return
|
|
ordered
|
|
|> Seq.map (fun cat ->
|
|
{ cat with
|
|
PostCount = counts
|
|
|> List.tryFind (fun c -> fst c = cat.Id)
|
|
|> Option.map snd
|
|
|> Option.defaultValue 0 })
|
|
|> Array.ofSeq
|
|
}
|
|
/// Find a category by its ID for the given web log
|
|
let findById catId webLogId =
|
|
log.LogTrace "Category.findById"
|
|
Document.findByIdAndWebLog<CategoryId, Category> Table.Category catId webLogId
|
|
|
|
/// Delete a category
|
|
let delete catId webLogId = backgroundTask {
|
|
log.LogTrace "Category.delete"
|
|
match! findById catId webLogId with
|
|
| Some cat ->
|
|
// Reassign any children to the category's parent category
|
|
let! children = Find.byContains<Category> Table.Category {| ParentId = catId |}
|
|
let hasChildren = not (List.isEmpty children)
|
|
if hasChildren then
|
|
let childQuery, childParams =
|
|
if cat.ParentId.IsSome then
|
|
Query.byId (Query.patch Table.Category) "",
|
|
children
|
|
|> List.map (fun child -> [ idParam child.Id; jsonParam "@data" {| ParentId = cat.ParentId |} ])
|
|
else
|
|
Query.byId (Query.removeFields Table.Category) "",
|
|
children
|
|
|> List.map (fun child ->
|
|
[ idParam child.Id; fieldNameParams [ nameof Category.Empty.ParentId ] ])
|
|
let! _ =
|
|
Configuration.dataSource ()
|
|
|> Sql.fromDataSource
|
|
|> Sql.executeTransactionAsync [ childQuery, childParams ]
|
|
()
|
|
// Delete the category off all posts where it is assigned
|
|
let! posts =
|
|
Custom.list
|
|
$"SELECT data FROM {Table.Post} WHERE data->'{nameof Post.Empty.CategoryIds}' @> @id"
|
|
[ jsonParam "@id" [| string catId |] ]
|
|
fromData<Post>
|
|
if not (List.isEmpty posts) then
|
|
let! _ =
|
|
Configuration.dataSource ()
|
|
|> Sql.fromDataSource
|
|
|> Sql.executeTransactionAsync
|
|
[ Query.byId (Query.patch Table.Post) "",
|
|
posts
|
|
|> List.map (fun post ->
|
|
[ idParam post.Id
|
|
jsonParam
|
|
"@data"
|
|
{| CategoryIds = post.CategoryIds |> List.filter (fun cat -> cat <> catId) |} ]) ]
|
|
()
|
|
// Delete the category itself
|
|
do! Delete.byId Table.Category catId
|
|
return if hasChildren then ReassignedChildCategories else CategoryDeleted
|
|
| None -> return CategoryNotFound
|
|
}
|
|
|
|
/// Save a category
|
|
let save (cat: Category) = backgroundTask {
|
|
log.LogTrace "Category.save"
|
|
do! save Table.Category cat
|
|
}
|
|
|
|
/// Restore categories from a backup
|
|
let restore cats = backgroundTask {
|
|
log.LogTrace "Category.restore"
|
|
let! _ =
|
|
Configuration.dataSource ()
|
|
|> Sql.fromDataSource
|
|
|> Sql.executeTransactionAsync [
|
|
Query.insert Table.Category, cats |> List.map (fun c -> [ jsonParam "@data" c ])
|
|
]
|
|
()
|
|
}
|
|
|
|
interface ICategoryData with
|
|
member _.Add cat = save cat
|
|
member _.CountAll webLogId = countAll webLogId
|
|
member _.CountTopLevel webLogId = countTopLevel webLogId
|
|
member _.FindAllForView webLogId = findAllForView webLogId
|
|
member _.FindById catId webLogId = findById catId webLogId
|
|
member _.FindByWebLog webLogId = findByWebLog webLogId
|
|
member _.Delete catId webLogId = delete catId webLogId
|
|
member _.Restore cats = restore cats
|
|
member _.Update cat = save cat
|