mirror of
https://github.com/nkanaev/yarr.git
synced 2026-06-15 12:35:04 +00:00
add storage interface, fix all references
This commit is contained in:
@@ -14,7 +14,7 @@ type Middleware struct {
|
||||
Password string
|
||||
BasePath string
|
||||
Public []string
|
||||
DB *storage.Storage
|
||||
DB storage.Storage
|
||||
}
|
||||
|
||||
func (m *Middleware) Handler(c *router.Context) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/nkanaev/yarr/src/server/auth"
|
||||
"github.com/nkanaev/yarr/src/server/router"
|
||||
"github.com/nkanaev/yarr/src/storage"
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
type FeverGroup struct {
|
||||
@@ -61,7 +62,7 @@ func writeFeverJSON(c *router.Context, data map[string]any, lastRefreshed int64)
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
func getLastRefreshedOnTime(feedStates []storage.FeedState) int64 {
|
||||
func getLastRefreshedOnTime(feedStates []model.FeedState) int64 {
|
||||
var lastRefreshed int64
|
||||
for _, state := range feedStates {
|
||||
if state.LastRefreshed.Unix() > lastRefreshed {
|
||||
@@ -140,7 +141,7 @@ func joinInts(values []int64) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func feedGroups(db *storage.Storage) []*FeverFeedsGroup {
|
||||
func feedGroups(db storage.Storage) []*FeverFeedsGroup {
|
||||
feeds := db.ListFeeds()
|
||||
|
||||
groupFeeds := make(map[int64][]int64)
|
||||
@@ -176,7 +177,7 @@ func (s *Server) feverGroupsHandler(c *router.Context) {
|
||||
func (s *Server) feverFeedsHandler(c *router.Context) {
|
||||
feeds := s.db.ListFeeds()
|
||||
states, _ := s.db.ListFeedStates()
|
||||
statesMap := make(map[int64]storage.FeedState)
|
||||
statesMap := make(map[int64]model.FeedState)
|
||||
for _, state := range states {
|
||||
statesMap[state.FeedID] = state
|
||||
}
|
||||
@@ -230,7 +231,7 @@ func (s *Server) feverFaviconsHandler(c *router.Context) {
|
||||
const listLimit = 50
|
||||
|
||||
func (s *Server) feverItemsHandler(c *router.Context) {
|
||||
filter := storage.ItemFilter{}
|
||||
filter := model.ItemFilter{}
|
||||
query := c.Req.URL.Query()
|
||||
|
||||
switch {
|
||||
@@ -262,11 +263,11 @@ func (s *Server) feverItemsHandler(c *router.Context) {
|
||||
time := date.Unix()
|
||||
|
||||
isSaved := 0
|
||||
if item.Status == storage.STARRED {
|
||||
if item.Status == model.STARRED {
|
||||
isSaved = 1
|
||||
}
|
||||
isRead := 0
|
||||
if item.Status == storage.READ {
|
||||
if item.Status == model.READ {
|
||||
isRead = 1
|
||||
}
|
||||
feverItems[i] = FeverItem{
|
||||
@@ -299,10 +300,10 @@ func (s *Server) feverLinksHandler(c *router.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) feverUnreadItemIDsHandler(c *router.Context) {
|
||||
status := storage.UNREAD
|
||||
status := model.UNREAD
|
||||
itemIds := make([]int64, 0)
|
||||
|
||||
itemFilter := storage.ItemFilter{
|
||||
itemFilter := model.ItemFilter{
|
||||
Status: &status,
|
||||
}
|
||||
for {
|
||||
@@ -322,10 +323,10 @@ func (s *Server) feverUnreadItemIDsHandler(c *router.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) feverSavedItemIDsHandler(c *router.Context) {
|
||||
status := storage.STARRED
|
||||
status := model.STARRED
|
||||
itemIds := make([]int64, 0)
|
||||
|
||||
itemFilter := storage.ItemFilter{
|
||||
itemFilter := model.ItemFilter{
|
||||
Status: &status,
|
||||
}
|
||||
for {
|
||||
@@ -353,16 +354,16 @@ func (s *Server) feverMarkHandler(c *router.Context) {
|
||||
|
||||
switch c.Req.Form.Get("mark") {
|
||||
case "item":
|
||||
var status storage.ItemStatus
|
||||
var status model.ItemStatus
|
||||
switch c.Req.Form.Get("as") {
|
||||
case "read":
|
||||
status = storage.READ
|
||||
status = model.READ
|
||||
case "unread":
|
||||
status = storage.UNREAD
|
||||
status = model.UNREAD
|
||||
case "saved":
|
||||
status = storage.STARRED
|
||||
status = model.STARRED
|
||||
case "unsaved":
|
||||
status = storage.READ
|
||||
status = model.READ
|
||||
default:
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
@@ -372,7 +373,7 @@ func (s *Server) feverMarkHandler(c *router.Context) {
|
||||
if c.Req.Form.Get("as") != "read" {
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
markFilter := storage.MarkFilter{FeedID: &id}
|
||||
markFilter := model.MarkFilter{FeedID: &id}
|
||||
x, _ := strconv.ParseInt(c.Req.Form.Get("before"), 10, 64)
|
||||
if x > 0 {
|
||||
before := time.Unix(x, 0).UTC()
|
||||
@@ -383,7 +384,7 @@ func (s *Server) feverMarkHandler(c *router.Context) {
|
||||
if c.Req.Form.Get("as") != "read" {
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
markFilter := storage.MarkFilter{}
|
||||
markFilter := model.MarkFilter{}
|
||||
if id > 0 {
|
||||
markFilter.FolderID = &id
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package server
|
||||
|
||||
import "github.com/nkanaev/yarr/src/storage"
|
||||
import "github.com/nkanaev/yarr/src/storage/model"
|
||||
|
||||
type ItemUpdateForm struct {
|
||||
Status *storage.ItemStatus `json:"status,omitempty"`
|
||||
Status *model.ItemStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type FolderCreateForm struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/nkanaev/yarr/src/server/gzip"
|
||||
"github.com/nkanaev/yarr/src/server/opml"
|
||||
"github.com/nkanaev/yarr/src/server/router"
|
||||
"github.com/nkanaev/yarr/src/storage"
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
"github.com/nkanaev/yarr/src/worker"
|
||||
)
|
||||
|
||||
@@ -141,7 +141,7 @@ func (s *Server) handleFolder(c *router.Context) {
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.db.UpdateFolder(id, storage.UpdateFolderParams{
|
||||
s.db.UpdateFolder(id, model.UpdateFolderParams{
|
||||
Title: body.Title,
|
||||
IsExpanded: body.IsExpanded,
|
||||
})
|
||||
@@ -248,7 +248,7 @@ func (s *Server) handleFeedList(c *router.Context) {
|
||||
map[string]any{"status": "multiple", "choice": result.Sources},
|
||||
)
|
||||
case result.Feed != nil:
|
||||
feed := s.db.CreateFeed(storage.CreateFeedParams{
|
||||
feed := s.db.CreateFeed(model.CreateFeedParams{
|
||||
Title: result.Feed.Title,
|
||||
Link: result.Feed.SiteURL,
|
||||
FeedLink: result.FeedLink,
|
||||
@@ -288,7 +288,7 @@ func (s *Server) handleFeed(c *router.Context) {
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
params := storage.UpdateFeedParams{}
|
||||
params := model.UpdateFeedParams{}
|
||||
if title, ok := body["title"]; ok {
|
||||
if reflect.TypeOf(title).Kind() == reflect.String {
|
||||
t := title.(string)
|
||||
@@ -297,10 +297,10 @@ func (s *Server) handleFeed(c *router.Context) {
|
||||
}
|
||||
if f_id, ok := body["folder_id"]; ok {
|
||||
if f_id == nil {
|
||||
params.FolderID = storage.SetNullable[int64](nil)
|
||||
params.FolderID = model.SetNullable[int64](nil)
|
||||
} else if reflect.TypeOf(f_id).Kind() == reflect.Float64 {
|
||||
folderId := int64(f_id.(float64))
|
||||
params.FolderID = storage.SetNullable(&folderId)
|
||||
params.FolderID = model.SetNullable(&folderId)
|
||||
}
|
||||
}
|
||||
if link, ok := body["feed_link"]; ok {
|
||||
@@ -366,7 +366,7 @@ func (s *Server) handleItemList(c *router.Context) {
|
||||
perPage := 20
|
||||
query := c.Req.URL.Query()
|
||||
|
||||
filter := storage.ItemFilter{}
|
||||
filter := model.ItemFilter{}
|
||||
if folderID, err := c.QueryInt64("folder_id"); err == nil {
|
||||
filter.FolderID = &folderID
|
||||
}
|
||||
@@ -377,7 +377,7 @@ func (s *Server) handleItemList(c *router.Context) {
|
||||
filter.After = &after
|
||||
}
|
||||
if status := query.Get("status"); len(status) != 0 {
|
||||
statusValue := storage.StatusValues[status]
|
||||
statusValue := model.StatusValues[status]
|
||||
filter.Status = &statusValue
|
||||
}
|
||||
if search := query.Get("search"); len(search) != 0 {
|
||||
@@ -403,7 +403,7 @@ func (s *Server) handleItemList(c *router.Context) {
|
||||
"has_more": hasMore,
|
||||
})
|
||||
} else if c.Req.Method == "PUT" {
|
||||
filter := storage.MarkFilter{}
|
||||
filter := model.MarkFilter{}
|
||||
|
||||
if folderID, err := c.QueryInt64("folder_id"); err == nil {
|
||||
filter.FolderID = &folderID
|
||||
@@ -422,7 +422,7 @@ func (s *Server) handleSettings(c *router.Context) {
|
||||
if c.Req.Method == "GET" {
|
||||
c.JSON(http.StatusOK, s.db.GetSettings())
|
||||
} else if c.Req.Method == "PUT" {
|
||||
var params storage.UpdateSettingsParams
|
||||
var params model.UpdateSettingsParams
|
||||
if err := json.NewDecoder(c.Req.Body).Decode(¶ms); err != nil {
|
||||
c.Out.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
@@ -452,7 +452,7 @@ func (s *Server) handleOPMLImport(c *router.Context) {
|
||||
return
|
||||
}
|
||||
for _, f := range doc.Feeds {
|
||||
s.db.CreateFeed(storage.CreateFeedParams{
|
||||
s.db.CreateFeed(model.CreateFeedParams{
|
||||
Title: f.Title,
|
||||
Link: f.SiteUrl,
|
||||
FeedLink: f.FeedUrl,
|
||||
@@ -461,7 +461,7 @@ func (s *Server) handleOPMLImport(c *router.Context) {
|
||||
for _, f := range doc.Folders {
|
||||
folder := s.db.CreateFolder(f.Title)
|
||||
for _, ff := range f.AllFeeds() {
|
||||
s.db.CreateFeed(storage.CreateFeedParams{
|
||||
s.db.CreateFeed(model.CreateFeedParams{
|
||||
Title: ff.Title,
|
||||
Link: ff.SiteUrl,
|
||||
FeedLink: ff.FeedUrl,
|
||||
@@ -485,7 +485,7 @@ func (s *Server) handleOPMLExport(c *router.Context) {
|
||||
|
||||
doc := opml.Folder{}
|
||||
|
||||
feedsByFolderID := make(map[int64][]*storage.Feed)
|
||||
feedsByFolderID := make(map[int64][]*model.Feed)
|
||||
for _, feed := range s.db.ListFeeds() {
|
||||
if feed.FolderId == nil {
|
||||
doc.Feeds = append(doc.Feeds, opml.Feed{
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/nkanaev/yarr/src/storage"
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
func TestStatic(t *testing.T) {
|
||||
@@ -79,8 +80,8 @@ func TestFeedIcons(t *testing.T) {
|
||||
log.SetOutput(io.Discard)
|
||||
db, _ := storage.New(":memory:")
|
||||
icon := []byte("test")
|
||||
feed := db.CreateFeed(storage.CreateFeedParams{})
|
||||
db.UpdateFeed(feed.Id, storage.UpdateFeedParams{Icon: storage.SetNullable(&icon)})
|
||||
feed := db.CreateFeed(model.CreateFeedParams{})
|
||||
db.UpdateFeed(feed.Id, model.UpdateFeedParams{Icon: model.SetNullable(&icon)})
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type Server struct {
|
||||
Addr string
|
||||
db *storage.Storage
|
||||
db storage.Storage
|
||||
worker *worker.Worker
|
||||
cache map[string]any
|
||||
cache_mutex *sync.Mutex
|
||||
@@ -29,7 +29,7 @@ type Server struct {
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
func NewServer(db *storage.Storage, addr string) *Server {
|
||||
func NewServer(db storage.Storage, addr string) *Server {
|
||||
return &Server{
|
||||
db: db,
|
||||
Addr: addr,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package factory
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
"github.com/nkanaev/yarr/src/storage/sqlite"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
Close() error
|
||||
Migrate() error
|
||||
CountItems() int
|
||||
CreateFeed(params model.CreateFeedParams) *model.Feed
|
||||
CreateFolder(title string) *model.Folder
|
||||
@@ -30,3 +30,7 @@ type Storage interface {
|
||||
UpdateItemStatus(item_id int64, status model.ItemStatus) bool
|
||||
UpdateSettings(params model.UpdateSettingsParams) bool
|
||||
}
|
||||
|
||||
func New(path string) (Storage, error) {
|
||||
return sqlite.New(path)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/nkanaev/yarr/src/content/scraper"
|
||||
"github.com/nkanaev/yarr/src/parser"
|
||||
"github.com/nkanaev/yarr/src/storage"
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
@@ -139,28 +140,28 @@ func findFavicon(siteUrl, feedUrl string) (*[]byte, error) {
|
||||
return &emptyIcon, nil
|
||||
}
|
||||
|
||||
func ConvertItems(items []parser.Item, feed storage.Feed) []storage.Item {
|
||||
result := make([]storage.Item, len(items))
|
||||
func ConvertItems(items []parser.Item, feed model.Feed) []model.Item {
|
||||
result := make([]model.Item, len(items))
|
||||
for i, item := range items {
|
||||
mediaLinks := make(storage.MediaLinks, 0)
|
||||
mediaLinks := make(model.MediaLinks, 0)
|
||||
for _, link := range item.MediaLinks {
|
||||
mediaLinks = append(mediaLinks, storage.MediaLink(link))
|
||||
mediaLinks = append(mediaLinks, model.MediaLink(link))
|
||||
}
|
||||
result[i] = storage.Item{
|
||||
result[i] = model.Item{
|
||||
GUID: item.GUID,
|
||||
FeedId: feed.Id,
|
||||
Title: item.Title,
|
||||
Link: item.URL,
|
||||
Content: item.Content,
|
||||
Date: item.Date,
|
||||
Status: storage.UNREAD,
|
||||
Status: model.UNREAD,
|
||||
MediaLinks: mediaLinks,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func listItems(f storage.Feed, db *storage.Storage) ([]storage.Item, error) {
|
||||
func listItems(f model.Feed, db storage.Storage) ([]model.Item, error) {
|
||||
lmod := ""
|
||||
etag := ""
|
||||
if state, _ := db.GetFeedState(f.Id); state != nil {
|
||||
@@ -193,7 +194,7 @@ func listItems(f storage.Feed, db *storage.Storage) ([]storage.Item, error) {
|
||||
etag = res.Header.Get("Etag")
|
||||
now := time.Now().UTC()
|
||||
if lmod != "" || etag != "" {
|
||||
db.UpdateFeedState(f.Id, storage.UpdateFeedStateParams{
|
||||
db.UpdateFeedState(f.Id, model.UpdateFeedStateParams{
|
||||
HTTPLastModified: &lmod,
|
||||
HTTPEtag: &etag,
|
||||
LastRefreshed: &now,
|
||||
|
||||
@@ -7,19 +7,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nkanaev/yarr/src/storage"
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
const NUM_WORKERS = 4
|
||||
|
||||
type Worker struct {
|
||||
db *storage.Storage
|
||||
db storage.Storage
|
||||
pending *int32
|
||||
refresh *time.Ticker
|
||||
reflock sync.Mutex
|
||||
stopper chan bool
|
||||
}
|
||||
|
||||
func NewWorker(db *storage.Storage) *Worker {
|
||||
func NewWorker(db storage.Storage) *Worker {
|
||||
pending := int32(0)
|
||||
return &Worker{db: db, pending: &pending}
|
||||
}
|
||||
@@ -39,13 +40,13 @@ func (w *Worker) StartFeedCleaner() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *Worker) FindFeedFavicon(feed storage.Feed) {
|
||||
func (w *Worker) FindFeedFavicon(feed model.Feed) {
|
||||
icon, err := findFavicon(feed.Link, feed.FeedLink)
|
||||
if err != nil {
|
||||
log.Printf("Failed to find favicon for %s (%s): %s", feed.FeedLink, feed.Link, err)
|
||||
}
|
||||
if icon != nil {
|
||||
w.db.UpdateFeed(feed.Id, storage.UpdateFeedParams{Icon: storage.SetNullable(icon)})
|
||||
w.db.UpdateFeed(feed.Id, model.UpdateFeedParams{Icon: model.SetNullable(icon)})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,11 +100,11 @@ func (w *Worker) RefreshFeeds() {
|
||||
go w.refresher(feeds)
|
||||
}
|
||||
|
||||
func (w *Worker) refresher(feeds []storage.Feed) {
|
||||
func (w *Worker) refresher(feeds []model.Feed) {
|
||||
// w.db.ResetFeedErrors()
|
||||
|
||||
srcqueue := make(chan storage.Feed, len(feeds))
|
||||
dstqueue := make(chan []storage.Item)
|
||||
srcqueue := make(chan model.Feed, len(feeds))
|
||||
dstqueue := make(chan []model.Item)
|
||||
|
||||
for range NUM_WORKERS {
|
||||
go w.worker(srcqueue, dstqueue)
|
||||
@@ -125,15 +126,15 @@ func (w *Worker) refresher(feeds []storage.Feed) {
|
||||
log.Printf("Finished refreshing %d feeds", len(feeds))
|
||||
}
|
||||
|
||||
func (w *Worker) worker(srcqueue <-chan storage.Feed, dstqueue chan<- []storage.Item) {
|
||||
func (w *Worker) worker(srcqueue <-chan model.Feed, dstqueue chan<- []model.Item) {
|
||||
for feed := range srcqueue {
|
||||
empty := ""
|
||||
w.db.UpdateFeedState(feed.Id, storage.UpdateFeedStateParams{LastError: &empty})
|
||||
w.db.UpdateFeedState(feed.Id, model.UpdateFeedStateParams{LastError: &empty})
|
||||
|
||||
items, err := listItems(feed, w.db)
|
||||
if err != nil {
|
||||
errMsg := err.Error()
|
||||
w.db.UpdateFeedState(feed.Id, storage.UpdateFeedStateParams{LastError: &errMsg})
|
||||
w.db.UpdateFeedState(feed.Id, model.UpdateFeedStateParams{LastError: &errMsg})
|
||||
}
|
||||
if len(items) > 0 && !feed.HasIcon {
|
||||
w.FindFeedFavicon(feed)
|
||||
|
||||
Reference in New Issue
Block a user