mirror of
https://github.com/nkanaev/yarr.git
synced 2026-06-15 12:35:04 +00:00
move structs to model
This commit is contained in:
@@ -1,22 +1,27 @@
|
||||
package storage
|
||||
package factory
|
||||
|
||||
import (
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
Close() error
|
||||
Migrate() error
|
||||
CountItems() int
|
||||
CreateFeed(params CreateFeedParams) *Feed
|
||||
CreateFeed(params model.CreateFeedParams) *model.Feed
|
||||
CreateFolder(title string) *Folder
|
||||
CreateItems(items []Item) bool
|
||||
DeleteFeed(feedId int64) bool
|
||||
DeleteFolder(folderId int64) bool
|
||||
DeleteOldItems()
|
||||
FeedStats() []FeedStat
|
||||
GetFeed(id int64) *Feed
|
||||
GetFeed(id int64) *model.Feed
|
||||
GetFeedState(feedID int64) (*FeedState, error)
|
||||
GetItem(id int64) *Item
|
||||
GetSettings() Settings
|
||||
ListFeedStates() ([]FeedState, error)
|
||||
ListFeeds() []Feed
|
||||
ListFolders() []Folder
|
||||
ListFeeds() []model.Feed
|
||||
ListFolders() []model.Folder
|
||||
ListItems(filter ItemFilter, limit int, newestFirst bool, withContent bool) []Item
|
||||
MarkItemsRead(filter MarkFilter) bool
|
||||
UpdateFeed(feedId int64, params UpdateFeedParams) (bool, error)
|
||||
@@ -25,4 +30,3 @@ type Storage interface {
|
||||
UpdateItemStatus(item_id int64, status ItemStatus) bool
|
||||
UpdateSettings(params UpdateSettingsParams) bool
|
||||
}
|
||||
|
||||
182
src/storage/model/model.go
Normal file
182
src/storage/model/model.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Feed struct {
|
||||
Id int64 `json:"id"`
|
||||
FolderId *int64 `json:"folder_id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Link string `json:"link"`
|
||||
FeedLink string `json:"feed_link"`
|
||||
Icon *[]byte `json:"icon,omitempty"`
|
||||
HasIcon bool `json:"has_icon"`
|
||||
}
|
||||
|
||||
type CreateFeedParams struct {
|
||||
Title string
|
||||
Description string
|
||||
Link string
|
||||
FeedLink string
|
||||
FolderID *int64
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Id int64 `json:"id"`
|
||||
GUID string `json:"guid"`
|
||||
FeedId int64 `json:"feed_id"`
|
||||
Title string `json:"title"`
|
||||
Link string `json:"link"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Date time.Time `json:"date"`
|
||||
Status ItemStatus `json:"status"`
|
||||
MediaLinks MediaLinks `json:"media_links"`
|
||||
}
|
||||
|
||||
type ItemStatus int
|
||||
|
||||
const (
|
||||
UNREAD ItemStatus = 0
|
||||
READ ItemStatus = 1
|
||||
STARRED ItemStatus = 2
|
||||
)
|
||||
|
||||
|
||||
var StatusRepresentations = map[ItemStatus]string{
|
||||
UNREAD: "unread",
|
||||
READ: "read",
|
||||
STARRED: "starred",
|
||||
}
|
||||
|
||||
var StatusValues = map[string]ItemStatus{
|
||||
"unread": UNREAD,
|
||||
"read": READ,
|
||||
"starred": STARRED,
|
||||
}
|
||||
|
||||
func (s ItemStatus) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(StatusRepresentations[s])
|
||||
}
|
||||
|
||||
func (s *ItemStatus) UnmarshalJSON(b []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(b, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = StatusValues[str]
|
||||
return nil
|
||||
}
|
||||
|
||||
type MediaLink struct {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type MediaLinks []MediaLink
|
||||
|
||||
type ItemFilter struct {
|
||||
FolderID *int64
|
||||
FeedID *int64
|
||||
Status *ItemStatus
|
||||
Search *string
|
||||
After *int64
|
||||
IDs *[]int64
|
||||
SinceID *int64
|
||||
MaxID *int64
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
type MarkFilter struct {
|
||||
FolderID *int64
|
||||
FeedID *int64
|
||||
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
type Folder struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
IsExpanded bool `json:"is_expanded"`
|
||||
}
|
||||
|
||||
type UpdateFolderParams struct {
|
||||
Title *string
|
||||
IsExpanded *bool
|
||||
}
|
||||
|
||||
|
||||
type Settings struct {
|
||||
Filter string `json:"filter"`
|
||||
Feed string `json:"feed"`
|
||||
FeedListWidth int `json:"feed_list_width"`
|
||||
ItemListWidth int `json:"item_list_width"`
|
||||
SortNewestFirst bool `json:"sort_newest_first"`
|
||||
ThemeName string `json:"theme_name"`
|
||||
ThemeFont string `json:"theme_font"`
|
||||
ThemeSize int `json:"theme_size"`
|
||||
RefreshRate int64 `json:"refresh_rate"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
type UpdateSettingsParams struct {
|
||||
Filter *string `json:"filter"`
|
||||
Feed *string `json:"feed"`
|
||||
FeedListWidth *int `json:"feed_list_width"`
|
||||
ItemListWidth *int `json:"item_list_width"`
|
||||
SortNewestFirst *bool `json:"sort_newest_first"`
|
||||
ThemeName *string `json:"theme_name"`
|
||||
ThemeFont *string `json:"theme_font"`
|
||||
ThemeSize *int `json:"theme_size"`
|
||||
RefreshRate *int64 `json:"refresh_rate"`
|
||||
Language *string `json:"language"`
|
||||
}
|
||||
|
||||
func (s Settings) Map() map[string]any {
|
||||
return map[string]any{
|
||||
"filter": s.Filter,
|
||||
"feed": s.Feed,
|
||||
"feed_list_width": s.FeedListWidth,
|
||||
"item_list_width": s.ItemListWidth,
|
||||
"sort_newest_first": s.SortNewestFirst,
|
||||
"theme_name": s.ThemeName,
|
||||
"theme_font": s.ThemeFont,
|
||||
"theme_size": s.ThemeSize,
|
||||
"refresh_rate": s.RefreshRate,
|
||||
"language": s.Language,
|
||||
}
|
||||
}
|
||||
|
||||
type FeedState struct {
|
||||
FeedID int64
|
||||
LastRefreshed time.Time
|
||||
LastError string
|
||||
HTTPLastModified string
|
||||
HTTPEtag string
|
||||
}
|
||||
|
||||
type UpdateFeedStateParams struct {
|
||||
LastRefreshed *time.Time
|
||||
LastError *string
|
||||
HTTPLastModified *string
|
||||
HTTPEtag *string
|
||||
}
|
||||
|
||||
type UpdateFeedParams struct {
|
||||
Title *string
|
||||
FeedLink *string
|
||||
FolderID Nullable[int64]
|
||||
Icon Nullable[[]byte]
|
||||
}
|
||||
|
||||
type Nullable[T any] struct {
|
||||
Set bool
|
||||
Value *T
|
||||
}
|
||||
|
||||
func SetNullable[T any](v *T) Nullable[T] {
|
||||
return Nullable[T]{Set: true, Value: v}
|
||||
}
|
||||
@@ -3,28 +3,11 @@ package sqlite
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
type Feed struct {
|
||||
Id int64 `json:"id"`
|
||||
FolderId *int64 `json:"folder_id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Link string `json:"link"`
|
||||
FeedLink string `json:"feed_link"`
|
||||
Icon *[]byte `json:"icon,omitempty"`
|
||||
HasIcon bool `json:"has_icon"`
|
||||
}
|
||||
|
||||
type CreateFeedParams struct {
|
||||
Title string
|
||||
Description string
|
||||
Link string
|
||||
FeedLink string
|
||||
FolderID *int64
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) CreateFeed(params CreateFeedParams) *Feed {
|
||||
func (s *SQLiteStorage) CreateFeed(params model.CreateFeedParams) *model.Feed {
|
||||
title := params.Title
|
||||
if title == "" {
|
||||
title = params.FeedLink
|
||||
@@ -73,13 +56,6 @@ func (s *SQLiteStorage) DeleteFeed(feedId int64) bool {
|
||||
return nrows == 1
|
||||
}
|
||||
|
||||
type UpdateFeedParams struct {
|
||||
Title *string
|
||||
FeedLink *string
|
||||
FolderID Nullable[int64]
|
||||
Icon Nullable[[]byte]
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) UpdateFeed(feedId int64, params UpdateFeedParams) (bool, error) {
|
||||
_, err := s.db.Exec(`
|
||||
update feeds set
|
||||
@@ -104,8 +80,8 @@ func (s *SQLiteStorage) UpdateFeed(feedId int64, params UpdateFeedParams) (bool,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) ListFeeds() []Feed {
|
||||
result := make([]Feed, 0)
|
||||
func (s *SQLiteStorage) ListFeeds() []model.Feed {
|
||||
result := make([]model.Feed, 0)
|
||||
rows, err := s.db.Query(`
|
||||
select id, folder_id, title, description, link, feed_link,
|
||||
ifnull(length(icon), 0) > 0 as has_icon
|
||||
@@ -117,7 +93,7 @@ func (s *SQLiteStorage) ListFeeds() []Feed {
|
||||
return result
|
||||
}
|
||||
for rows.Next() {
|
||||
var f Feed
|
||||
var f model.Feed
|
||||
err = rows.Scan(
|
||||
&f.Id,
|
||||
&f.FolderId,
|
||||
|
||||
@@ -5,14 +5,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type FeedState struct {
|
||||
FeedID int64
|
||||
LastRefreshed time.Time
|
||||
LastError string
|
||||
HTTPLastModified string
|
||||
HTTPEtag string
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) ListFeedStates() ([]FeedState, error) {
|
||||
rows, err := s.db.Query(`
|
||||
select
|
||||
@@ -72,13 +64,6 @@ func (s *SQLiteStorage) GetFeedState(feedID int64) (*FeedState, error) {
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
type UpdateFeedStateParams struct {
|
||||
LastRefreshed *time.Time
|
||||
LastError *string
|
||||
HTTPLastModified *string
|
||||
HTTPEtag *string
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) UpdateFeedState(feedID int64, params UpdateFeedStateParams) (bool, error) {
|
||||
lastError := params.LastError
|
||||
if lastError != nil && *lastError == "" {
|
||||
|
||||
@@ -5,12 +5,6 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type Folder struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
IsExpanded bool `json:"is_expanded"`
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) CreateFolder(title string) *Folder {
|
||||
expanded := true
|
||||
row := s.db.QueryRow(`
|
||||
@@ -38,11 +32,6 @@ func (s *SQLiteStorage) DeleteFolder(folderId int64) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type UpdateFolderParams struct {
|
||||
Title *string
|
||||
IsExpanded *bool
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) UpdateFolder(folderId int64, params UpdateFolderParams) (bool, error) {
|
||||
_, err := s.db.Exec(`
|
||||
update folders set
|
||||
|
||||
@@ -1,57 +1,20 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nkanaev/yarr/src/storage/model"
|
||||
)
|
||||
|
||||
type ItemStatus int
|
||||
|
||||
const (
|
||||
UNREAD ItemStatus = 0
|
||||
READ ItemStatus = 1
|
||||
STARRED ItemStatus = 2
|
||||
)
|
||||
|
||||
var StatusRepresentations = map[ItemStatus]string{
|
||||
UNREAD: "unread",
|
||||
READ: "read",
|
||||
STARRED: "starred",
|
||||
}
|
||||
|
||||
var StatusValues = map[string]ItemStatus{
|
||||
"unread": UNREAD,
|
||||
"read": READ,
|
||||
"starred": STARRED,
|
||||
}
|
||||
|
||||
func (s ItemStatus) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(StatusRepresentations[s])
|
||||
}
|
||||
|
||||
func (s *ItemStatus) UnmarshalJSON(b []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(b, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = StatusValues[str]
|
||||
return nil
|
||||
}
|
||||
|
||||
type MediaLink struct {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type MediaLinks []MediaLink
|
||||
|
||||
// TODO: serialize/deserialize
|
||||
func (m *MediaLinks) Scan(src any) error {
|
||||
switch data := src.(type) {
|
||||
case []byte:
|
||||
@@ -67,55 +30,6 @@ func (m MediaLinks) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Id int64 `json:"id"`
|
||||
GUID string `json:"guid"`
|
||||
FeedId int64 `json:"feed_id"`
|
||||
Title string `json:"title"`
|
||||
Link string `json:"link"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Date time.Time `json:"date"`
|
||||
Status ItemStatus `json:"status"`
|
||||
MediaLinks MediaLinks `json:"media_links"`
|
||||
}
|
||||
|
||||
type ItemFilter struct {
|
||||
FolderID *int64
|
||||
FeedID *int64
|
||||
Status *ItemStatus
|
||||
Search *string
|
||||
After *int64
|
||||
IDs *[]int64
|
||||
SinceID *int64
|
||||
MaxID *int64
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
type MarkFilter struct {
|
||||
FolderID *int64
|
||||
FeedID *int64
|
||||
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
type ItemList []Item
|
||||
|
||||
func (list ItemList) Len() int {
|
||||
return len(list)
|
||||
}
|
||||
|
||||
func (list ItemList) SortKey(i int) string {
|
||||
return list[i].Date.Format(time.RFC3339) + "::" + list[i].GUID
|
||||
}
|
||||
|
||||
func (list ItemList) Less(i, j int) bool {
|
||||
return list.SortKey(i) < list.SortKey(j)
|
||||
}
|
||||
|
||||
func (list ItemList) Swap(i, j int) {
|
||||
list[i], list[j] = list[j], list[i]
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) CreateItems(items []Item) bool {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
@@ -125,10 +39,13 @@ func (s *SQLiteStorage) CreateItems(items []Item) bool {
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
itemsSorted := ItemList(items)
|
||||
sort.Sort(itemsSorted)
|
||||
slices.SortStableFunc(items, func(a, b model.Item) int {
|
||||
sa := a.Date.Format(time.RFC3339) + "::" + a.GUID
|
||||
sb := b.Date.Format(time.RFC3339) + "::" + b.GUID
|
||||
return cmp.Compare(sa, sb)
|
||||
})
|
||||
|
||||
for _, item := range itemsSorted {
|
||||
for _, item := range items {
|
||||
_, err = tx.Exec(`
|
||||
insert into items (
|
||||
guid, feed_id, title, link, date,
|
||||
@@ -151,7 +68,7 @@ func (s *SQLiteStorage) CreateItems(items []Item) bool {
|
||||
sql.Named("media_links", item.MediaLinks),
|
||||
sql.Named("date_arrived", now),
|
||||
sql.Named("last_arrived", now),
|
||||
sql.Named("status", UNREAD),
|
||||
sql.Named("status", model.UNREAD),
|
||||
)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
|
||||
@@ -6,34 +6,6 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
Filter string `json:"filter"`
|
||||
Feed string `json:"feed"`
|
||||
FeedListWidth int `json:"feed_list_width"`
|
||||
ItemListWidth int `json:"item_list_width"`
|
||||
SortNewestFirst bool `json:"sort_newest_first"`
|
||||
ThemeName string `json:"theme_name"`
|
||||
ThemeFont string `json:"theme_font"`
|
||||
ThemeSize int `json:"theme_size"`
|
||||
RefreshRate int64 `json:"refresh_rate"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
func (s Settings) Map() map[string]any {
|
||||
return map[string]any{
|
||||
"filter": s.Filter,
|
||||
"feed": s.Feed,
|
||||
"feed_list_width": s.FeedListWidth,
|
||||
"item_list_width": s.ItemListWidth,
|
||||
"sort_newest_first": s.SortNewestFirst,
|
||||
"theme_name": s.ThemeName,
|
||||
"theme_font": s.ThemeFont,
|
||||
"theme_size": s.ThemeSize,
|
||||
"refresh_rate": s.RefreshRate,
|
||||
"language": s.Language,
|
||||
}
|
||||
}
|
||||
|
||||
func settingsDefaults() Settings {
|
||||
return Settings{
|
||||
Filter: "",
|
||||
@@ -89,19 +61,6 @@ func (s *SQLiteStorage) GetSettings() Settings {
|
||||
return result
|
||||
}
|
||||
|
||||
type UpdateSettingsParams struct {
|
||||
Filter *string `json:"filter"`
|
||||
Feed *string `json:"feed"`
|
||||
FeedListWidth *int `json:"feed_list_width"`
|
||||
ItemListWidth *int `json:"item_list_width"`
|
||||
SortNewestFirst *bool `json:"sort_newest_first"`
|
||||
ThemeName *string `json:"theme_name"`
|
||||
ThemeFont *string `json:"theme_font"`
|
||||
ThemeSize *int `json:"theme_size"`
|
||||
RefreshRate *int64 `json:"refresh_rate"`
|
||||
Language *string `json:"language"`
|
||||
}
|
||||
|
||||
func (s *SQLiteStorage) UpdateSettings(params UpdateSettingsParams) bool {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
|
||||
@@ -21,15 +21,6 @@ type SQLiteStorage struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
type Nullable[T any] struct {
|
||||
Set bool
|
||||
Value *T
|
||||
}
|
||||
|
||||
func SetNullable[T any](v *T) Nullable[T] {
|
||||
return Nullable[T]{Set: true, Value: v}
|
||||
}
|
||||
|
||||
func New(path string) (*SQLiteStorage, error) {
|
||||
if pos := strings.IndexRune(path, '?'); pos == -1 {
|
||||
params := "_journal=WAL&_sync=NORMAL&_busy_timeout=5000&cache=shared"
|
||||
|
||||
Reference in New Issue
Block a user