namespace MyWebLog.Data.SQLite open System.Threading.Tasks open Microsoft.Data.Sqlite open MyWebLog open MyWebLog.Data open Newtonsoft.Json // The web log podcast insert loop is not statically compilable; this is OK #nowarn "3511" /// SQLite myWebLog web log data implementation type SQLiteWebLogData(conn: SqliteConnection, ser: JsonSerializer) = // SUPPORT FUNCTIONS /// Add parameters for web log INSERT or web log/RSS options UPDATE statements let addWebLogRssParameters (cmd : SqliteCommand) (webLog : WebLog) = [ cmd.Parameters.AddWithValue ("@isFeedEnabled", webLog.Rss.IsFeedEnabled) cmd.Parameters.AddWithValue ("@feedName", webLog.Rss.FeedName) cmd.Parameters.AddWithValue ("@itemsInFeed", maybe webLog.Rss.ItemsInFeed) cmd.Parameters.AddWithValue ("@isCategoryEnabled", webLog.Rss.IsCategoryEnabled) cmd.Parameters.AddWithValue ("@isTagEnabled", webLog.Rss.IsTagEnabled) cmd.Parameters.AddWithValue ("@copyright", maybe webLog.Rss.Copyright) ] |> ignore /// Add parameters for web log INSERT or UPDATE statements let addWebLogParameters (cmd: SqliteCommand) (webLog: WebLog) = [ cmd.Parameters.AddWithValue ("@id", string webLog.Id) cmd.Parameters.AddWithValue ("@name", webLog.Name) cmd.Parameters.AddWithValue ("@slug", webLog.Slug) cmd.Parameters.AddWithValue ("@subtitle", maybe webLog.Subtitle) cmd.Parameters.AddWithValue ("@defaultPage", webLog.DefaultPage) cmd.Parameters.AddWithValue ("@postsPerPage", webLog.PostsPerPage) cmd.Parameters.AddWithValue ("@themeId", string webLog.ThemeId) cmd.Parameters.AddWithValue ("@urlBase", webLog.UrlBase) cmd.Parameters.AddWithValue ("@timeZone", webLog.TimeZone) cmd.Parameters.AddWithValue ("@autoHtmx", webLog.AutoHtmx) cmd.Parameters.AddWithValue ("@uploads", string webLog.Uploads) cmd.Parameters.AddWithValue ("@redirectRules", Utils.serialize ser webLog.RedirectRules) ] |> ignore addWebLogRssParameters cmd webLog /// Add parameters for custom feed INSERT or UPDATE statements let addCustomFeedParameters (cmd: SqliteCommand) (webLogId: WebLogId) (feed: CustomFeed) = [ cmd.Parameters.AddWithValue ("@id", string feed.Id) cmd.Parameters.AddWithValue ("@webLogId", string webLogId) cmd.Parameters.AddWithValue ("@source", string feed.Source) cmd.Parameters.AddWithValue ("@path", string feed.Path) cmd.Parameters.AddWithValue ("@podcast", maybe (if Option.isSome feed.Podcast then Some (Utils.serialize ser feed.Podcast) else None)) ] |> ignore /// Shorthand to map a data reader to a custom feed let toCustomFeed = Map.toCustomFeed ser /// Get the current custom feeds for a web log let getCustomFeeds (webLog : WebLog) = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "SELECT * FROM web_log_feed WHERE web_log_id = @webLogId" addWebLogId cmd webLog.Id use! rdr = cmd.ExecuteReaderAsync () return toList toCustomFeed rdr } /// Append custom feeds to a web log let appendCustomFeeds (webLog : WebLog) = backgroundTask { let! feeds = getCustomFeeds webLog return { webLog with Rss = { webLog.Rss with CustomFeeds = feeds } } } /// Update the custom feeds for a web log let updateCustomFeeds (webLog : WebLog) = backgroundTask { let! feeds = getCustomFeeds webLog let toDelete, toAdd = Utils.diffLists feeds webLog.Rss.CustomFeeds string let toId (feed : CustomFeed) = feed.Id let toUpdate = webLog.Rss.CustomFeeds |> List.filter (fun f -> not (toDelete |> List.map toId |> List.append (toAdd |> List.map toId) |> List.contains f.Id)) use cmd = conn.CreateCommand () cmd.Parameters.Add ("@id", SqliteType.Text) |> ignore toDelete |> List.map (fun it -> backgroundTask { cmd.CommandText <- "DELETE FROM web_log_feed WHERE id = @id" cmd.Parameters["@id"].Value <- string it.Id do! write cmd }) |> Task.WhenAll |> ignore cmd.Parameters.Clear () toAdd |> List.map (fun it -> backgroundTask { cmd.CommandText <- "INSERT INTO web_log_feed ( id, web_log_id, source, path, podcast ) VALUES ( @id, @webLogId, @source, @path, @podcast )" cmd.Parameters.Clear () addCustomFeedParameters cmd webLog.Id it do! write cmd }) |> Task.WhenAll |> ignore toUpdate |> List.map (fun it -> backgroundTask { cmd.CommandText <- "UPDATE web_log_feed SET source = @source, path = @path, podcast = @podcast WHERE id = @id AND web_log_id = @webLogId" cmd.Parameters.Clear () addCustomFeedParameters cmd webLog.Id it do! write cmd }) |> Task.WhenAll |> ignore } // IMPLEMENTATION FUNCTIONS /// Add a web log let add webLog = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "INSERT INTO web_log ( id, name, slug, subtitle, default_page, posts_per_page, theme_id, url_base, time_zone, auto_htmx, uploads, is_feed_enabled, feed_name, items_in_feed, is_category_enabled, is_tag_enabled, copyright, redirect_rules ) VALUES ( @id, @name, @slug, @subtitle, @defaultPage, @postsPerPage, @themeId, @urlBase, @timeZone, @autoHtmx, @uploads, @isFeedEnabled, @feedName, @itemsInFeed, @isCategoryEnabled, @isTagEnabled, @copyright, @redirectRules )" addWebLogParameters cmd webLog do! write cmd do! updateCustomFeeds webLog } /// Retrieve all web logs let all () = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "SELECT * FROM web_log" use! rdr = cmd.ExecuteReaderAsync () let! webLogs = toList (Map.toWebLog ser) rdr |> List.map (fun webLog -> backgroundTask { return! appendCustomFeeds webLog }) |> Task.WhenAll return List.ofArray webLogs } /// Delete a web log by its ID let delete webLogId = backgroundTask { use cmd = conn.CreateCommand () addWebLogId cmd webLogId let subQuery table = $"(SELECT id FROM {table} WHERE web_log_id = @webLogId)" let postSubQuery = subQuery "post" let pageSubQuery = subQuery "page" cmd.CommandText <- $" DELETE FROM post_comment WHERE post_id IN {postSubQuery}; DELETE FROM post_revision WHERE post_id IN {postSubQuery}; DELETE FROM post_permalink WHERE post_id IN {postSubQuery}; DELETE FROM post_tag WHERE post_id IN {postSubQuery}; DELETE FROM post_category WHERE post_id IN {postSubQuery}; DELETE FROM post WHERE web_log_id = @webLogId; DELETE FROM page_revision WHERE page_id IN {pageSubQuery}; DELETE FROM page_permalink WHERE page_id IN {pageSubQuery}; DELETE FROM page WHERE web_log_id = @webLogId; DELETE FROM category WHERE web_log_id = @webLogId; DELETE FROM tag_map WHERE web_log_id = @webLogId; DELETE FROM upload WHERE web_log_id = @webLogId; DELETE FROM web_log_user WHERE web_log_id = @webLogId; DELETE FROM web_log_feed WHERE web_log_id = @webLogId; DELETE FROM web_log WHERE id = @webLogId" do! write cmd } /// Find a web log by its host (URL base) let findByHost (url : string) = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "SELECT * FROM web_log WHERE url_base = @urlBase" cmd.Parameters.AddWithValue ("@urlBase", url) |> ignore use! rdr = cmd.ExecuteReaderAsync () if rdr.Read () then let! webLog = appendCustomFeeds (Map.toWebLog ser rdr) return Some webLog else return None } /// Find a web log by its ID let findById webLogId = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "SELECT * FROM web_log WHERE id = @webLogId" addWebLogId cmd webLogId use! rdr = cmd.ExecuteReaderAsync () if rdr.Read () then let! webLog = appendCustomFeeds (Map.toWebLog ser rdr) return Some webLog else return None } /// Update redirect rules for a web log let updateRedirectRules webLog = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "UPDATE web_log SET redirect_rules = @redirectRules WHERE id = @id" cmd.Parameters.AddWithValue ("@redirectRules", Utils.serialize ser webLog.RedirectRules) |> ignore cmd.Parameters.AddWithValue ("@id", string webLog.Id) |> ignore do! write cmd } /// Update RSS options for a web log let updateRssOptions webLog = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "UPDATE web_log SET is_feed_enabled = @isFeedEnabled, feed_name = @feedName, items_in_feed = @itemsInFeed, is_category_enabled = @isCategoryEnabled, is_tag_enabled = @isTagEnabled, copyright = @copyright WHERE id = @id" addWebLogRssParameters cmd webLog cmd.Parameters.AddWithValue ("@id", string webLog.Id) |> ignore do! write cmd do! updateCustomFeeds webLog } /// Update settings for a web log let updateSettings webLog = backgroundTask { use cmd = conn.CreateCommand () cmd.CommandText <- "UPDATE web_log SET name = @name, slug = @slug, subtitle = @subtitle, default_page = @defaultPage, posts_per_page = @postsPerPage, theme_id = @themeId, url_base = @urlBase, time_zone = @timeZone, auto_htmx = @autoHtmx, uploads = @uploads, is_feed_enabled = @isFeedEnabled, feed_name = @feedName, items_in_feed = @itemsInFeed, is_category_enabled = @isCategoryEnabled, is_tag_enabled = @isTagEnabled, copyright = @copyright, redirect_rules = @redirectRules WHERE id = @id" addWebLogParameters cmd webLog do! write cmd } interface IWebLogData with member _.Add webLog = add webLog member _.All () = all () member _.Delete webLogId = delete webLogId member _.FindByHost url = findByHost url member _.FindById webLogId = findById webLogId member _.UpdateRedirectRules webLog = updateRedirectRules webLog member _.UpdateRssOptions webLog = updateRssOptions webLog member _.UpdateSettings webLog = updateSettings webLog