commit ed95fc8404c32829d0715ad12d6150cb663c049c from: Matt Streatfield date: Tue Apr 15 07:35:36 2025 UTC Create DB package for reading/writing data Implement URL detection for search and lookup RSS feed commit - 38dff9091688843ea4d52933f27306435c054355 commit + ed95fc8404c32829d0715ad12d6150cb663c049c blob - e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob + 87330582443cec7a37beb0894694a7680aef3d02 --- .gitignore +++ .gitignore @@ -0,0 +1 @@ +/rss.db \ No newline at end of file blob - 8ef5e3310035605efd81c6ea5d6b857f69f8e6f9 blob + a01942c5a3ee04ca4a83d453267cbe45f2af0f4d --- cmd/rss/rss.go +++ cmd/rss/rss.go @@ -1,10 +1,14 @@ 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 @@ -1,3 +1,7 @@ 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 @@ -2,7 +2,11 @@ package service import ( "html/template" + "log" "net/http" + "net/url" + + "github.com/streatCodes/rss/rss" ) type TemplateData struct { @@ -21,6 +25,26 @@ func (service *Service) homeHandler(w http.ResponseWri 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 @@ -3,13 +3,22 @@ package service 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() @@ -21,5 +30,5 @@ func NewService() Service { 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 @@ -0,0 +1,25 @@ +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 @@ -0,0 +1,47 @@ +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 @@ -0,0 +1,21 @@ + + + + RSS Title + This is an example of an RSS feed + http://www.example.com/main.html + 2020 Example.com All rights reserved + Mon, 06 Sep 2010 00:01:00 +0000 + Sun, 06 Sep 2009 16:20:00 +0000 + 1800 + + + Example entry + Here is some text containing an interesting description. + http://www.example.com/blog/post/1 + 7bd204c6-1655-4c27-aeee-53f933c5395f + Sun, 06 Sep 2009 16:20:00 +0000 + + + + \ No newline at end of file blob - /dev/null blob + 1a8dad7d542e277fcd1d8762b09fcbddaa378b5e (mode 644) --- /dev/null +++ internal/db/feeds.go @@ -0,0 +1,46 @@ +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 @@ -0,0 +1,4 @@ +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=