Compare commits

...

10 Commits

Author SHA1 Message Date
Bel LaPointe
6b48dd3d61 all 2020-05-18 14:43:09 -06:00
bel
7e15610a34 Hopefully fixed 2019-06-20 17:54:43 -06:00
Bel LaPointe
a57dff60d8 no more logger 2019-05-02 09:35:57 -06:00
Bel LaPointe
7bfae2f004 add api/list/tag/tag_name_here 2019-04-23 13:56:14 -06:00
Bel LaPointe
ba3487852c whoops 2019-03-16 09:47:41 -06:00
Bel LaPointe
ccc9ea86f3 fixd podcast 2019-03-16 09:32:27 -06:00
Bel LaPointe
7fb3ffda5d Change link to regular 2019-03-14 10:16:47 -06:00
Bel LaPointe
e9038f7194 typo 2019-02-28 16:41:15 -07:00
Bel LaPointe
49b95cf5a5 add podcast support 2019-02-27 08:04:07 -07:00
Bel LaPointe
eaf9ee8a3a and move to completed.rss 2018-11-17 12:17:01 -07:00
21 changed files with 175 additions and 52 deletions

0
.gitattributes vendored Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

0
config/config.go Normal file → Executable file
View File

0
config/config_test.go Normal file → Executable file
View File

115
exchange/exchange.go Normal file → Executable file
View File

@@ -5,12 +5,15 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"local/logger" "io"
"local/rssmon2/monitor" "local/rssmon2/monitor"
"local/rssmon2/rss" "local/rssmon2/rss"
"local/rssmon2/server" "local/rssmon2/server"
"local/rssmon2/store" "local/rssmon2/store"
"log"
"net/http" "net/http"
"os"
"path"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@@ -25,6 +28,7 @@ type Exchange struct {
Srv *server.Server Srv *server.Server
allFeeds map[string]*rss.Feed allFeeds map[string]*rss.Feed
toraddr string toraddr string
savepath string
} }
func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr string) *Exchange { func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr string) *Exchange {
@@ -34,6 +38,7 @@ func New(mon *monitor.Monitor, sclient store.Client, srv *server.Server, toraddr
Srv: srv, Srv: srv,
allFeeds: make(map[string]*rss.Feed), allFeeds: make(map[string]*rss.Feed),
toraddr: toraddr, toraddr: toraddr,
savepath: "/mnt/podcasts",
} }
} }
@@ -62,12 +67,12 @@ func (ex *Exchange) LoadDB() error {
func (ex *Exchange) NewFeed(url, itemFilter, contentFilter string, tags []string, interval time.Duration) { func (ex *Exchange) NewFeed(url, itemFilter, contentFilter string, tags []string, interval time.Duration) {
feed, err := rss.New(url, itemFilter, contentFilter, tags, interval) feed, err := rss.New(url, itemFilter, contentFilter, tags, interval)
if err != nil { if err != nil {
logger.Logf("can't create new RSS %q: %v", url, err) log.Printf("can't create new RSS %q: %v", url, err)
return return
} }
ex.allFeeds[url] = feed ex.allFeeds[url] = feed
if err := ex.Mon.Submit(url, feed.Interval); err != nil { if err := ex.Mon.Submit(url, feed.Interval); err != nil {
logger.Logf("Cannot accept new feed %q: %v", url, err) log.Printf("Cannot accept new feed %q: %v", url, err)
} }
} }
@@ -106,6 +111,32 @@ func (ex *Exchange) GetFeedItem(ID string) (string, error) {
return item.Content, nil return item.Content, nil
} }
func (ex *Exchange) ListTag(tag string, n int) (string, error) {
feeds := []*rss.Feed{}
feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1)
if err != nil {
return "", err
}
for _, feedName := range feedNames {
b, err := ex.SClient.Get(nsForFeeds, feedName)
if err != nil {
return "", err
}
feed, err := rss.Deserialize(b)
if err != nil {
return "", err
}
for i := range feed.Tags {
if feed.Tags[i] == tag {
feeds = append(feeds, feed)
}
}
}
b, err := json.Marshal(feeds)
return string(b), err
}
func (ex *Exchange) GetFeedTagRSS(tag string) (string, error) { func (ex *Exchange) GetFeedTagRSS(tag string) (string, error) {
feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1) feedNames, err := ex.SClient.List(nsForFeeds, "", true, -1)
if err != nil { if err != nil {
@@ -159,48 +190,48 @@ func (ex *Exchange) UpdateFeed(url string) {
if !ok { if !ok {
f, err := rss.New(url, "", "", nil, time.Minute) f, err := rss.New(url, "", "", nil, time.Minute)
if err != nil { if err != nil {
logger.Logf("cannot identify unknown feed triggered in monitor: %q: %v", url, err) log.Printf("cannot identify unknown feed triggered in monitor: %q: %v", url, err)
return return
} }
b, err := ex.SClient.Get(nsForFeeds, f.ID()) b, err := ex.SClient.Get(nsForFeeds, f.ID())
if err != nil { if err != nil {
logger.Logf("cannot get unknown feed triggered in monitor: %q: %v", url, err) log.Printf("cannot get unknown feed triggered in monitor: %q: %v", url, err)
return return
} }
feed, err = rss.Deserialize(b) feed, err = rss.Deserialize(b)
if err != nil { if err != nil {
logger.Logf("cannot deserialize feed triggered in monitor: %q: %v", url, err) log.Printf("cannot deserialize feed triggered in monitor: %q: %v", url, err)
return return
} }
} }
items, err := ex.allFeeds[url].Update() items, err := ex.allFeeds[url].Update()
if err != nil { if err != nil {
logger.Logf("can't update old RSS %q: %v", url, err) log.Printf("can't update old RSS %q: %v", url, err)
return return
} }
b, err := feed.Serialize() b, err := feed.Serialize()
if err != nil { if err != nil {
logger.Logf("can't serialize to save RSS %q: %v", url, err) log.Printf("can't serialize to save RSS %q: %v", url, err)
return return
} }
if err := ex.SClient.Set(nsForFeeds, feed.ID(), b); err != nil { if err := ex.SClient.Set(nsForFeeds, feed.ID(), b); err != nil {
logger.Logf("can't save RSS %q.%q: %v", nsForFeeds, feed.ID(), err) log.Printf("can't save RSS %q.%q: %v", nsForFeeds, feed.ID(), err)
return return
} }
logger.Log("Saved feed", feed) log.Print("Saved feed", feed)
for i := range items { for i := range items {
b, err := items[i].Serialize() b, err := items[i].Serialize()
if err != nil { if err != nil {
logger.Logf("can't save rss item %q.%q: %v", url, items[i].Link, err) log.Printf("can't save rss item %q.%q: %v", url, items[i].Link, err)
return return
} }
if err := ex.SClient.Set(feed.ID(), items[i].ID(), b); err != nil { if err := ex.SClient.Set(feed.ID(), items[i].ID(), b); err != nil {
logger.Logf("can't save rss item %q.%q: %v", feed.ID(), items[i].ID(), err) log.Printf("can't save rss item %q.%q: %v", feed.ID(), items[i].ID(), err)
return return
} }
//logger.Log("Saved feed item", feed.ID(), items[i].ID(), items[i]) //log.Print("Saved feed item", feed.ID(), items[i].ID(), items[i])
} }
logger.Logf("Saved %d feed items for %s", len(items), feed.Title) log.Printf("Saved %d feed items for %s", len(items), feed.Title)
go ex.handlerByTag(feed.Tags, items) go ex.handlerByTag(feed.Tags, items)
} }
@@ -210,53 +241,93 @@ func (ex *Exchange) handlerByTag(tags []string, items []*rss.Item) {
switch tags[i] { switch tags[i] {
case "torrent": case "torrent":
ex.handlerTorrent(items[j]) ex.handlerTorrent(items[j])
case "podcast":
ex.handlerPodcast(items[j])
} }
} }
} }
} }
func (ex *Exchange) handlerPodcast(item *rss.Item) {
os.MkdirAll(ex.savepath, os.ModePerm)
if len(item.Enclosures) < 1 {
return
}
for i := range item.Enclosures {
link := item.Enclosures[i]
link = strings.TrimSpace(link)
if !strings.Contains(link, ".mp3") {
continue
}
savePath := path.Join(ex.savepath, path.Base(link))
if _, err := os.Stat(savePath); !os.IsNotExist(err) {
log.Printf("err: %v already exists", savePath)
continue
}
resp, err := http.Get(link)
if err != nil {
log.Printf("cannot get podcast %q: %v", link, err)
continue
}
defer resp.Body.Close()
out, err := os.Create(savePath)
if err != nil {
log.Printf("cannot create file %q for saving: %v", savePath, err)
continue
}
if _, err := io.Copy(out, resp.Body); err != nil {
log.Printf("failed to write: %v", err)
continue
}
}
}
func (ex *Exchange) handlerTorrent(item *rss.Item) { func (ex *Exchange) handlerTorrent(item *rss.Item) {
magnet := regexp.MustCompile("magnet:.xt[^ $]*") magnet := regexp.MustCompile("magnet:.xt[^ $]*")
if !magnet.MatchString(item.Content) { if !magnet.MatchString(item.Content) {
logger.Log("no magnet link: ", item.Content) log.Print("no magnet link: ", item.Content)
} }
match := magnet.FindString(item.Content) match := magnet.FindString(item.Content)
resp, err := http.Get(ex.toraddr) resp, err := http.Get(ex.toraddr)
if err != nil { if err != nil {
logger.Logf("cannot get session id at %v: %v", ex.toraddr, err) log.Printf("cannot get session id at %v: %v", ex.toraddr, err)
return return
} }
sessionID := resp.Header.Get("X-Transmission-Session-Id") sessionID := resp.Header.Get("X-Transmission-Session-Id")
requestBody, err := json.Marshal(map[string]interface{}{ requestBody, err := json.Marshal(map[string]interface{}{
"method": "torrent-add", "method": "torrent-add",
"arguments": map[string]interface{}{ "arguments": map[string]interface{}{
"filename": match, "filename": match,
"download-dir": "/data/completed-rss",
}, },
}) })
if err != nil { if err != nil {
logger.Log("cannot build request body: %v", err) log.Print("cannot build request body: %v", err)
return return
} }
req, err := http.NewRequest("POST", ex.toraddr, bytes.NewBuffer(requestBody)) req, err := http.NewRequest("POST", ex.toraddr, bytes.NewBuffer(requestBody))
if err != nil { if err != nil {
logger.Log("cannot POST request: %v", err) log.Print("cannot POST request: %v", err)
return return
} }
req.Header.Add("X-Transmission-Session-Id", sessionID) req.Header.Add("X-Transmission-Session-Id", sessionID)
resp, err = (&http.Client{}).Do(req) resp, err = (&http.Client{}).Do(req)
if err != nil { if err != nil {
logger.Log("failed POST: %v", err) log.Print("failed POST: %v", err)
return return
} }
var out map[string]interface{} var out map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
logger.Log("cannot decode response: %v", err) log.Print("cannot decode response: %v", err)
return return
} }
if v, ok := out["result"]; !ok || v != "success" { if v, ok := out["result"]; !ok || v != "success" {
logger.Logf("failed magnet submission for feed item %v: %v", item.Name, v) log.Printf("failed magnet submission for feed item %v: %v", item.Name, v)
} }
} }

5
main.go Normal file → Executable file
View File

@@ -1,12 +1,12 @@
package main package main
import ( import (
"local/logger"
"local/rssmon2/config" "local/rssmon2/config"
"local/rssmon2/exchange" "local/rssmon2/exchange"
"local/rssmon2/monitor" "local/rssmon2/monitor"
"local/rssmon2/server" "local/rssmon2/server"
"local/rssmon2/store" "local/rssmon2/store"
"log"
) )
func main() { func main() {
@@ -42,6 +42,7 @@ func core() {
ex.GetFeedRSS, ex.GetFeedRSS,
ex.GetFeedItem, ex.GetFeedItem,
ex.GetFeedTagRSS, ex.GetFeedTagRSS,
ex.ListTag,
); err != nil { ); err != nil {
panic(err) panic(err)
} }
@@ -51,7 +52,7 @@ func core() {
panic(err) panic(err)
} }
logger.Log("Starting with", config) log.Print("Starting with", config)
if err := srv.Serve(); err != nil { if err := srv.Serve(); err != nil {
panic(err) panic(err)
} }

5
main_test.go Normal file → Executable file
View File

@@ -110,6 +110,11 @@ func Test_Core(t *testing.T) {
status: 200, status: 200,
post: func() { time.Sleep(time.Second * 15) }, post: func() { time.Sleep(time.Second * 15) },
}, },
{
method: "get",
path: "api/list/tag/gotest",
status: 200,
},
} }
for _, c := range cases { for _, c := range cases {
c.method = strings.ToUpper(c.method) c.method = strings.ToUpper(c.method)

0
monitor/item.go Normal file → Executable file
View File

0
monitor/item_test.go Normal file → Executable file
View File

12
monitor/monitor.go Normal file → Executable file
View File

@@ -2,7 +2,7 @@ package monitor
import ( import (
"errors" "errors"
"local/logger" "log"
"time" "time"
"github.com/golang-collections/go-datastructures/queue" "github.com/golang-collections/go-datastructures/queue"
@@ -59,29 +59,29 @@ func (monitor *Monitor) loop() error {
} }
var err error var err error
if nextEvent, err = nextEventTime(queue); err != nil { if nextEvent, err = nextEventTime(queue); err != nil {
logger.Log("no next event time", err) log.Print("no next event time", err)
nextEvent = time.Now().Add(time.Minute * 60) nextEvent = time.Now().Add(time.Minute * 60)
continue continue
} }
case <-time.After(nextEvent.Sub(time.Now())): case <-time.After(nextEvent.Sub(time.Now())):
items, err := queue.Get(1) items, err := queue.Get(1)
if err != nil { if err != nil {
logger.Fatal("can't get item") log.Fatal("can't get item")
} }
if len(items) == 0 { if len(items) == 0 {
logger.Log("no items in queue") log.Print("no items in queue")
nextEvent = time.Now().Add(time.Minute * 60) nextEvent = time.Now().Add(time.Minute * 60)
continue continue
} }
item, ok := items[0].(*Item) item, ok := items[0].(*Item)
if !ok { if !ok {
logger.Fatal("queue contains illegal item") log.Fatal("queue contains illegal item")
} }
go monitor.trigger(item.URL) go monitor.trigger(item.URL)
item.increment() item.increment()
queue.Put(item) queue.Put(item)
if nextEvent, err = nextEventTime(queue); err != nil { if nextEvent, err = nextEventTime(queue); err != nil {
logger.Log("no next event time", err) log.Print("no next event time", err)
nextEvent = time.Now().Add(time.Minute * 60) nextEvent = time.Now().Add(time.Minute * 60)
continue continue
} }

0
monitor/monitor_test.go Normal file → Executable file
View File

13
rss/feed.go Normal file → Executable file
View File

@@ -5,7 +5,7 @@ import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"local/logger" "log"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
@@ -107,8 +107,11 @@ func (feed *Feed) fromGofeed(gofeed *gofeed.Feed) ([]*Item, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
feed.Updated = latest.Add(time.Second) //time.Now() //*updated //time.Now().UTC() //*updated if latest != feed.Updated {
feed.Updated = latest.Add(time.Second)
}
feed.Title = gofeed.Title feed.Title = gofeed.Title
log.Printf("found %v new items for %v", len(newitems), feed.Title)
return newitems, nil return newitems, nil
} }
@@ -118,19 +121,19 @@ func (feed *Feed) appendNewItems(items []*gofeed.Item) ([]*Item, time.Time, erro
for i := range items { for i := range items {
t := gofeedItemTS(items[i]) t := gofeedItemTS(items[i])
if *t != (time.Time{}) && t.Before(feed.Updated) { if *t != (time.Time{}) && t.Before(feed.Updated) {
logger.Logf("\tnot adding %v because its timestamp before %v", items[i].Link, feed.Updated) log.Printf("\tnot adding %v because its timestamp (%v) before updated (%v)", items[i].Link, *t, feed.Updated)
continue continue
} }
if t.After(latest) { if t.After(latest) {
latest = *t latest = *t
} }
if ok, _ := regexp.MatchString(feed.ItemFilter, items[i].Title); !ok { if ok, _ := regexp.MatchString(feed.ItemFilter, items[i].Title); !ok {
logger.Logf("\tnot adding %v because its title doesnt match item filter %v", items[i].Link, feed.ItemFilter) //log.Printf("\tnot adding %v because its title doesnt match item filter %v", items[i].Link, feed.ItemFilter)
continue continue
} }
item := FromGofeedItem(items[i], feed.ContentFilter) item := FromGofeedItem(items[i], feed.ContentFilter)
newitems = append(newitems, item) newitems = append(newitems, item)
} }
logger.Logf("\tnew latest is %v", latest) log.Printf("\tnew latest is %v from %v", latest, feed.Updated)
return newitems, latest, nil return newitems, latest, nil
} }

0
rss/feed_test.go Normal file → Executable file
View File

25
rss/item.go Normal file → Executable file
View File

@@ -16,17 +16,19 @@ import (
) )
type Item struct { type Item struct {
Name string Name string
Link string Link string
Content string Content string
TS time.Time Enclosures []string
TS time.Time
} }
func (item *Item) String() string { func (item *Item) String() string {
return fmt.Sprintf("Name %v, Link %v, Content %v, TS %v", return fmt.Sprintf("Name %v, Link %v, Content %v, Enclosures %v, TS %v",
item.Name, item.Name,
item.Link, item.Link,
len(item.Content), len(item.Content),
item.Enclosures,
item.TS.Local(), item.TS.Local(),
) )
} }
@@ -51,11 +53,16 @@ func (item *Item) Serialize() ([]byte, error) {
} }
func FromGofeedItem(gfitem *gofeed.Item, filter string) *Item { func FromGofeedItem(gfitem *gofeed.Item, filter string) *Item {
enclosures := []string{}
for i := range gfitem.Enclosures {
enclosures = append(enclosures, gfitem.Enclosures[i].URL)
}
item := &Item{ item := &Item{
Name: gfitem.Title, Name: gfitem.Title,
Link: gfitem.Link, Link: gfitem.Link,
Content: "", Content: "",
TS: *gofeedItemTS(gfitem), Enclosures: enclosures,
TS: *gofeedItemTS(gfitem),
} }
content := gfitem.Content content := gfitem.Content
if content == "" { if content == "" {

0
rss/item_test.go Normal file → Executable file
View File

5
rss/xml.go Normal file → Executable file
View File

@@ -19,8 +19,9 @@ func ToRSS(feed *Feed, items []*Item) (string, error) {
v := &url.Values{} v := &url.Values{}
v.Add("url", feed.ID()+"."+items[i].ID()) v.Add("url", feed.ID()+"."+items[i].ID())
root.Items[i] = &feeds.Item{ root.Items[i] = &feeds.Item{
Title: items[i].Name, Title: items[i].Name,
Link: &feeds.Link{Href: "/api/feed/item?" + v.Encode()}, //Link: &feeds.Link{Href: "/api/feed/item?" + v.Encode()},
Link: &feeds.Link{Href: items[i].Link},
Description: items[i].Content, Description: items[i].Content,
Created: items[i].TS, Created: items[i].TS,
} }

39
server/server.go Normal file → Executable file
View File

@@ -3,7 +3,7 @@ package server
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"local/logger" "log"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -22,15 +22,24 @@ type Server struct {
getFeedHandler func(string, int) (string, error) getFeedHandler func(string, int) (string, error)
getFeedItemHandler func(string) (string, error) getFeedItemHandler func(string) (string, error)
getFeedTagHandler func(string) (string, error) getFeedTagHandler func(string) (string, error)
getListTagHandler func(string, int) (string, error)
} }
func New(addr string, newFeedHandler func(string, string, string, []string, time.Duration), getFeedHandler func(string, int) (string, error), getFeedItemHandler func(string) (string, error), getFeedTagHandler func(string) (string, error)) (*Server, error) { func New(
addr string,
newFeedHandler func(string, string, string, []string, time.Duration),
getFeedHandler func(string, int) (string, error),
getFeedItemHandler func(string) (string, error),
getFeedTagHandler func(string) (string, error),
getListTagHandler func(string, int) (string, error),
) (*Server, error) {
return &Server{ return &Server{
addr: addr, addr: addr,
newFeedHandler: newFeedHandler, newFeedHandler: newFeedHandler,
getFeedHandler: getFeedHandler, getFeedHandler: getFeedHandler,
getFeedItemHandler: getFeedItemHandler, getFeedItemHandler: getFeedItemHandler,
getFeedTagHandler: getFeedTagHandler, getFeedTagHandler: getFeedTagHandler,
getListTagHandler: getListTagHandler,
}, nil }, nil
} }
@@ -81,6 +90,8 @@ func (s *Server) api(w http.ResponseWriter, r *http.Request) {
switch advance(r) { switch advance(r) {
case "feed": case "feed":
s.feed(w, r) s.feed(w, r)
case "list":
s.list(w, r)
default: default:
s.notFound(w, r) s.notFound(w, r)
} }
@@ -155,7 +166,7 @@ func (s *Server) getFeedTag(w http.ResponseWriter, r *http.Request) {
} }
feedBody, err := s.getFeedTagHandler(url) feedBody, err := s.getFeedTagHandler(url)
if err != nil { if err != nil {
logger.Logf("cannot get feed tag %s: %v", url, err) log.Printf("cannot get feed tag %s: %v", url, err)
s.mybad(w, r) s.mybad(w, r)
return return
} }
@@ -170,7 +181,7 @@ func (s *Server) getFeedItem(w http.ResponseWriter, r *http.Request) {
} }
feedBody, err := s.getFeedItemHandler(url) feedBody, err := s.getFeedItemHandler(url)
if err != nil { if err != nil {
logger.Logf("cannot get feed item %s: %v", url, err) log.Printf("cannot get feed item %s: %v", url, err)
s.mybad(w, r) s.mybad(w, r)
return return
} }
@@ -191,13 +202,31 @@ func (s *Server) getFeed(w http.ResponseWriter, r *http.Request) {
} }
feedBody, err := s.getFeedHandler(url, limit) feedBody, err := s.getFeedHandler(url, limit)
if err != nil { if err != nil {
logger.Logf("cannot get feed %s: %v", url, err) log.Printf("cannot get feed %s: %v", url, err)
s.mybad(w, r) s.mybad(w, r)
return return
} }
fmt.Fprintln(w, feedBody) fmt.Fprintln(w, feedBody)
} }
func (s *Server) list(w http.ResponseWriter, r *http.Request) {
switch advance(r) {
case "tag":
s.listTag(w, r)
default:
s.notFound(w, r)
}
}
func (s *Server) listTag(w http.ResponseWriter, r *http.Request) {
tag := advance(r)
out, err := s.getListTagHandler(tag, -1)
if err != nil {
panic(err)
}
fmt.Fprintln(w, out)
}
func validURL(loc string) bool { func validURL(loc string) bool {
_, err := url.ParseRequestURI(loc) _, err := url.ParseRequestURI(loc)
return err == nil return err == nil

8
server/server_test.go Normal file → Executable file
View File

@@ -19,7 +19,7 @@ func Test_Server(t *testing.T) {
testPort = strings.Split(server.Listener.Addr().String(), ":")[1] testPort = strings.Split(server.Listener.Addr().String(), ":")[1]
var err error var err error
s, err := New(testPort, func(string, string, string, []string, time.Duration) {}, func(string, int) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }) s, err := New(testPort, func(string, string, string, []string, time.Duration) {}, func(string, int) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string) (string, error) { return "", nil }, func(string, int) (string, error) { return "", nil })
if err != nil { if err != nil {
t.Errorf("failed to create server: %v", err) t.Errorf("failed to create server: %v", err)
} }
@@ -58,6 +58,12 @@ func Test_Server(t *testing.T) {
if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil { if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
} }
if err := checkStatus("GET", "api/feed/tag/b", http.StatusOK); err != nil {
t.Errorf(err.Error())
}
if err := checkStatus("GET", "api/list/tag/b", http.StatusOK); err != nil {
t.Errorf(err.Error())
}
syscall.Kill(syscall.Getpid(), syscall.SIGINT) syscall.Kill(syscall.Getpid(), syscall.SIGINT)
} }

0
store/bolt.go Normal file → Executable file
View File

0
store/bolt_test.go Normal file → Executable file
View File

0
store/store.go Normal file → Executable file
View File