Add podcast email field for RSS itunes:owner tag
Build and Deploy / build-and-deploy (push) Successful in 56s

Enables users to set a podcast contact email from the dashboard
settings. This email is included in the RSS feed as
itunes:owner/itunes:email, which podcast directories like Spotify
and Apple Podcasts require for indexing and verification.
This commit is contained in:
2026-07-12 17:29:10 -04:00
parent 9011a00475
commit 9e9124ecb0
5 changed files with 39 additions and 10 deletions
+1
View File
@@ -84,6 +84,7 @@ func (h *EpisodeHandler) ServeDashboard(w http.ResponseWriter, r *http.Request)
"PodcastSlug": user.PodcastSlug,
"PodcastImageURL": podcastImageURL,
"PodcastAuthor": user.PodcastAuthor,
"PodcastEmail": user.PodcastEmail,
"RSSURL": template.URL(h.BaseURL + "/rss/" + user.PodcastSlug),
})
}
+16
View File
@@ -26,11 +26,18 @@ type rssChannel struct {
Description string `xml:"description"`
Language string `xml:"language"`
Author string `xml:"itunes:author,omitempty"`
ItunesOwner *rssItunesOwner `xml:"itunes:owner,omitempty"`
Image *rssImage `xml:"image,omitempty"`
ItunesImage *rssItunesImage `xml:"itunes:image,omitempty"`
ItunesType string `xml:"itunes:type,omitempty"`
Items []rssItem `xml:"item"`
}
type rssItunesOwner struct {
Name string `xml:"itunes:name"`
Email string `xml:"itunes:email"`
}
type rssImage struct {
URL string `xml:"url"`
Title string `xml:"title"`
@@ -132,6 +139,14 @@ func (h *RSSHandler) ServeRSS(w http.ResponseWriter, r *http.Request) {
chanItunesImage = &rssItunesImage{Href: h.fullURL(user.PodcastImage)}
}
var owner *rssItunesOwner
if user.PodcastEmail != "" || author != "" {
owner = &rssItunesOwner{
Name: author,
Email: user.PodcastEmail,
}
}
feed := rssFeed{
Version: "2.0",
ItunesNS: "http://www.itunes.com/dtds/podcast-1.0.dtd",
@@ -141,6 +156,7 @@ func (h *RSSHandler) ServeRSS(w http.ResponseWriter, r *http.Request) {
Description: title,
Language: "en-us",
Author: author,
ItunesOwner: owner,
Image: chanImage,
ItunesImage: chanItunesImage,
Items: items,
+6 -1
View File
@@ -64,6 +64,11 @@ func (h *SettingsHandler) ServeSettings(w http.ResponseWriter, r *http.Request)
author = user.PodcastAuthor
}
email := r.FormValue("podcast_email")
if email == "" {
email = user.PodcastEmail
}
image := user.PodcastImage
// Handle podcast image upload
@@ -90,6 +95,6 @@ func (h *SettingsHandler) ServeSettings(w http.ResponseWriter, r *http.Request)
}
}
h.Store.UpdateUserPodcast(userID, title, slug, image, author)
h.Store.UpdateUserPodcast(userID, title, slug, image, author, email)
http.Redirect(w, r, "/?success=podcast+settings+saved", http.StatusSeeOther)
}
+11 -9
View File
@@ -14,6 +14,7 @@ type User struct {
PodcastSlug string
PodcastImage string
PodcastAuthor string
PodcastEmail string
CreatedAt string
}
@@ -56,6 +57,7 @@ func migrate(db *sql.DB) error {
podcast_slug TEXT NOT NULL DEFAULT '',
podcast_image TEXT NOT NULL DEFAULT '',
podcast_author TEXT NOT NULL DEFAULT '',
podcast_email TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -96,8 +98,8 @@ func (s *Store) CreateUser(email, passwordHash string) (int64, error) {
func (s *Store) UserByEmail(email string) (*User, error) {
u := &User{}
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, created_at FROM users WHERE email = ?", email).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.CreatedAt)
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, podcast_email, created_at FROM users WHERE email = ?", email).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.PodcastEmail, &u.CreatedAt)
if err != nil {
return nil, err
}
@@ -106,8 +108,8 @@ func (s *Store) UserByEmail(email string) (*User, error) {
func (s *Store) GetUser(id int64) (*User, error) {
u := &User{}
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, created_at FROM users WHERE id = ?", id).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.CreatedAt)
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, podcast_email, created_at FROM users WHERE id = ?", id).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.PodcastEmail, &u.CreatedAt)
if err != nil {
return nil, err
}
@@ -116,8 +118,8 @@ func (s *Store) GetUser(id int64) (*User, error) {
func (s *Store) UserBySlug(slug string) (*User, error) {
u := &User{}
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, created_at FROM users WHERE podcast_slug = ?", slug).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.CreatedAt)
err := s.db.QueryRow("SELECT id, email, password_hash, podcast_title, podcast_slug, podcast_image, podcast_author, podcast_email, created_at FROM users WHERE podcast_slug = ?", slug).
Scan(&u.ID, &u.Email, &u.PasswordHash, &u.PodcastTitle, &u.PodcastSlug, &u.PodcastImage, &u.PodcastAuthor, &u.PodcastEmail, &u.CreatedAt)
if err != nil {
return nil, err
}
@@ -136,9 +138,9 @@ func (s *Store) SlugTaken(slug string, excludeUserID int64) (bool, error) {
return true, nil
}
func (s *Store) UpdateUserPodcast(id int64, title, slug, image, author string) error {
_, err := s.db.Exec("UPDATE users SET podcast_title=?, podcast_slug=?, podcast_image=?, podcast_author=? WHERE id=?",
title, slug, image, author, id)
func (s *Store) UpdateUserPodcast(id int64, title, slug, image, author, email string) error {
_, err := s.db.Exec("UPDATE users SET podcast_title=?, podcast_slug=?, podcast_image=?, podcast_author=?, podcast_email=? WHERE id=?",
title, slug, image, author, email, id)
return err
}
+5
View File
@@ -42,6 +42,11 @@
<input type="text" id="podcast_author" name="podcast_author" class="nb-input blue"
value="{{.PodcastAuthor}}" placeholder="Podcast Host" />
</div>
<div class="form-group form-flex-1">
<label for="podcast_email">Podcast Email</label>
<input type="email" id="podcast_email" name="podcast_email" class="nb-input blue"
value="{{.PodcastEmail}}" placeholder="podcast@example.com" />
</div>
<div class="form-group">
<label for="podcast_image">Podcast Image</label>
<div class="file-upload">