commit 874b706b1cb8542a2aa62bd35d6da2ba7bc16f78 from: Matt Streatfield date: Tue Apr 15 13:35:01 2025 UTC Improve parsing of additional rss channel meta data Initial rendering of channel results Move handlers into their own file commit - ed95fc8404c32829d0715ad12d6150cb663c049c commit + 874b706b1cb8542a2aa62bd35d6da2ba7bc16f78 blob - 569fb214121210f5a197fcb76bb0289a2499044a blob + 3a6e23323fb3edd9d1ef72cfec4be4c458b63e9e --- internal/service/search.go +++ internal/service/search.go @@ -1,53 +1,38 @@ package service import ( - "html/template" - "log" "net/http" "net/url" "github.com/streatCodes/rss/rss" ) -type TemplateData struct { - Results []string -} - -func render(w http.ResponseWriter, name string, data any) { - tmpl := template.Must(template.ParseGlob("internal/templates/*.tmpl")) - tmpl.ExecuteTemplate(w, name, data) -} - -func (service *Service) homeHandler(w http.ResponseWriter, r *http.Request) { - render(w, "home", nil) -} - -func (service *Service) searchHandler(w http.ResponseWriter, r *http.Request) { - isHtmx := r.Header.Get("HX-Request") - searchResult := r.URL.Query().Get("search") - - log.Printf("Search: %s\n", searchResult) - +func (service *Service) findChannel(query string) ([]rss.Channel, error) { //If the search query is a URL then ingest the feed - if parsedURL, err := url.ParseRequestURI(searchResult); err == nil { - log.Println("Found URL") - res, err := http.Get(searchResult) + if parsedURL, err := url.ParseRequestURI(query); err == nil { + //Check to see if we have the feed in the database + if channel, err := service.db.GetFeed([]byte(query)); channel != nil && err == nil { + return []rss.Channel{*channel}, nil + } + + //Fetch from the internet + res, err := http.Get(query) if err != nil { - panic("TODO") + return nil, err } - f, err := rss.Decode(res.Body) + feed, err := rss.Decode(res.Body) if err != nil { - panic("TODO") + return nil, err } key := []byte(parsedURL.String()) - service.db.SaveFeed(key, &f.Channel) + err = service.db.SaveFeed(key, &feed.Channel) + if err != nil { + return nil, err + } + return []rss.Channel{feed.Channel}, nil } - if isHtmx == "true" { - render(w, "results", []string{searchResult}) - } else { - render(w, "home", TemplateData{Results: []string{searchResult}}) - } + return []rss.Channel{}, nil } blob - /dev/null blob + 4b18ed7b92c24b94ded69ac7c9f72d3b415a8587 (mode 644) --- /dev/null +++ internal/service/handlers.go @@ -0,0 +1,37 @@ +package service + +import ( + "html/template" + "net/http" + + "github.com/streatCodes/rss/rss" +) + +type TemplateData struct { + Results []rss.Channel +} + +func render(w http.ResponseWriter, name string, data any) { + tmpl := template.Must(template.ParseGlob("internal/templates/*.tmpl")) + tmpl.ExecuteTemplate(w, name, data) +} + +func (service *Service) homeHandler(w http.ResponseWriter, r *http.Request) { + render(w, "home", nil) +} + +func (service *Service) searchHandler(w http.ResponseWriter, r *http.Request) { + isHtmx := r.Header.Get("HX-Request") + searchQuery := r.URL.Query().Get("search") + + channels, err := service.findChannel(searchQuery) + if err != nil { + panic("TODO") + } + + if isHtmx == "true" { + render(w, "results", channels) + } else { + render(w, "home", TemplateData{Results: channels}) + } +} blob - 01923330f89d7dcde4d7ccf0cc1c3525e8813ab4 blob + d74a5939f20dbb31284e8318c329c0f2bace4fc1 --- internal/templates/results.tmpl +++ internal/templates/results.tmpl @@ -1,7 +1,24 @@ {{ define "results" }}
{{ range . }} -
{{ . }}
+
+ +
+

{{ .Title }}

+

{{ .Description }}

+ {{ range .Link }} + {{ . }} + {{ end }} + +
PubDate - {{ .PubDate }}
+
Items - {{ len .Items }}
+ {{ range .Categories }} +
Category - {{ .Text }}
+ {{ end }} +
Owner - {{ .Owner.Email }}
+ +
+
{{ end }}
{{ end }} \ No newline at end of file blob - da992a297e97bd57d0db1dac3d28069d234372b2 blob + b54f0b31a6885e0c6d5655fff30222904da89b17 --- rss/rss.go +++ rss/rss.go @@ -37,25 +37,31 @@ type Channel struct { PubDate RFC1123Time `xml:"pubDate"` TTL int `xml:"ttl"` Items []Item `xml:"item"` + Language string `xml:"language"` - ITunesImage string `xml:"itunes:image"` - ITunesAuthor string `xml:"itunes:author"` - ITunesCategories []ItunesCategory `xml:"itunes:category"` - ITunesOwner []ItunesOwner `xml:"itunes:owner"` - ITunesExplicit bool `xml:"itunes:explicit"` + //Additional metadata + Image Image `xml:"image"` + Author string `xml:"author"` + Categories []Category `xml:"category"` + Owner Owner `xml:"owner"` + Explicit bool `xml:"explicit"` } type AtomLink struct { Href string `xml:"href,attr"` } -type ItunesCategory struct { +type Image struct { + Href string `xml:"href,attr"` +} + +type Category struct { Text string `xml:"text,attr"` } -type ItunesOwner struct { - Name string `xml:"itunes:name"` - Email string `xml:"itunes:email"` +type Owner struct { + Name string `xml:"name"` + Email string `xml:"email"` } type Item struct { blob - e5edb9bb43d9a16eb0624c1483c7e177a9ec46d6 blob + 29868590b90b48eeb4158681a5840cb26c12e1ff --- rss/rss_test.go +++ rss/rss_test.go @@ -26,13 +26,21 @@ func TestDecode(t *testing.T) { if feed.Channel.Link[0] != "https://risky.biz/" { t.Errorf("Channel.Link: expected %q, got %q", "https://risky.biz/", feed.Channel.Link) } - // Fri, 11 Apr 2025 14:42:00 +1000 if feed.Channel.PubDate.String() != "2025-04-11 14:42:00 +1000 AEST" { t.Errorf("Channel.PubDate: expected %q, got %q", "2025-04-11 14:42:00 +1000 AEST", feed.Channel.PubDate.String()) } - if feed.Channel.ITunesExplicit != false { - t.Errorf("Channel.ITunesExplicit: expected %t, got %t", false, feed.Channel.ITunesExplicit) + if feed.Channel.Image.Href != "https://risky.biz/static/img/rb-news.png" { + t.Errorf("feed.Channel.Image: expected %q, got %q", "https://risky.biz/static/img/rb-news.png", feed.Channel.Image.Href) } + if feed.Channel.Explicit != false { + t.Errorf("Channel.Explicit: expected %t, got %t", false, feed.Channel.Explicit) + } + if feed.Channel.Categories[0].Text != "News" { + t.Errorf("Channel.Explicit: expected %q, got %q", "News", feed.Channel.Categories[0].Text) + } + if feed.Channel.Categories[1].Text != "Technology" { + t.Errorf("Channel.Explicit: expected %q, got %q", "Technology", feed.Channel.Categories[0].Text) + } item := feed.Channel.Items[0] if item.Title != "Risky Bulletin: Trump orders investigation into former CISA director Chris Krebs" { blob - d68dc54d6642e3d88afa288017eb85d2a38994cd blob + b3c398b78b58e18fc417a6119e785b66f98aa4f5 --- web/style.css +++ web/style.css @@ -199,6 +199,7 @@ main:has(.result)>hgroup.search-animation { box-shadow: var(--shadow); display: flex; align-items: center; + margin-bottom: 2rem; input { all: unset; @@ -218,4 +219,20 @@ main:has(.result)>hgroup.search-animation { button:focus, button:hover { color: var(--blue); } +} + +.results .result { + background-color: var(--black); + display: flex; + border-radius: 2rem; + padding: 1rem; + box-shadow: var(--shadow); + margin-bottom: 1rem; + + img { + width: 200px; + height: 200px; + border-radius: 2rem; + margin-right: 1rem; + } } \ No newline at end of file