namespace MyWebLog.Data.Postgres open BitBadger.Npgsql.FSharp.Documents open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql.FSharp /// PostgreSQL myWebLog page data implementation type PostgresPageData(log: ILogger) = // SUPPORT FUNCTIONS /// Append revisions to a page let appendPageRevisions (page: Page) = backgroundTask { log.LogTrace "Page.appendPageRevisions" let! revisions = Revisions.findByEntityId Table.PageRevision Table.Page page.Id return { page with Revisions = revisions } } /// Return a page with no text or revisions let pageWithoutText (row: RowReader) = { fromData row with Text = "" } /// Update a page's revisions let updatePageRevisions (pageId: PageId) oldRevs newRevs = log.LogTrace "Page.updatePageRevisions" Revisions.update Table.PageRevision Table.Page pageId oldRevs newRevs /// Does the given page exist? let pageExists (pageId: PageId) webLogId = log.LogTrace "Page.pageExists" Document.existsByWebLog Table.Page pageId webLogId // IMPLEMENTATION FUNCTIONS /// Get all pages for a web log (without text or revisions) let all webLogId = log.LogTrace "Page.all" Custom.list $"{selectWithCriteria Table.Page} ORDER BY LOWER(data ->> '{nameof Page.Empty.Title}')" [ webLogContains webLogId ] fromData /// Count all pages for the given web log let countAll webLogId = log.LogTrace "Page.countAll" Count.byContains Table.Page (webLogDoc webLogId) /// Count all pages shown in the page list for the given web log let countListed webLogId = log.LogTrace "Page.countListed" Count.byContains Table.Page {| webLogDoc webLogId with IsInPageList = true |} /// Find a page by its ID (without revisions) let findById pageId webLogId = log.LogTrace "Page.findById" Document.findByIdAndWebLog Table.Page pageId webLogId /// Find a complete page by its ID let findFullById pageId webLogId = backgroundTask { log.LogTrace "Page.findFullById" match! findById pageId webLogId with | Some page -> let! withMore = appendPageRevisions page return Some withMore | None -> return None } // TODO: need to handle when the page being deleted is the home page /// Delete a page by its ID let delete pageId webLogId = backgroundTask { log.LogTrace "Page.delete" match! pageExists pageId webLogId with | true -> do! Custom.nonQuery $"""DELETE FROM {Table.PageRevision} WHERE page_id = @id; DELETE FROM {Table.Page} WHERE {Query.whereById "@id"}""" [ "@id", Sql.string (string pageId) ] return true | false -> return false } /// Find a page by its permalink for the given web log let findByPermalink (permalink: Permalink) webLogId = log.LogTrace "Page.findByPermalink" Find.byContains Table.Page {| webLogDoc webLogId with Permalink = permalink |} |> tryHead /// Find the current permalink within a set of potential prior permalinks for the given web log let findCurrentPermalink (permalinks: Permalink list) webLogId = backgroundTask { log.LogTrace "Page.findCurrentPermalink" if List.isEmpty permalinks then return None else let linkSql, linkParam = arrayContains (nameof Page.Empty.PriorPermalinks) string permalinks return! Custom.single $"""SELECT data ->> '{nameof Page.Empty.Permalink}' AS permalink FROM page WHERE {Query.whereDataContains "@criteria"} AND {linkSql}""" [ webLogContains webLogId; linkParam ] Map.toPermalink } /// Get all complete pages for the given web log let findFullByWebLog webLogId = backgroundTask { log.LogTrace "Page.findFullByWebLog" let! pages = Document.findByWebLog Table.Page webLogId let! revisions = Revisions.findByWebLog Table.PageRevision Table.Page PageId webLogId return pages |> List.map (fun it -> { it with Revisions = revisions |> List.filter (fun r -> fst r = it.Id) |> List.map snd }) } /// Get all listed pages for the given web log (without revisions or text) let findListed webLogId = log.LogTrace "Page.findListed" Custom.list $"{selectWithCriteria Table.Page} ORDER BY LOWER(data ->> '{nameof Page.Empty.Title}')" [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with IsInPageList = true |} ] pageWithoutText /// Get a page of pages for the given web log (without revisions) let findPageOfPages webLogId pageNbr = log.LogTrace "Page.findPageOfPages" Custom.list $"{selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.Empty.Title}') LIMIT @pageSize OFFSET @toSkip" [ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ] fromData /// Restore pages from a backup let restore (pages: Page list) = backgroundTask { log.LogTrace "Page.restore" let revisions = pages |> List.collect (fun p -> p.Revisions |> List.map (fun r -> p.Id, r)) let! _ = Configuration.dataSource () |> Sql.fromDataSource |> Sql.executeTransactionAsync [ Query.insert Table.Page, pages |> List.map (fun page -> [ "@data", Query.jsonbDocParam { page with Revisions = [] } ]) Revisions.insertSql Table.PageRevision, revisions |> List.map (fun (pageId, rev) -> Revisions.revParams pageId rev) ] () } /// Save a page let save (page: Page) = backgroundTask { log.LogTrace "Page.save" let! oldPage = findFullById page.Id page.WebLogId do! save Table.Page { page with Revisions = [] } do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions () } /// Update a page's prior permalinks let updatePriorPermalinks pageId webLogId (permalinks: Permalink list) = backgroundTask { log.LogTrace "Page.updatePriorPermalinks" match! pageExists pageId webLogId with | true -> do! Update.partialById Table.Page (string pageId) {| PriorPermalinks = permalinks |} return true | false -> return false } interface IPageData with member _.Add page = save page member _.All webLogId = all webLogId member _.CountAll webLogId = countAll webLogId member _.CountListed webLogId = countListed webLogId member _.Delete pageId webLogId = delete pageId webLogId member _.FindById pageId webLogId = findById pageId webLogId member _.FindByPermalink permalink webLogId = findByPermalink permalink webLogId member _.FindCurrentPermalink permalinks webLogId = findCurrentPermalink permalinks webLogId member _.FindFullById pageId webLogId = findFullById pageId webLogId member _.FindFullByWebLog webLogId = findFullByWebLog webLogId member _.FindListed webLogId = findListed webLogId member _.FindPageOfPages webLogId pageNbr = findPageOfPages webLogId pageNbr member _.Restore pages = restore pages member _.Update page = save page member _.UpdatePriorPermalinks pageId webLogId permalinks = updatePriorPermalinks pageId webLogId permalinks