namespace MyWebLog.Data.SQLite open System.Threading.Tasks open BitBadger.Documents open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Newtonsoft.Json /// SQLite myWebLog category data implementation type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogger) = /// The name of the parent ID field let parentIdField = nameof Category.Empty.ParentId /// Count all categories for the given web log let countAll webLogId = backgroundTask { log.LogTrace "Category.countAll" let! count = conn.countByFields Table.Category Any [ webLogField webLogId ] return int count } /// Count all top-level categories for the given web log let countTopLevel webLogId = backgroundTask { log.LogTrace "Category.countTopLevel" let! count = conn.countByFields Table.Category All [ webLogField webLogId; Field.NotExists parentIdField ] return int count } /// Find all categories for the given web log let findByWebLog webLogId = log.LogTrace "Category.findByWebLog" conn.findByFields Table.Category Any [ webLogField 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 -> backgroundTask { // Parent category post counts include posts in subcategories let childField = 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 fields = [ webLogField webLogId; Field.Equal (nameof Post.Empty.Status) (string Published); childField ] let query = (Query.statementWhere (Query.count Table.Post) (Query.whereByFields All fields)) .Replace("(*)", $"(DISTINCT data->>'{nameof Post.Empty.Id}')") let! postCount = conn.customScalar query (addFieldParams fields []) toCount return it.Id, int postCount }) |> Task.WhenAll return ordered |> Seq.map (fun cat -> { cat with PostCount = defaultArg (counts |> Array.tryFind (fun c -> fst c = cat.Id) |> Option.map snd) 0 }) |> Array.ofSeq } /// Find a category by its ID for the given web log let findById (catId: CategoryId) webLogId = log.LogTrace "Category.findById" conn.findFirstByFields Table.Category All [ idField catId; webLogField 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 = conn.countByFields Table.Category Any [ Field.Equal parentIdField (string catId) ] if children > 0L then let parent = [ Field.Equal parentIdField (string catId) ] match cat.ParentId with | Some _ -> do! conn.patchByFields Table.Category Any parent {| ParentId = cat.ParentId |} | None -> do! conn.removeFieldsByFields Table.Category Any parent [ parentIdField ] // Delete the category off all posts where it is assigned, and the category itself let catIdField = nameof Post.Empty.CategoryIds let fields = [ webLogField webLogId; Field.InArray catIdField Table.Post [ string catId ] ] let query = (Query.statementWhere (Query.find Table.Post) (Query.whereByFields All fields)) .Replace("SELECT data", $"SELECT data->>'{nameof Post.Empty.Id}', data->'{catIdField}'") let! posts = conn.customList query (addFieldParams fields []) (fun rdr -> rdr.GetString 0, Utils.deserialize ser (rdr.GetString 1)) for postId, cats in posts do do! conn.patchById Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |} do! conn.deleteById Table.Category catId return if children = 0L then CategoryDeleted else ReassignedChildCategories | None -> return CategoryNotFound } /// Save a category let save cat = log.LogTrace "Category.save" conn.save Table.Category cat /// Restore categories from a backup let restore cats = backgroundTask { log.LogTrace "Category.restore" for cat in cats do do! save cat } 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