From 848658bb21784b2c08f99a98c9a90f5cfacbcc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Provazn=C3=ADk?= Date: Wed, 19 Jan 2022 12:33:31 +0100 Subject: [PATCH] Adding support for CDN rewrites --- src/Funcaster/Domain.fs | 10 +++++- src/Funcaster/DomainExtensions.fs | 23 ++++++++++++++ src/Funcaster/Funcaster.fsproj | 1 + src/Funcaster/Functions.fs | 23 ++++++++++++-- src/Funcaster/Program.fs | 6 ++-- src/Funcaster/Storage.fs | 53 ++++++++++++++++++++++++++++--- 6 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/Funcaster/DomainExtensions.fs diff --git a/src/Funcaster/Domain.fs b/src/Funcaster/Domain.fs index 7669011..d21230f 100644 --- a/src/Funcaster/Domain.fs +++ b/src/Funcaster/Domain.fs @@ -72,4 +72,12 @@ type Channel = { Category : string option Type : ChannelType Restrictions : string list -} \ No newline at end of file +} + +type CdnSetup = { + IsEnabled : bool + CdnUrl : Uri +} + +module CdnSetup = + let none = { IsEnabled = false; CdnUrl = Uri("https://www.example.com") } \ No newline at end of file diff --git a/src/Funcaster/DomainExtensions.fs b/src/Funcaster/DomainExtensions.fs new file mode 100644 index 0000000..3222316 --- /dev/null +++ b/src/Funcaster/DomainExtensions.fs @@ -0,0 +1,23 @@ +module Funcaster.DomainExtensions + +open System +open Funcaster.Domain + +module CdnSetup = + let private _rewriteUrl (old:Uri) (new':Uri) = + let builder = UriBuilder(old) + builder.Host <- new'.Host + builder.Uri + + let rewriteUrl (cdn:CdnSetup) (url:Uri) = + if cdn.IsEnabled then _rewriteUrl url cdn.CdnUrl else url + + let rewriteItem (cdn:CdnSetup) (item:Item) = + { item + with + Enclosure = { item.Enclosure with Url = rewriteUrl cdn item.Enclosure.Url } + Image = item.Image |> Option.map (rewriteUrl cdn) + } + + let rewriteChannel (cdn:CdnSetup) (channel:Channel) = + { channel with Image = rewriteUrl cdn channel.Image } \ No newline at end of file diff --git a/src/Funcaster/Funcaster.fsproj b/src/Funcaster/Funcaster.fsproj index aec4114..9b9687f 100644 --- a/src/Funcaster/Funcaster.fsproj +++ b/src/Funcaster/Funcaster.fsproj @@ -17,6 +17,7 @@ + diff --git a/src/Funcaster/Functions.fs b/src/Funcaster/Functions.fs index e48d012..539f2e1 100644 --- a/src/Funcaster/Functions.fs +++ b/src/Funcaster/Functions.fs @@ -3,6 +3,7 @@ namespace Funcaster.Functions open System open System.Net open Funcaster.Domain +open Funcaster.DomainExtensions open Funcaster.RssXml open Funcaster.Storage open Microsoft.Azure.Functions.Worker @@ -26,15 +27,31 @@ module Stubs = Restrictions = [] } -type Functions(log:ILogger, podcast:PodcastTable, episodes:EpisodesTable) = +type Functions(log:ILogger, podcast:PodcastTable, episodes:EpisodesTable, cdn:CdnSetupTable) = [] member _.RssFeed ([] req: HttpRequestData, ctx: FunctionContext) = task { + // get CDN info + let! cdnSetupOpt = getCdnSetup cdn () + let cdnSetup = cdnSetupOpt |> Option.defaultValue CdnSetup.none + + // get Channel let! channelOpt = getPodcast podcast () - let channel = channelOpt |> Option.defaultValue Stubs.getEmptyChannel + let channel = + channelOpt + |> Option.defaultValue Stubs.getEmptyChannel + |> CdnSetup.rewriteChannel cdnSetup + + // get Episodes let! allItems = getEpisodes episodes () - let items = allItems |> List.filter (fun x -> x.Publish <= DateTimeOffset.UtcNow) |> List.sortByDescending (fun x -> x.Publish) + let items = + allItems + |> List.filter (fun x -> x.Publish <= DateTimeOffset.UtcNow) + |> List.sortByDescending (fun x -> x.Publish) + |> List.map (CdnSetup.rewriteItem cdnSetup) + + // write response let res = req.CreateResponse(HttpStatusCode.OK) res.Headers.Add("Content-Type", "application/rss+xml; charset=utf-8"); RssXml.getDoc channel items |> RssXml.toString |> res.WriteString diff --git a/src/Funcaster/Program.fs b/src/Funcaster/Program.fs index e74d572..9a85e2c 100644 --- a/src/Funcaster/Program.fs +++ b/src/Funcaster/Program.fs @@ -6,10 +6,12 @@ open Funcaster.Storage let configureServices (ctx:HostBuilderContext) (svcs:IServiceCollection) = let connString = ctx.Configuration.["PodcastStorage"] - let episodes = EpisodesTable.create connString - let podcast = PodcastTable.create connString + let episodes = EpisodesTable.createSafe connString + let podcast = PodcastTable.createSafe connString + let cdnSetup = CdnSetupTable.createSafe connString svcs.AddSingleton(episodes) |> ignore svcs.AddSingleton(podcast) |> ignore + svcs.AddSingleton(cdnSetup) |> ignore () [] diff --git a/src/Funcaster/Storage.fs b/src/Funcaster/Storage.fs index 49d03cc..98620ec 100644 --- a/src/Funcaster/Storage.fs +++ b/src/Funcaster/Storage.fs @@ -26,8 +26,10 @@ module private Helpers = type PodcastTable = PodcastTable of TableClient module PodcastTable = - let create (conn:string) = - TableClient(conn, "Podcast") |> PodcastTable + let createSafe (conn:string) = + let client = TableClient(conn, "Podcast") + let _ = client.CreateIfNotExists() + client |> PodcastTable module Channel = let toEntity (c:Channel) : TableEntity = @@ -83,8 +85,10 @@ let upsertPodcast (PodcastTable podcastTable) (channel:Channel) = type EpisodesTable = EpisodesTable of TableClient module EpisodesTable = - let create (conn:string) = - TableClient(conn, "Episodes") |> EpisodesTable + let createSafe (conn:string) = + let client = TableClient(conn, "Episodes") + let _ = client.CreateIfNotExists() + client |> EpisodesTable module Item = let toPartialEnclosureEntity (guid:string) (c:Enclosure) : TableEntity = @@ -169,4 +173,45 @@ let updateEnclosure (EpisodesTable episodesTable) (guid:string) (enc:Enclosure) let entity = enc |> Item.toPartialEnclosureEntity (guid |> key) let! _ = episodesTable.UpsertEntityAsync(entity, TableUpdateMode.Merge) return () + } + +type CdnSetupTable = CdnSetupTable of TableClient + +module CdnSetupTable = + let createSafe (conn:string) = + let client = TableClient(conn, "CdnSetup") + let _ = client.CreateIfNotExists() + client |> CdnSetupTable + +module CdnSetup = + let toEntity (c:CdnSetup) : TableEntity = + let e = TableEntity() + e.PartitionKey <- "cdn" + e.RowKey <- "cdn" + e.["CdnUrl"] <- c.CdnUrl |> string + e.["IsEnabled"] <- c.IsEnabled + e + + let fromEntity (e:TableEntity) : CdnSetup = + { + CdnUrl = e.GetString("CdnUrl") |> Uri + IsEnabled = e.GetBoolean("IsEnabled") |> Option.ofNullable |> Option.defaultValue false + } + +let getCdnSetup (CdnSetupTable cdnTable) () = + task { + return + tableQuery { + filter (pk "cdn" + rk "cdn") + } + |> cdnTable.Query + |> Seq.tryHead + |> Option.map CdnSetup.fromEntity + } + +let upsertCdnSetup (CdnSetupTable cdnTable) (cdnSetup:CdnSetup) = + task { + let entity = cdnSetup |> CdnSetup.toEntity + let! _ = cdnTable.UpsertEntityAsync(entity, TableUpdateMode.Merge) + return () } \ No newline at end of file