Add funding (#7)/GUID (#4)/medium (#3) to podcast

- Add info log for non-default DB connections
This commit is contained in:
Daniel J. Summers 2022-07-04 18:40:32 -04:00
parent c957279162
commit 879710a0a3
4 changed files with 120 additions and 20 deletions

View File

@ -262,6 +262,18 @@ type EditCustomFeedModel =
/// The base URL for relative URL media files for this podcast (optional; defaults to web log base) /// The base URL for relative URL media files for this podcast (optional; defaults to web log base)
mediaBaseUrl : string mediaBaseUrl : string
/// The URL for funding information for the podcast
fundingUrl : string
/// The text for the funding link
fundingText : string
/// A unique identifier to follow this podcast
guid : string
/// The medium for the content of this podcast
medium : string
} }
/// An empty custom feed model /// An empty custom feed model
@ -283,6 +295,10 @@ type EditCustomFeedModel =
explicit = "no" explicit = "no"
defaultMediaType = "audio/mpeg" defaultMediaType = "audio/mpeg"
mediaBaseUrl = "" mediaBaseUrl = ""
fundingUrl = ""
fundingText = ""
guid = ""
medium = ""
} }
/// Create a model from a custom feed /// Create a model from a custom feed
@ -310,6 +326,12 @@ type EditCustomFeedModel =
explicit = ExplicitRating.toString p.explicit explicit = ExplicitRating.toString p.explicit
defaultMediaType = defaultArg p.defaultMediaType "" defaultMediaType = defaultArg p.defaultMediaType ""
mediaBaseUrl = defaultArg p.mediaBaseUrl "" mediaBaseUrl = defaultArg p.mediaBaseUrl ""
fundingUrl = defaultArg p.fundingUrl ""
fundingText = defaultArg p.fundingText ""
guid = p.guid
|> Option.map (fun it -> it.ToString().ToLowerInvariant ())
|> Option.defaultValue ""
medium = p.medium |> Option.map PodcastMedium.toString |> Option.defaultValue ""
} }
| None -> rss | None -> rss
@ -333,11 +355,10 @@ type EditCustomFeedModel =
explicit = ExplicitRating.parse this.explicit explicit = ExplicitRating.parse this.explicit
defaultMediaType = noneIfBlank this.defaultMediaType defaultMediaType = noneIfBlank this.defaultMediaType
mediaBaseUrl = noneIfBlank this.mediaBaseUrl mediaBaseUrl = noneIfBlank this.mediaBaseUrl
// TODO: implement UI to update these guid = noneIfBlank this.guid |> Option.map Guid.Parse
guid = None fundingUrl = noneIfBlank this.fundingUrl
fundingUrl = None fundingText = noneIfBlank this.fundingText
fundingText = None medium = noneIfBlank this.medium |> Option.map PodcastMedium.parse
medium = None
} }
else else
None None

View File

@ -2,6 +2,7 @@
module MyWebLog.Handlers.Feed module MyWebLog.Handlers.Feed
open System open System
open System.Collections.Generic
open System.IO open System.IO
open System.Net open System.Net
open System.ServiceModel.Syndication open System.ServiceModel.Syndication
@ -129,17 +130,19 @@ let private toFeedItem webLog (authors : MetaItem list) (cats : DisplayCategory[
|> List.iter item.Categories.Add |> List.iter item.Categories.Add
item item
/// Convert non-absolute URLs to an absolute URL for this web log
let toAbsolute webLog (link : string) =
if link.StartsWith "http" then link else WebLog.absoluteUrl webLog (Permalink link)
/// Add episode information to a podcast feed item /// Add episode information to a podcast feed item
let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (post : Post) (item : SyndicationItem) = let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (post : Post) (item : SyndicationItem) =
// Convert non-absolute URLs to an absolute URL for this web log
let toAbsolute (link : string) = if link.StartsWith "http" then link else WebLog.absoluteUrl webLog (Permalink link)
let epMediaUrl = let epMediaUrl =
match episode.media with match episode.media with
| link when link.StartsWith "http" -> link | link when link.StartsWith "http" -> link
| link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}" | link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}"
| link -> WebLog.absoluteUrl webLog (Permalink link) | link -> WebLog.absoluteUrl webLog (Permalink link)
let epMediaType = [ episode.mediaType; podcast.defaultMediaType ] |> List.tryFind Option.isSome |> Option.flatten let epMediaType = [ episode.mediaType; podcast.defaultMediaType ] |> List.tryFind Option.isSome |> Option.flatten
let epImageUrl = defaultArg episode.imageUrl (Permalink.toString podcast.imageUrl) |> toAbsolute let epImageUrl = defaultArg episode.imageUrl (Permalink.toString podcast.imageUrl) |> toAbsolute webLog
let epExplicit = defaultArg episode.explicit podcast.explicit |> ExplicitRating.toString let epExplicit = defaultArg episode.explicit podcast.explicit |> ExplicitRating.toString
let xmlDoc = XmlDocument () let xmlDoc = XmlDocument ()
@ -165,7 +168,7 @@ let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (po
match episode.chapterFile with match episode.chapterFile with
| Some chapters -> | Some chapters ->
let url = toAbsolute chapters let url = toAbsolute webLog chapters
let typ = let typ =
match episode.chapterType with match episode.chapterType with
| Some mime -> Some mime | Some mime -> Some mime
@ -179,7 +182,7 @@ let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (po
match episode.transcriptUrl with match episode.transcriptUrl with
| Some transcript -> | Some transcript ->
let url = toAbsolute transcript let url = toAbsolute webLog transcript
let elt = xmlDoc.CreateElement ("podcast", "transcript", Namespace.podcast) let elt = xmlDoc.CreateElement ("podcast", "transcript", Namespace.podcast)
elt.SetAttribute ("url", url) elt.SetAttribute ("url", url)
elt.SetAttribute ("type", Option.get episode.transcriptType) elt.SetAttribute ("type", Option.get episode.transcriptType)
@ -301,6 +304,17 @@ let private addPodcast webLog (rssFeed : SyndicationFeed) (feed : CustomFeed) =
rssFeed.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor) rssFeed.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor)
rssFeed.ElementExtensions.Add ("explicit", Namespace.iTunes, ExplicitRating.toString podcast.explicit) rssFeed.ElementExtensions.Add ("explicit", Namespace.iTunes, ExplicitRating.toString podcast.explicit)
podcast.subtitle |> Option.iter (fun sub -> rssFeed.ElementExtensions.Add ("subtitle", Namespace.iTunes, sub)) podcast.subtitle |> Option.iter (fun sub -> rssFeed.ElementExtensions.Add ("subtitle", Namespace.iTunes, sub))
podcast.fundingUrl
|> Option.iter (fun url ->
let funding = xmlDoc.CreateElement ("podcast", "funding", Namespace.podcast)
funding.SetAttribute ("url", toAbsolute webLog url)
funding.InnerText <- defaultArg podcast.fundingText "Support This Podcast"
rssFeed.ElementExtensions.Add funding)
podcast.guid
|> Option.iter (fun guid ->
rssFeed.ElementExtensions.Add ("guid", Namespace.podcast, guid.ToString().ToLowerInvariant ()))
podcast.medium
|> Option.iter (fun med -> rssFeed.ElementExtensions.Add ("medium", Namespace.podcast, PodcastMedium.toString med))
/// Get the feed's self reference and non-feed link /// Get the feed's self reference and non-feed link
let private selfAndLink webLog feedType ctx = let private selfAndLink webLog feedType ctx =
@ -402,7 +416,7 @@ let generate (feedType : FeedType) postCount : HttpHandler = fun next ctx -> bac
open DotLiquid open DotLiquid
// GET: /admin/rss/settings // GET: /admin/settings/rss
let editSettings : HttpHandler = fun next ctx -> task { let editSettings : HttpHandler = fun next ctx -> task {
let webLog = ctx.WebLog let webLog = ctx.WebLog
let feeds = let feeds =
@ -418,7 +432,7 @@ let editSettings : HttpHandler = fun next ctx -> task {
|> viewForTheme "admin" "rss-settings" next ctx |> viewForTheme "admin" "rss-settings" next ctx
} }
// POST: /admin/rss/settings // POST: /admin/settings/rss
let saveSettings : HttpHandler = fun next ctx -> task { let saveSettings : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
let! model = ctx.BindFormAsync<EditRssModel> () let! model = ctx.BindFormAsync<EditRssModel> ()
@ -432,7 +446,7 @@ let saveSettings : HttpHandler = fun next ctx -> task {
| None -> return! Error.notFound next ctx | None -> return! Error.notFound next ctx
} }
// GET: /admin/rss/{id}/edit // GET: /admin/settings/rss/{id}/edit
let editCustomFeed feedId : HttpHandler = fun next ctx -> task { let editCustomFeed feedId : HttpHandler = fun next ctx -> task {
let customFeed = let customFeed =
match feedId with match feedId with
@ -445,12 +459,22 @@ let editCustomFeed feedId : HttpHandler = fun next ctx -> task {
page_title = $"""{if feedId = "new" then "Add" else "Edit"} Custom RSS Feed""" page_title = $"""{if feedId = "new" then "Add" else "Edit"} Custom RSS Feed"""
model = EditCustomFeedModel.fromFeed f model = EditCustomFeedModel.fromFeed f
categories = CategoryCache.get ctx categories = CategoryCache.get ctx
medium_values = [|
KeyValuePair.Create ("", "&ndash; Unspecified &ndash;")
KeyValuePair.Create (PodcastMedium.toString Podcast, "Podcast")
KeyValuePair.Create (PodcastMedium.toString Music, "Music")
KeyValuePair.Create (PodcastMedium.toString Video, "Video")
KeyValuePair.Create (PodcastMedium.toString Film, "Film")
KeyValuePair.Create (PodcastMedium.toString Audiobook, "Audiobook")
KeyValuePair.Create (PodcastMedium.toString Newsletter, "Newsletter")
KeyValuePair.Create (PodcastMedium.toString Blog, "Blog")
|]
|} |}
|> viewForTheme "admin" "custom-feed-edit" next ctx |> viewForTheme "admin" "custom-feed-edit" next ctx
| None -> return! Error.notFound next ctx | None -> return! Error.notFound next ctx
} }
// POST: /admin/rss/save // POST: /admin/settings/rss/save
let saveCustomFeed : HttpHandler = fun next ctx -> task { let saveCustomFeed : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.WebLog.findById ctx.WebLog.id with match! data.WebLog.findById ctx.WebLog.id with
@ -476,7 +500,7 @@ let saveCustomFeed : HttpHandler = fun next ctx -> task {
| None -> return! Error.notFound next ctx | None -> return! Error.notFound next ctx
} }
// POST /admin/rss/{id}/delete // POST /admin/settings/rss/{id}/delete
let deleteCustomFeed feedId : HttpHandler = fun next ctx -> task { let deleteCustomFeed feedId : HttpHandler = fun next ctx -> task {
let data = ctx.Data let data = ctx.Data
match! data.WebLog.findById ctx.WebLog.id with match! data.WebLog.findById ctx.WebLog.id with

View File

@ -41,13 +41,17 @@ module DataImplementation =
let get (sp : IServiceProvider) : IData = let get (sp : IServiceProvider) : IData =
let config = sp.GetRequiredService<IConfiguration> () let config = sp.GetRequiredService<IConfiguration> ()
if (config.GetConnectionString >> isNull >> not) "SQLite" then if (config.GetConnectionString >> isNull >> not) "SQLite" then
let log = sp.GetRequiredService<ILogger<SQLiteData>> ()
let conn = new SqliteConnection (config.GetConnectionString "SQLite") let conn = new SqliteConnection (config.GetConnectionString "SQLite")
log.LogInformation $"Using SQL database {conn.DataSource}"
SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously SQLiteData.setUpConnection conn |> Async.AwaitTask |> Async.RunSynchronously
upcast SQLiteData (conn, sp.GetRequiredService<ILogger<SQLiteData>> ()) upcast SQLiteData (conn, sp.GetRequiredService<ILogger<SQLiteData>> ())
elif (config.GetSection "RethinkDB").Exists () then elif (config.GetSection "RethinkDB").Exists () then
let log = sp.GetRequiredService<ILogger<RethinkDbData>> ()
Json.all () |> Seq.iter Converter.Serializer.Converters.Add Json.all () |> Seq.iter Converter.Serializer.Converters.Add
let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB") let rethinkCfg = DataConfig.FromConfiguration (config.GetSection "RethinkDB")
let conn = rethinkCfg.CreateConnectionAsync () |> Async.AwaitTask |> Async.RunSynchronously let conn = rethinkCfg.CreateConnectionAsync () |> Async.AwaitTask |> Async.RunSynchronously
log.LogInformation $"Using RethinkDB database {rethinkCfg.Database}"
upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ()) upcast RethinkDbData (conn, rethinkCfg, sp.GetRequiredService<ILogger<RethinkDbData>> ())
else else
let log = sp.GetRequiredService<ILogger<SQLiteData>> () let log = sp.GetRequiredService<ILogger<SQLiteData>> ()

View File

@ -185,7 +185,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pb-3">
<div class="col-12 col-lg-10 offset-lg-1"> <div class="col-12 col-lg-10 offset-lg-1">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="mediaBaseUrl" id="mediaBaseUrl" class="form-control" <input type="text" name="mediaBaseUrl" id="mediaBaseUrl" class="form-control"
@ -195,6 +195,57 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-12 col-lg-5 offset-lg-1 pb-3">
<div class="form-floating">
<input type="text" name="fundingUrl" id="fundingUrl" class="form-control" placeholder="Funding URL"
value="{{ model.funding_url }}">
<label for="fundingUrl">Funding URL</label>
<span class="form-text fst-italic">
Optional; URL describing donation options for this podcast, relative URL supported
</span>
</div>
</div>
<div class="col-12 col-lg-5 pb-3">
<div class="form-floating">
<input type="text" name="fundingText" id="fundingText" class="form-control" maxlength="128"
placeholder="Funding Text" value="{{ model.funding_text }}">
<label for="fundingText">Funding Text</label>
<span class="form-text fst-italic">Optional; text for the funding link</span>
</div>
</div>
</div>
<div class="row pb-3">
<div class="col-8 col-lg-5 offset-lg-1 pb-3">
<div class="form-floating">
<input type="text" name="guid" id="guid" class="form-control" placeholder="GUID"
value="{{ model.guid }}">
<label for="guid">Podcast GUID</label>
<span class="form-text fst-italic">
Optional; v5 UUID uniquely identifying this podcast; once entered, do not change this value
(<a href="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid"
target="_blank">documentation</a>)
</span>
</div>
</div>
<div class="col-4 col-lg-3 offset-lg-2 pb-3">
<div class="form-floating">
<select name="medium" id="medium" class="form-control">
{% for med in medium_values -%}
<option value="{{ med[0] }}"{% if model.medium == med[0] %} selected{% endif %}>
{{ med[1] }}
</option>
{%- endfor %}
</select>
<label for="medium">Medium</label>
<span class="form-text fst-italic">
Optional; medium of the podcast content
(<a href="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#medium"
target="_blank">documentation</a>)
</span>
</div>
</div>
</div>
</fieldset> </fieldset>
</div> </div>
</div> </div>