commit - ed95fc8404c32829d0715ad12d6150cb663c049c
commit + 874b706b1cb8542a2aa62bd35d6da2ba7bc16f78
blob - 569fb214121210f5a197fcb76bb0289a2499044a
blob + 3a6e23323fb3edd9d1ef72cfec4be4c458b63e9e
--- internal/service/search.go
+++ internal/service/search.go
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
+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
{{ define "results" }}
<div class="results">
{{ range . }}
- <div class="result">{{ . }}</div>
+ <div class="result">
+ <img src="{{ .Image.Href }}" />
+ <div>
+ <h3>{{ .Title }}</h3>
+ <p>{{ .Description }}</p>
+ {{ range .Link }}
+ <a href="{{.}}">{{ . }}</a>
+ {{ end }}
+ <!-- <div>Copyright - {{ .Copyright }}</div> -->
+ <div>PubDate - {{ .PubDate }}</div>
+ <div>Items - {{ len .Items }}</div>
+ {{ range .Categories }}
+ <div>Category - {{ .Text }}</div>
+ {{ end }}
+ <div>Owner - {{ .Owner.Email }}</div>
+ <!-- <div>Explicit - {{ .Explicit }}</div> -->
+ </div>
+ </div>
{{ end }}
</div>
{{ end }}
\ No newline at end of file
blob - da992a297e97bd57d0db1dac3d28069d234372b2
blob + b54f0b31a6885e0c6d5655fff30222904da89b17
--- rss/rss.go
+++ rss/rss.go
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
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
box-shadow: var(--shadow);
display: flex;
align-items: center;
+ margin-bottom: 2rem;
input {
all: unset;
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