2021-07-23 03:09:46 +00:00
|
|
|
package website
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/fs"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.handmade.network/hmn/hmn/src/db"
|
|
|
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
|
|
|
"git.handmade.network/hmn/hmn/src/models"
|
|
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
|
|
"git.handmade.network/hmn/hmn/src/parsing"
|
|
|
|
"git.handmade.network/hmn/hmn/src/templates"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/tcolgate/mp3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type PodcastIndexData struct {
|
|
|
|
templates.BaseData
|
|
|
|
Podcast templates.Podcast
|
|
|
|
Episodes []templates.PodcastEpisode
|
|
|
|
EditUrl string
|
|
|
|
NewEpisodeUrl string
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastIndex(c *RequestContext) ResponseData {
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, true, "")
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if podcastResult.Podcast == nil {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseDataAutocrumb(c, podcastResult.Podcast.Title)
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
podcastIndexData := PodcastIndexData{
|
|
|
|
BaseData: baseData,
|
2021-10-27 00:45:11 +00:00
|
|
|
Podcast: templates.PodcastToTemplate(podcastResult.Podcast, podcastResult.ImageFile),
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if canEdit {
|
2021-10-27 00:45:11 +00:00
|
|
|
podcastIndexData.EditUrl = hmnurl.BuildPodcastEdit()
|
|
|
|
podcastIndexData.NewEpisodeUrl = hmnurl.BuildPodcastEpisodeNew()
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, episode := range podcastResult.Episodes {
|
2021-10-27 00:45:11 +00:00
|
|
|
podcastIndexData.Episodes = append(podcastIndexData.Episodes, templates.PodcastEpisodeToTemplate(episode, 0, podcastResult.ImageFile))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
var res ResponseData
|
|
|
|
err = res.WriteTemplate("podcast_index.html", podcastIndexData, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast index page"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastEditData struct {
|
|
|
|
templates.BaseData
|
|
|
|
Podcast templates.Podcast
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEdit(c *RequestContext) ResponseData {
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, false, "")
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || !canEdit {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
podcast := templates.PodcastToTemplate(podcastResult.Podcast, podcastResult.ImageFile)
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseData(
|
|
|
|
c,
|
|
|
|
fmt.Sprintf("Edit %s", podcast.Title),
|
|
|
|
[]templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
|
|
|
|
)
|
2021-07-23 03:09:46 +00:00
|
|
|
podcastEditData := PodcastEditData{
|
|
|
|
BaseData: baseData,
|
|
|
|
Podcast: podcast,
|
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
|
|
|
err = res.WriteTemplate("podcast_edit.html", podcastEditData, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast edit page"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEditSubmit(c *RequestContext) ResponseData {
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, false, "")
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || !canEdit {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Perf.StartBlock("PODCAST", "Handling file upload")
|
|
|
|
c.Perf.StartBlock("PODCAST", "Parsing form")
|
|
|
|
maxFileSize := int64(2 * 1024 * 1024)
|
|
|
|
maxBodySize := maxFileSize + 1024*1024
|
|
|
|
c.Req.Body = http.MaxBytesReader(c.Res, c.Req.Body, maxBodySize)
|
|
|
|
err = c.Req.ParseMultipartForm(maxBodySize)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
|
|
|
// NOTE(asaf): The error for exceeding the max filesize doesn't have a special type, so we can't easily detect it here.
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to parse form"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
title := c.Req.Form.Get("title")
|
|
|
|
if len(strings.TrimSpace(title)) == 0 {
|
2021-07-23 03:22:31 +00:00
|
|
|
return RejectRequest(c, "Podcast title is empty")
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
description := c.Req.Form.Get("description")
|
|
|
|
if len(strings.TrimSpace(description)) == 0 {
|
2021-07-23 03:22:31 +00:00
|
|
|
return RejectRequest(c, "Podcast description is empty")
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Updating podcast")
|
|
|
|
tx, err := c.Conn.Begin(c.Context())
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to start db transaction"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
defer tx.Rollback(c.Context())
|
2021-08-27 17:58:52 +00:00
|
|
|
|
2021-08-28 17:07:45 +00:00
|
|
|
imageSaveResult := SaveImageFile(c, tx, "podcast_image", maxFileSize, fmt.Sprintf("podcast/%s/logo%d", c.CurrentProject.Slug, time.Now().UTC().Unix()))
|
|
|
|
if imageSaveResult.ValidationError != "" {
|
|
|
|
return RejectRequest(c, imageSaveResult.ValidationError)
|
|
|
|
} else if imageSaveResult.FatalError != nil {
|
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(imageSaveResult.FatalError, "Failed to save podcast image"))
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
|
2021-09-08 00:55:52 +00:00
|
|
|
if imageSaveResult.ImageFile != nil {
|
2021-07-23 03:09:46 +00:00
|
|
|
_, err = tx.Exec(c.Context(),
|
|
|
|
`
|
|
|
|
UPDATE handmade_podcast
|
|
|
|
SET
|
|
|
|
title = $1,
|
|
|
|
description = $2,
|
|
|
|
image_id = $3
|
|
|
|
WHERE id = $4
|
|
|
|
`,
|
|
|
|
title,
|
|
|
|
description,
|
2021-09-08 00:55:52 +00:00
|
|
|
imageSaveResult.ImageFile.ID,
|
2021-07-23 03:09:46 +00:00
|
|
|
podcastResult.Podcast.ID,
|
|
|
|
)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to update podcast"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_, err = tx.Exec(c.Context(),
|
|
|
|
`
|
|
|
|
UPDATE handmade_podcast
|
|
|
|
SET
|
|
|
|
title = $1,
|
|
|
|
description = $2
|
|
|
|
WHERE id = $3
|
|
|
|
`,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
podcastResult.Podcast.ID,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
err = tx.Commit(c.Context())
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to commit db transaction"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
res := c.Redirect(hmnurl.BuildPodcastEdit(), http.StatusSeeOther)
|
2021-08-17 05:18:04 +00:00
|
|
|
res.AddFutureNotice("success", "Podcast updated successfully.")
|
|
|
|
return res
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastEpisodeData struct {
|
|
|
|
templates.BaseData
|
|
|
|
Podcast templates.Podcast
|
|
|
|
Episode templates.PodcastEpisode
|
|
|
|
EditUrl string
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEpisode(c *RequestContext) ResponseData {
|
|
|
|
episodeGUIDStr := c.PathParams["episodeid"]
|
|
|
|
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, true, episodeGUIDStr)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || len(podcastResult.Episodes) == 0 {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
editUrl := ""
|
|
|
|
if canEdit {
|
2021-10-27 00:45:11 +00:00
|
|
|
editUrl = hmnurl.BuildPodcastEpisodeEdit(podcastResult.Episodes[0].GUID.String())
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
podcast := templates.PodcastToTemplate(podcastResult.Podcast, podcastResult.ImageFile)
|
|
|
|
episode := templates.PodcastEpisodeToTemplate(podcastResult.Episodes[0], 0, podcastResult.ImageFile)
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseData(
|
|
|
|
c,
|
|
|
|
fmt.Sprintf("%s | %s", episode.Title, podcast.Title),
|
|
|
|
[]templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
|
|
|
|
)
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
podcastEpisodeData := PodcastEpisodeData{
|
|
|
|
BaseData: baseData,
|
|
|
|
Podcast: podcast,
|
|
|
|
Episode: episode,
|
|
|
|
EditUrl: editUrl,
|
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
|
|
|
err = res.WriteTemplate("podcast_episode.html", podcastEpisodeData, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast episode page"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastEpisodeEditData struct {
|
|
|
|
templates.BaseData
|
|
|
|
IsEdit bool
|
|
|
|
Title string
|
|
|
|
Description string
|
|
|
|
EpisodeNumber int
|
|
|
|
CurrentFile string
|
|
|
|
EpisodeFiles []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEpisodeNew(c *RequestContext) ResponseData {
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, false, "")
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || !canEdit {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
episodeFiles, err := GetEpisodeFiles(c.CurrentProject.Slug)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to fetch podcast episode file list"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
podcast := templates.PodcastToTemplate(podcastResult.Podcast, "")
|
2021-07-23 03:09:46 +00:00
|
|
|
var res ResponseData
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseData(
|
|
|
|
c,
|
|
|
|
fmt.Sprintf("New episode | %s", podcast.Title),
|
|
|
|
[]templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}},
|
|
|
|
)
|
2021-07-23 03:09:46 +00:00
|
|
|
err = res.WriteTemplate("podcast_episode_edit.html", PodcastEpisodeEditData{
|
|
|
|
BaseData: baseData,
|
|
|
|
IsEdit: false,
|
|
|
|
EpisodeFiles: episodeFiles,
|
|
|
|
}, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast episode new page"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEpisodeEdit(c *RequestContext) ResponseData {
|
|
|
|
episodeGUIDStr, found := c.PathParams["episodeid"]
|
|
|
|
if !found || episodeGUIDStr == "" {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, true, episodeGUIDStr)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || len(podcastResult.Episodes) == 0 || !canEdit {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
episodeFiles, err := GetEpisodeFiles(c.CurrentProject.Slug)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to fetch podcast episode file list"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
episode := podcastResult.Episodes[0]
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
podcast := templates.PodcastToTemplate(podcastResult.Podcast, "")
|
|
|
|
podcastEpisode := templates.PodcastEpisodeToTemplate(episode, 0, "")
|
2021-09-01 18:25:09 +00:00
|
|
|
baseData := getBaseData(
|
|
|
|
c,
|
|
|
|
fmt.Sprintf("Edit episode %s | %s", podcastEpisode.Title, podcast.Title),
|
|
|
|
[]templates.Breadcrumb{{Name: podcast.Title, Url: podcast.Url}, {Name: podcastEpisode.Title, Url: podcastEpisode.Url}},
|
|
|
|
)
|
2021-07-23 03:09:46 +00:00
|
|
|
podcastEpisodeEditData := PodcastEpisodeEditData{
|
|
|
|
BaseData: baseData,
|
|
|
|
IsEdit: true,
|
|
|
|
Title: episode.Title,
|
|
|
|
Description: episode.Description,
|
|
|
|
EpisodeNumber: episode.EpisodeNumber,
|
|
|
|
CurrentFile: episode.AudioFile,
|
|
|
|
EpisodeFiles: episodeFiles,
|
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
|
|
|
err = res.WriteTemplate("podcast_episode_edit.html", podcastEpisodeEditData, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast episode edit page"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastEpisodeSubmit(c *RequestContext) ResponseData {
|
|
|
|
episodeGUIDStr, found := c.PathParams["episodeid"]
|
|
|
|
|
|
|
|
isEdit := found && episodeGUIDStr != ""
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, isEdit, episodeGUIDStr)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 03:50:35 +00:00
|
|
|
canEdit := c.CurrentUserCanEditCurrentProject
|
2021-07-23 03:09:46 +00:00
|
|
|
|
|
|
|
if podcastResult.Podcast == nil || (isEdit && len(podcastResult.Episodes) == 0) || !canEdit {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Perf.StartBlock("OS", "Fetching podcast episode files")
|
|
|
|
episodeFiles, err := GetEpisodeFiles(c.CurrentProject.Slug)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to fetch podcast episode file list"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.Req.ParseForm()
|
|
|
|
title := c.Req.Form.Get("title")
|
2021-07-23 03:22:31 +00:00
|
|
|
if len(strings.TrimSpace(title)) == 0 {
|
|
|
|
return RejectRequest(c, "Episode title is empty")
|
|
|
|
}
|
2021-07-23 03:09:46 +00:00
|
|
|
description := c.Req.Form.Get("description")
|
2021-07-23 03:22:31 +00:00
|
|
|
if len(strings.TrimSpace(description)) == 0 {
|
|
|
|
return RejectRequest(c, "Episode description is empty")
|
|
|
|
}
|
2021-07-23 03:09:46 +00:00
|
|
|
episodeNumberStr := c.Req.Form.Get("episode_number")
|
|
|
|
episodeNumber, err := strconv.Atoi(episodeNumberStr)
|
|
|
|
if err != nil {
|
2021-07-23 03:22:31 +00:00
|
|
|
return RejectRequest(c, "Episode number can't be parsed")
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
episodeFile := c.Req.Form.Get("episode_file")
|
|
|
|
found = false
|
|
|
|
for _, ef := range episodeFiles {
|
|
|
|
if episodeFile == ef {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
2021-07-23 03:22:31 +00:00
|
|
|
return RejectRequest(c, "Requested episode file not found")
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.Perf.StartBlock("MP3", "Parsing mp3 file for duration")
|
|
|
|
file, err := os.Open(fmt.Sprintf("public/media/podcast/%s/%s", c.CurrentProject.Slug, episodeFile))
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to open podcast file"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mp3Decoder := mp3.NewDecoder(file)
|
|
|
|
var duration float64
|
|
|
|
skipped := 0
|
|
|
|
var decodingError error
|
|
|
|
var f mp3.Frame
|
|
|
|
for {
|
|
|
|
if err = mp3Decoder.Decode(&f, &skipped); err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
decodingError = err
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
duration = duration + f.Duration().Seconds()
|
|
|
|
}
|
|
|
|
file.Close()
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if decodingError != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to decode mp3 file"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.Perf.StartBlock("MARKDOWN", "Parsing description")
|
2021-08-27 03:59:12 +00:00
|
|
|
descriptionRendered := parsing.ParseMarkdown(description, parsing.ForumRealMarkdown)
|
2021-07-23 03:09:46 +00:00
|
|
|
c.Perf.EndBlock()
|
|
|
|
|
|
|
|
guidStr := ""
|
|
|
|
if isEdit {
|
|
|
|
guidStr = podcastResult.Episodes[0].GUID.String()
|
|
|
|
c.Perf.StartBlock("SQL", "Updating podcast episode")
|
|
|
|
_, err := c.Conn.Exec(c.Context(),
|
|
|
|
`
|
|
|
|
UPDATE handmade_podcastepisode
|
|
|
|
SET
|
|
|
|
title = $1,
|
|
|
|
description = $2,
|
|
|
|
description_rendered = $3,
|
|
|
|
audio_filename = $4,
|
|
|
|
duration = $5,
|
|
|
|
episode_number = $6
|
|
|
|
WHERE
|
|
|
|
guid = $7
|
|
|
|
`,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
descriptionRendered,
|
|
|
|
episodeFile,
|
|
|
|
duration,
|
|
|
|
episodeNumber,
|
|
|
|
podcastResult.Episodes[0].GUID,
|
|
|
|
)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to update podcast episode"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
guid := uuid.New()
|
|
|
|
guidStr = guid.String()
|
|
|
|
c.Perf.StartBlock("SQL", "Creating new podcast episode")
|
|
|
|
_, err := c.Conn.Exec(c.Context(),
|
|
|
|
`
|
|
|
|
INSERT INTO handmade_podcastepisode
|
|
|
|
(guid, title, description, description_rendered, audio_filename, duration, pub_date, episode_number, podcast_id)
|
|
|
|
VALUES
|
|
|
|
($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
|
|
`,
|
|
|
|
guid,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
descriptionRendered,
|
|
|
|
episodeFile,
|
|
|
|
duration,
|
|
|
|
time.Now(),
|
|
|
|
episodeNumber,
|
|
|
|
podcastResult.Podcast.ID,
|
|
|
|
)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "Failed to create podcast episode"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 00:45:11 +00:00
|
|
|
res := c.Redirect(hmnurl.BuildPodcastEpisodeEdit(guidStr), http.StatusSeeOther)
|
2021-08-17 05:18:04 +00:00
|
|
|
res.AddFutureNotice("success", "Podcast episode updated successfully.")
|
|
|
|
return res
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetEpisodeFiles(projectSlug string) ([]string, error) {
|
|
|
|
folderStr := fmt.Sprintf("public/media/podcast/%s/", projectSlug)
|
|
|
|
folder := os.DirFS(folderStr)
|
|
|
|
files, err := fs.Glob(folder, "*.mp3")
|
|
|
|
return files, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastRSSData struct {
|
|
|
|
Podcast templates.Podcast
|
|
|
|
Episodes []templates.PodcastEpisode
|
|
|
|
}
|
|
|
|
|
|
|
|
func PodcastRSS(c *RequestContext) ResponseData {
|
|
|
|
podcastResult, err := FetchPodcast(c, c.CurrentProject.ID, true, "")
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if podcastResult.Podcast == nil {
|
|
|
|
return FourOhFour(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
podcastRSSData := PodcastRSSData{
|
2021-10-27 00:45:11 +00:00
|
|
|
Podcast: templates.PodcastToTemplate(podcastResult.Podcast, podcastResult.ImageFile),
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, episode := range podcastResult.Episodes {
|
|
|
|
var filesize int64
|
|
|
|
stat, err := os.Stat(fmt.Sprintf("./public/media/podcast/%s/%s", c.CurrentProject.Slug, episode.AudioFile))
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Err(err).Msg("Couldn't get filesize for podcast episode")
|
|
|
|
} else {
|
|
|
|
filesize = stat.Size()
|
|
|
|
}
|
2021-10-27 00:45:11 +00:00
|
|
|
podcastRSSData.Episodes = append(podcastRSSData.Episodes, templates.PodcastEpisodeToTemplate(episode, filesize, podcastResult.ImageFile))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var res ResponseData
|
|
|
|
err = res.WriteTemplate("podcast.xml", podcastRSSData, c.Perf)
|
|
|
|
if err != nil {
|
2021-08-28 12:21:03 +00:00
|
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to render podcast RSS"))
|
2021-07-23 03:09:46 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
type PodcastResult struct {
|
|
|
|
Podcast *models.Podcast
|
|
|
|
ImageFile string
|
|
|
|
Episodes []*models.PodcastEpisode
|
|
|
|
}
|
|
|
|
|
|
|
|
func FetchPodcast(c *RequestContext, projectId int, fetchEpisodes bool, episodeGUID string) (PodcastResult, error) {
|
|
|
|
var result PodcastResult
|
|
|
|
c.Perf.StartBlock("SQL", "Fetch podcast")
|
|
|
|
type podcastQuery struct {
|
|
|
|
Podcast models.Podcast `db:"podcast"`
|
|
|
|
ImageFilename string `db:"imagefile.file"`
|
|
|
|
}
|
|
|
|
podcastQueryResult, err := db.QueryOne(c.Context(), c.Conn, podcastQuery{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM handmade_podcast AS podcast
|
|
|
|
LEFT JOIN handmade_imagefile AS imagefile ON imagefile.id = podcast.image_id
|
|
|
|
WHERE podcast.project_id = $1
|
|
|
|
`,
|
|
|
|
projectId,
|
|
|
|
)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-09-14 04:13:58 +00:00
|
|
|
if errors.Is(err, db.NotFound) {
|
2021-07-23 03:09:46 +00:00
|
|
|
return result, nil
|
|
|
|
} else {
|
|
|
|
return result, oops.New(err, "failed to fetch podcast")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
podcast := podcastQueryResult.(*podcastQuery).Podcast
|
|
|
|
podcastImageFilename := podcastQueryResult.(*podcastQuery).ImageFilename
|
|
|
|
result.Podcast = &podcast
|
|
|
|
result.ImageFile = podcastImageFilename
|
|
|
|
|
|
|
|
if fetchEpisodes {
|
|
|
|
type podcastEpisodeQuery struct {
|
|
|
|
Episode models.PodcastEpisode `db:"episode"`
|
|
|
|
}
|
|
|
|
if episodeGUID == "" {
|
|
|
|
c.Perf.StartBlock("SQL", "Fetch podcast episodes")
|
|
|
|
podcastEpisodeQueryResult, err := db.Query(c.Context(), c.Conn, podcastEpisodeQuery{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM handmade_podcastepisode AS episode
|
|
|
|
WHERE episode.podcast_id = $1
|
|
|
|
ORDER BY episode.season_number DESC, episode.episode_number DESC
|
|
|
|
`,
|
|
|
|
podcast.ID,
|
|
|
|
)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
|
|
|
return result, oops.New(err, "failed to fetch podcast episodes")
|
|
|
|
}
|
2021-12-15 01:36:37 +00:00
|
|
|
for _, episodeRow := range podcastEpisodeQueryResult {
|
2021-07-23 03:09:46 +00:00
|
|
|
result.Episodes = append(result.Episodes, &episodeRow.(*podcastEpisodeQuery).Episode)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
guid, err := uuid.Parse(episodeGUID)
|
|
|
|
if err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
c.Perf.StartBlock("SQL", "Fetch podcast episode")
|
|
|
|
podcastEpisodeQueryResult, err := db.QueryOne(c.Context(), c.Conn, podcastEpisodeQuery{},
|
|
|
|
`
|
|
|
|
SELECT $columns
|
|
|
|
FROM handmade_podcastepisode AS episode
|
|
|
|
WHERE episode.podcast_id = $1 AND episode.guid = $2
|
|
|
|
`,
|
|
|
|
podcast.ID,
|
|
|
|
guid,
|
|
|
|
)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-09-14 04:13:58 +00:00
|
|
|
if errors.Is(err, db.NotFound) {
|
2021-07-23 03:09:46 +00:00
|
|
|
return result, nil
|
|
|
|
} else {
|
|
|
|
return result, oops.New(err, "failed to fetch podcast episode")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
episode := podcastEpisodeQueryResult.(*podcastEpisodeQuery).Episode
|
|
|
|
result.Episodes = append(result.Episodes, &episode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|