commit - 38dff9091688843ea4d52933f27306435c054355
commit + ed95fc8404c32829d0715ad12d6150cb663c049c
blob - e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
blob + 87330582443cec7a37beb0894694a7680aef3d02
--- .gitignore
+++ .gitignore
+/rss.db
\ No newline at end of file
blob - 8ef5e3310035605efd81c6ea5d6b857f69f8e6f9
blob + a01942c5a3ee04ca4a83d453267cbe45f2af0f4d
--- cmd/rss/rss.go
+++ cmd/rss/rss.go
package main
import (
+ "log"
+
"github.com/streatCodes/rss/internal/service"
)
func main() {
- _ = service.NewService()
-
+ _, err := service.New("rss.db")
+ if err != nil {
+ log.Fatalf("Error initiating service %s\n", err)
+ }
}
blob - a0881e7b1d7d6acade066ef3f776676828712d12
blob + d0e504a23f18937db7d92bea2393ef5dea7f23b0
--- go.mod
+++ go.mod
module github.com/streatCodes/rss
go 1.24.1
+
+require go.etcd.io/bbolt v1.4.0
+
+require golang.org/x/sys v0.29.0 // indirect
blob - ba5a2e0707e500d09c40d17af9491c793c1a9a11
blob + 569fb214121210f5a197fcb76bb0289a2499044a
--- internal/service/search.go
+++ internal/service/search.go
import (
"html/template"
+ "log"
"net/http"
+ "net/url"
+
+ "github.com/streatCodes/rss/rss"
)
type TemplateData struct {
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)
+
+ //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 err != nil {
+ panic("TODO")
+ }
+
+ f, err := rss.Decode(res.Body)
+ if err != nil {
+ panic("TODO")
+ }
+
+ key := []byte(parsedURL.String())
+ service.db.SaveFeed(key, &f.Channel)
+ }
+
if isHtmx == "true" {
render(w, "results", []string{searchResult})
} else {
blob - de3497b969992add0cd7859db4149a5440db6953
blob + c25c607897bf71ad59b2e2c57879c2fea54275f2
--- internal/service/service.go
+++ internal/service/service.go
import (
"log"
"net/http"
+ "time"
+
+ "github.com/streatCodes/rss/internal/db"
+ bolt "go.etcd.io/bbolt"
)
type Service struct {
+ db *db.DB
}
-func NewService() Service {
- service := Service{}
+func New(dbPath string) (*Service, error) {
+ db, err := db.New(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
+ if err != nil {
+ return nil, err
+ }
+ service := &Service{db: db}
mux := http.NewServeMux()
log.Printf("Server running at %s", addr)
log.Fatal(http.ListenAndServe(addr, mux))
- return service
+ return service, nil
}
blob - /dev/null
blob + 242b02fed7d0b62a37f7f060050ed7d4a8ecb14b (mode 644)
--- /dev/null
+++ internal/db/db.go
+package db
+
+import (
+ "os"
+
+ bolt "go.etcd.io/bbolt"
+)
+
+type DB struct {
+ raw *bolt.DB
+}
+
+func New(path string, mode os.FileMode, options *bolt.Options) (*DB, error) {
+ db, err := bolt.Open(path, mode, options)
+ if err != nil {
+ return nil, err
+ }
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ tx.CreateBucketIfNotExists(feedsBucket)
+ return nil
+ })
+
+ return &DB{raw: db}, err
+}
blob - /dev/null
blob + 773e9b46e59c135b9444cd9365bd7193d0b39b57 (mode 644)
--- /dev/null
+++ internal/db/db_test.go
+package db
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/streatCodes/rss/rss"
+ bolt "go.etcd.io/bbolt"
+)
+
+func TestSaveFeed(t *testing.T) {
+ tempDir := t.TempDir()
+ filePath := filepath.Join(tempDir, "test.db")
+
+ f, err := os.Open("example.rss")
+ if err != nil {
+ t.Error(err)
+ }
+ defer f.Close()
+
+ feed, err := rss.Decode(f)
+ if err != nil {
+ t.Error(err)
+ }
+
+ db, err := New(filePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
+ if err != nil {
+ t.Error(err)
+ }
+
+ feedKey := []byte("test")
+ err = db.SaveFeed(feedKey, &feed.Channel)
+ if err != nil {
+ t.Error(err)
+ }
+
+ retrievedFeed, err := db.GetFeed(feedKey)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if feed.Channel.Title != retrievedFeed.Title {
+ t.Errorf("Retrieved feed title: expected %q, got %q", feed.Channel.Title, retrievedFeed.Title)
+ }
+}
blob - /dev/null
blob + cd7597ce5e6e0a566dd4394a1870fa4f4febebb6 (mode 644)
--- /dev/null
+++ internal/db/example.rss
+<?xml version="1.0" encoding="UTF-8" ?>
+<rss version="2.0">
+<channel>
+ <title>RSS Title</title>
+ <description>This is an example of an RSS feed</description>
+ <link>http://www.example.com/main.html</link>
+ <copyright>2020 Example.com All rights reserved</copyright>
+ <lastBuildDate>Mon, 06 Sep 2010 00:01:00 +0000</lastBuildDate>
+ <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
+ <ttl>1800</ttl>
+
+ <item>
+ <title>Example entry</title>
+ <description>Here is some text containing an interesting description.</description>
+ <link>http://www.example.com/blog/post/1</link>
+ <guid isPermaLink="false">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
+ <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
+ </item>
+
+</channel>
+</rss>
\ No newline at end of file
blob - /dev/null
blob + 1a8dad7d542e277fcd1d8762b09fcbddaa378b5e (mode 644)
--- /dev/null
+++ internal/db/feeds.go
+package db
+
+import (
+ "encoding/json"
+
+ "github.com/streatCodes/rss/rss"
+ bolt "go.etcd.io/bbolt"
+)
+
+var feedsBucket = []byte("feeds")
+
+func (db *DB) SaveFeed(key []byte, feed *rss.Channel) error {
+ value, err := json.Marshal(feed)
+ if err != nil {
+ return err
+ }
+
+ err = db.raw.Update(func(tx *bolt.Tx) error {
+ bucket := tx.Bucket(feedsBucket)
+ if err := bucket.Put(key, value); err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+func (db *DB) GetFeed(key []byte) (*rss.Channel, error) {
+ var channelBytes []byte
+ err := db.raw.View(func(tx *bolt.Tx) error {
+ if bucket := tx.Bucket(feedsBucket); bucket != nil {
+ channelBytes = bucket.Get(key)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ var channel rss.Channel
+ err = json.Unmarshal(channelBytes, &channel)
+ return &channel, err
+}
blob - /dev/null
blob + 27c73bfc28798dc23ad8db4a3c53c477b1ef55f6 (mode 644)
--- /dev/null
+++ go.sum
+go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
+go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=