Skip to content

Commit

Permalink
feat: adding support for tagging (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
CallumKerson committed Apr 14, 2023
1 parent 57e943e commit c118587
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 24 deletions.
8 changes: 8 additions & 0 deletions cmd/athenaeum/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ func TestRootCommand(t *testing.T) {
expectedContentType: "text/xml; charset=utf-8",
expectedBody: getExpectedFeed(t, "woo_zeller.rss", host),
},
{
name: "tag feed",
path: "/podcast/tags/Hugo%20Awards/feed.rss",
method: "GET",
expectedStatus: 200,
expectedContentType: "text/xml; charset=utf-8",
expectedBody: getExpectedFeed(t, "hugo_awards.rss", host),
},
}

for _, testCase := range tests {
Expand Down
23 changes: 23 additions & 0 deletions cmd/athenaeum/testdata/hugo_awards.rss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>Hugo Awards</title>
<link>{{.}}</link>
<copyright></copyright>
<language>EN</language>
<description>Hugo Awards Audiobooks</description>
<itunes:block>yes</itunes:block>
<itunes:explicit>yes</itunes:explicit>
<itunes:image href="{{.}}/static/itunes_image.jpg"></itunes:image>
<item>
<title>This Is How You Lose the Time War</title>
<guid>{{.}}/media/Amal%20El-Mohtar%20and%20Max%20Gladstone/This%20Is%20How%20You%20Lose%20the%20Time%20War/This%20Is%20How%20You%20Lose%20the%20Time%20War.m4b</guid>
<pubDate>Tue, 16 Jul 2019 08:00:00 +0000</pubDate>
<description><![CDATA[This Is How You Lose the Time War by Amal El-Mohtar & Max Gladstone]]></description>
<content:encoded><![CDATA[<h1>This Is How You Lose the Time War</h1><h2>By Amal El-Mohtar & Max Gladstone</h2><h4>Narrated by Cynthia Farrell & Emily Woo Zeller</h4><p>Among the ashes of a dying world, an agent of the Commandant finds a letter. It reads: Burn before reading.</p><p>Thus begins an unlikely correspondence between two rival agents hellbent on securing the best possible future for their warring factions. Now, what began as a taunt, a battlefield boast, grows into something more. Something epic. Something romantic. Something that could change the past and the future.</p><p>Except the discovery of their bond would mean death for each of them. There's still a war going on, after all. And someone has to win that war. That's how war works. Right?</p>]]></content:encoded>
<itunes:duration>0:04</itunes:duration>
<itunes:subtitle>Amal El-Mohtar &amp; Max Gladstone</itunes:subtitle>
<enclosure url="{{.}}/media/Amal%20El-Mohtar%20and%20Max%20Gladstone/This%20Is%20How%20You%20Lose%20the%20Time%20War/This%20Is%20How%20You%20Lose%20the%20Time%20War.m4b" length="145608" type="audio/mp4a-latm"></enclosure>
</item>
</channel>
</rss>
9 changes: 9 additions & 0 deletions internal/audiobooks/service/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ func NarratorFilter(name string) Filter {
}
}

func TagFilter(tag string) Filter {
return func(a *audiobooks.Audiobook) bool {
if a != nil && contains(a.Tags, tag) {
return true
}
return false
}
}

func contains[K comparable](slice []K, item K) bool {
for _, v := range slice {
if v == item {
Expand Down
4 changes: 4 additions & 0 deletions internal/audiobooks/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func (s *Service) GetAudiobooksByNarrator(ctx context.Context, name string) ([]a
return s.audiobookStore.Get(ctx, NarratorFilter(name))
}

func (s *Service) GetAudiobooksByTag(ctx context.Context, tag string) ([]audiobooks.Audiobook, error) {
return s.audiobookStore.Get(ctx, TagFilter(tag))
}

func (s *Service) GetAudiobooksBy(ctx context.Context, filter func(*audiobooks.Audiobook) bool) ([]audiobooks.Audiobook, error) {
return s.audiobookStore.Get(ctx, filter)
}
41 changes: 21 additions & 20 deletions internal/podcasts/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type AudiobooksClient interface {
GetAudiobooksByGenre(ctx context.Context, genre audiobooks.Genre) ([]audiobooks.Audiobook, error)
GetAudiobooksByAuthor(ctx context.Context, author string) (books []audiobooks.Audiobook, err error)
GetAudiobooksByNarrator(ctx context.Context, narrator string) (books []audiobooks.Audiobook, err error)
GetAudiobooksByTag(ctx context.Context, tag string) (books []audiobooks.Audiobook, err error)
UpdateAudiobooks(ctx context.Context) error
}

Expand All @@ -45,18 +46,7 @@ func (s *Service) WriteAllAudiobooksFeed(ctx context.Context, writer io.Writer)
if err != nil {
return err
}
allAudiobooksFeedOpts := &FeedOpts{
Title: allAudiobooksFeedTitle,
Description: allAudiobooksFeedDescription,
Link: s.host,
ImageLink: s.fedImageLink,
Explicit: s.feedExplicit,
Language: s.feedLanguage,
Author: s.feedAuthor,
Email: s.feedAuthorEmail,
Copyright: s.feedCopyright,
}
return s.WriteFeedFromAudiobooks(ctx, books, allAudiobooksFeedOpts, writer)
return s.writeAudiobookFeed(ctx, allAudiobooksFeedTitle, allAudiobooksFeedDescription, books, writer)
}

func (s *Service) WriteGenreAudiobookFeed(ctx context.Context, genre audiobooks.Genre, writer io.Writer) error {
Expand Down Expand Up @@ -86,7 +76,7 @@ func (s *Service) WriteAuthorAudiobookFeed(ctx context.Context, author string, w
if len(books) < 1 {
return false, nil
}
return true, s.writePersonAudiobookFeed(ctx, author, authorDescriptionFormat, books, writer)
return true, s.writeAudiobookFeed(ctx, author, fmt.Sprintf(authorDescriptionFormat, author), books, writer)
}

func (s *Service) WriteNarratorAudiobookFeed(ctx context.Context, narrator string, writer io.Writer) (bool, error) {
Expand All @@ -97,7 +87,18 @@ func (s *Service) WriteNarratorAudiobookFeed(ctx context.Context, narrator strin
if len(books) < 1 {
return false, nil
}
return true, s.writePersonAudiobookFeed(ctx, narrator, narratorDescriptionFormat, books, writer)
return true, s.writeAudiobookFeed(ctx, narrator, fmt.Sprintf(narratorDescriptionFormat, narrator), books, writer)
}

func (s *Service) WriteTagAudiobookFeed(ctx context.Context, tag string, writer io.Writer) (bool, error) {
books, err := s.GetAudiobooksByTag(ctx, tag)
if err != nil {
return false, err
}
if len(books) < 1 {
return false, nil
}
return true, s.writeAudiobookFeed(ctx, tag, fmt.Sprintf(descriptionFormat, tag), books, writer)
}

func (s *Service) IsReady(ctx context.Context) bool {
Expand All @@ -119,11 +120,11 @@ func New(audiobooksClient AudiobooksClient, logger loggerrific.Logger, opts ...O
return svc
}

func (s *Service) writePersonAudiobookFeed(ctx context.Context, personName, descFormat string,
personBooks []audiobooks.Audiobook, writer io.Writer) error {
personFeedOpts := &FeedOpts{
Title: personName,
Description: fmt.Sprintf(descFormat, personName),
func (s *Service) writeAudiobookFeed(ctx context.Context, title, description string,
books []audiobooks.Audiobook, writer io.Writer) error {
feedOtps := &FeedOpts{
Title: title,
Description: description,
Link: s.host,
ImageLink: s.fedImageLink,
Explicit: s.feedExplicit,
Expand All @@ -132,5 +133,5 @@ func (s *Service) writePersonAudiobookFeed(ctx context.Context, personName, desc
Email: s.feedAuthorEmail,
Copyright: s.feedCopyright,
}
return s.WriteFeedFromAudiobooks(ctx, personBooks, personFeedOpts, writer)
return s.WriteFeedFromAudiobooks(ctx, books, feedOtps, writer)
}
10 changes: 10 additions & 0 deletions internal/podcasts/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ func (c *testAudiobookClient) GetAudiobooksByNarrator(ctx context.Context, narra
return testbooks.AudiobooksFilteredBy(testbooks.NarratorFilter(narrator)), nil
}

func (c *testAudiobookClient) GetAudiobooksByTag(ctx context.Context, tag string) ([]audiobooks.Audiobook, error) {
return testbooks.AudiobooksFilteredBy(testbooks.TagFilter(tag)), nil
}

func (c *testAudiobookClient) UpdateAudiobooks(ctx context.Context) error {
return nil
}
Expand Down Expand Up @@ -65,6 +69,12 @@ func TestGetFeed(t *testing.T) {
{name: "Feed for narrator that does not exist in library", writeFeedTest: func(svc *Service, wrt io.Writer) (bool, error) {
return svc.WriteNarratorAudiobookFeed(context.Background(), "Simon Vance", wrt)
}, expectedFeed: "", expectedFeedExists: false},
{name: "Tag feed", writeFeedTest: func(svc *Service, wrt io.Writer) (bool, error) {
return svc.WriteTagAudiobookFeed(context.Background(), "Hugo Awards", wrt)
}, pathToExpectedFeed: "hugo_awards_feed.rss", expectedFeedExists: true},
{name: "Feed for tag that does not exist in library", writeFeedTest: func(svc *Service, wrt io.Writer) (bool, error) {
return svc.WriteNarratorAudiobookFeed(context.Background(), "Nebula Awards", wrt)
}, expectedFeed: "", expectedFeedExists: false},
}

svc := New(&testAudiobookClient{},
Expand Down
28 changes: 28 additions & 0 deletions internal/podcasts/service/testdata/hugo_awards_feed.rss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>Hugo Awards</title>
<link>http://www.example-podcast.com/audiobooks</link>
<copyright></copyright>
<language>EN</language>
<description>Hugo Awards Audiobooks</description>
<itunes:author>A Person</itunes:author>
<itunes:block>yes</itunes:block>
<itunes:explicit>yes</itunes:explicit>
<itunes:owner>
<itunes:name>A Person</itunes:name>
<itunes:email>person@domain.test</itunes:email>
</itunes:owner>
<itunes:image href="http://www.example-podcast.com/images/itunes.jpg"></itunes:image>
<item>
<title>This Is How You Lose the Time War</title>
<guid>http://www.example-podcast.com/audiobooks/media/Amal%20El-Mohtar%20and%20Max%20Gladstone/This%20Is%20How%20You%20Lose%20the%20Time%20War/This%20Is%20How%20You%20Lose%20the%20Time%20War.m4b</guid>
<pubDate>Tue, 16 Jul 2019 08:00:00 +0000</pubDate>
<description><![CDATA[This Is How You Lose the Time War by Amal El-Mohtar & Max Gladstone]]></description>
<content:encoded><![CDATA[<h1>This Is How You Lose the Time War</h1><h2>By Amal El-Mohtar & Max Gladstone</h2><h4>Narrated by Cynthia Farrell & Emily Woo Zeller</h4><p>Among the ashes of a dying world, an agent of the Commandant finds a letter. It reads: Burn before reading.</p><p>Thus begins an unlikely correspondence between two rival agents hellbent on securing the best possible future for their warring factions. Now, what began as a taunt, a battlefield boast, grows into something more. Something epic. Something romantic. Something that could change the past and the future.</p><p>Except the discovery of their bond would mean death for each of them. There's still a war going on, after all. And someone has to win that war. That's how war works. Right?</p>]]></content:encoded>
<itunes:duration>0:04</itunes:duration>
<itunes:subtitle>Amal El-Mohtar &amp; Max Gladstone</itunes:subtitle>
<enclosure url="http://www.example-podcast.com/audiobooks/media/Amal%20El-Mohtar%20and%20Max%20Gladstone/This%20Is%20How%20You%20Lose%20the%20Time%20War/This%20Is%20How%20You%20Lose%20the%20Time%20War.m4b" length="145608" type="audio/mp4a-latm"></enclosure>
</item>
</channel>
</rss>
10 changes: 10 additions & 0 deletions internal/testing/testbooks/testbooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
Duration: time.Nanosecond * 4671000064,
ReleaseDate: &toml.LocalDate{Year: 2019, Month: 07, Day: 16},
Genres: []audiobooks.Genre{audiobooks.SciFi, audiobooks.LGBT},
Tags: []string{"Hugo Awards"},
Description: &description.Description{
Text: "Among the ashes of a dying world, an agent of the Commandant finds a letter. It reads: Burn before reading.\n" +
"Thus begins an unlikely correspondence between two rival agents hellbent " +
Expand Down Expand Up @@ -93,6 +94,15 @@ func GenreFilter(genre audiobooks.Genre) Filter {
}
}

func TagFilter(tag string) Filter {
return func(a *audiobooks.Audiobook) bool {
if a != nil && contains(a.Tags, tag) {
return true
}
return false
}
}

func contains[K comparable](slice []K, item K) bool {
for _, v := range slice {
if v == item {
Expand Down
2 changes: 2 additions & 0 deletions internal/transport/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type AudiobooksPodcastService interface {
WriteGenreAudiobookFeed(context.Context, audiobooks.Genre, io.Writer) error
WriteAuthorAudiobookFeed(context.Context, string, io.Writer) (bool, error)
WriteNarratorAudiobookFeed(context.Context, string, io.Writer) (bool, error)
WriteTagAudiobookFeed(context.Context, string, io.Writer) (bool, error)
UpdateFeeds(context.Context) error
IsReady(ctx context.Context) bool
}
Expand Down Expand Up @@ -79,6 +80,7 @@ func (h *Handler) mapRoutes() {
router.HandleFunc(fmt.Sprintf("/genre/{genre}%s", h.mainFeedPath), h.getGenreFeed)
router.HandleFunc(fmt.Sprintf("/authors/{author}%s", h.mainFeedPath), h.getAuthorFeed)
router.HandleFunc(fmt.Sprintf("/narrators/{narrator}%s", h.mainFeedPath), h.getNarratorFeed)
router.HandleFunc(fmt.Sprintf("/tags/{tag}%s", h.mainFeedPath), h.getTagFeed)
router.HandleFunc(h.mainFeedPath, h.getFeed)
})

Expand Down
10 changes: 7 additions & 3 deletions internal/transport/http/handler_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ func (h *Handler) getGenreFeed(writer http.ResponseWriter, request *http.Request
}

func (h *Handler) getAuthorFeed(writer http.ResponseWriter, request *http.Request) {
h.getPersonFeed(writer, request, "author", h.PodcastService.WriteAuthorAudiobookFeed)
h.getFeedForStr(writer, request, "author", h.PodcastService.WriteAuthorAudiobookFeed)
}

func (h *Handler) getNarratorFeed(writer http.ResponseWriter, request *http.Request) {
h.getPersonFeed(writer, request, "narrator", h.PodcastService.WriteNarratorAudiobookFeed)
h.getFeedForStr(writer, request, "narrator", h.PodcastService.WriteNarratorAudiobookFeed)
}

func (h *Handler) getPersonFeed(writer http.ResponseWriter, request *http.Request, pathVar string,
func (h *Handler) getTagFeed(writer http.ResponseWriter, request *http.Request) {
h.getFeedForStr(writer, request, "tag", h.PodcastService.WriteTagAudiobookFeed)
}

func (h *Handler) getFeedForStr(writer http.ResponseWriter, request *http.Request, pathVar string,
writeFunc func(context.Context, string, io.Writer) (bool, error)) {
nameStr, err := url.PathUnescape(chi.URLParam(request, pathVar))
if err != nil {
Expand Down
14 changes: 13 additions & 1 deletion internal/transport/http/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
"<channel><title>%s</title></channel></rss>"
testAuthor = "Agatha Test-y"
testNarrator = "David Crochet"
testTag = "Cosy Classics"
)

func TestHandler(t *testing.T) {
Expand Down Expand Up @@ -54,7 +55,9 @@ func TestHandler(t *testing.T) {
{name: "author feed", method: "GET", path: "/podcast/authors/Agatha%20Test-y/feed.rss", expectedStatus: 200, expectedContentType: ContentTypeTextXML, expectedBody: fmt.Sprintf(feedFormat, testAuthor)},
{name: "no author feed", method: "GET", path: "/podcast/authors/something/feed.rss", expectedStatus: 404, expectedContentType: "text/plain; charset=utf-8", expectedBody: "Not Found"},
{name: "narrator feed", method: "GET", path: "/podcast/narrators/David%20Crochet/feed.rss", expectedStatus: 200, expectedContentType: ContentTypeTextXML, expectedBody: fmt.Sprintf(feedFormat, testNarrator)},
{name: "no author feed", method: "GET", path: "/podcast/narrators/something/feed.rss", expectedStatus: 404, expectedContentType: "text/plain; charset=utf-8", expectedBody: "Not Found"},
{name: "no narrator feed", method: "GET", path: "/podcast/narrators/something/feed.rss", expectedStatus: 404, expectedContentType: "text/plain; charset=utf-8", expectedBody: "Not Found"},
{name: "tag feed", method: "GET", path: "/podcast/tags/Cosy%20Classics/feed.rss", expectedStatus: 200, expectedContentType: ContentTypeTextXML, expectedBody: fmt.Sprintf(feedFormat, testTag)},
{name: "no tag feed", method: "GET", path: "/podcast/tags/something/feed.rss", expectedStatus: 404, expectedContentType: "text/plain; charset=utf-8", expectedBody: "Not Found"},
{name: "media", method: "GET", path: "/media/media.txt", expectedStatus: 200, expectedContentType: "text/plain; charset=utf-8", expectedBody: "served file"},
{name: "update", method: "POST", path: "/update", expectedStatus: 204, expectedContentType: "", expectedBody: ""},
{name: "update on get fails", method: "GET", path: "/update", expectedStatus: 405, expectedContentType: "text/plain; charset=utf-8", expectedBody: "Method Not Allowed"},
Expand Down Expand Up @@ -138,6 +141,15 @@ func (s *DummyPodcastService) WriteNarratorAudiobookFeed(ctx context.Context, na
return false, err
}

func (s *DummyPodcastService) WriteTagAudiobookFeed(ctx context.Context, tag string, w io.Writer) (bool, error) {
var err error = nil
if tag == testTag {
_, err = fmt.Fprintf(w, feedFormat, tag)
return true, err
}
return false, err
}

func (s *DummyPodcastService) IsReady(ctx context.Context) bool {
return true
}
Expand Down
1 change: 1 addition & 0 deletions pkg/audiobooks/audiobook.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Audiobook struct {
Genres []Genre `json:"genres,omitempty" toml:",omitempty"`
Series *Series `json:"series,omitempty" toml:",omitempty"`
Narrators []string `json:"narrators" toml:",omitempty"`
Tags []string `json:"tags,omitempty" toml:",omitempty"`
Duration time.Duration `json:"duration" toml:",omitempty"`
FileSize uint64 `json:"fileSize" toml:",omitempty"`
MIMEType string `json:"mimeType" toml:",omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Authors = ["Amal El-Mohtar", "Max Gladstone"]
ReleaseDate = 2019-07-16
Genres = ["Sci-Fi", "LGBT"]
Narrators = ["Cynthia Farrell", "Emily Woo Zeller"]
Tags = ["Hugo Awards", ]

[Description]
Text = """Among the ashes of a dying world, an agent of the Commandant finds a letter. It reads: Burn before reading.
Expand Down

0 comments on commit c118587

Please sign in to comment.