mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-24 00:33:14 +00:00
remove fever code
This commit is contained in:
parent
62e0caa950
commit
db7a178a8d
313
server/fever.go
313
server/fever.go
@ -1,313 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"github.com/nkanaev/yarr/storage"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var feverHandlers = map[string]func(rw http.ResponseWriter, req *http.Request){
|
|
||||||
"groups": FeverGroupsHandler,
|
|
||||||
"feeds": FeverFeedsHandler,
|
|
||||||
"unread_item_ids": FeverFilteredItemIDsHandler,
|
|
||||||
"saved_item_ids": FeverFilteredItemIDsHandler,
|
|
||||||
|
|
||||||
"favicons": FeverFaviconsHandler,
|
|
||||||
"items": FeverItemsHandler,
|
|
||||||
"links": FeverLinksHandler,
|
|
||||||
"mark": FeverMarkHandler,
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeverGroup struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeverFeedsGroup struct {
|
|
||||||
GroupID int64 `json:"group_id"`
|
|
||||||
FeedIDs string `json:"feed_ids"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeverFeed struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
FaviconID int64 `json:"favicon_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
SiteUrl string `json:"site_url"`
|
|
||||||
IsSpark int `json:"is_spark"`
|
|
||||||
LastUpdated int64 `json:"last_updated_on_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeverItem struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
FeedID int64 `json:"feed_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Author string `json:"author"`
|
|
||||||
HTML string `json:"html"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
IsSaved int `json:"is_saved"`
|
|
||||||
IsRead int `json:"is_read"`
|
|
||||||
CreatedAt int64 `json:"created_on_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeverFavicon struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFeverJSON(rw http.ResponseWriter, data map[string]interface{}) {
|
|
||||||
data["api_version"] = 1
|
|
||||||
data["auth"] = 1
|
|
||||||
// NOTE: does not adhere to the API spec
|
|
||||||
data["last_refreshed_on_time"] = time.Now().Unix()
|
|
||||||
writeJSON(rw, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
query := req.URL.Query()
|
|
||||||
fmt.Println(req.URL.String())
|
|
||||||
for key, handler := range feverHandlers {
|
|
||||||
if _, ok := query[key]; ok {
|
|
||||||
handler(rw, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeJSON(rw, map[string]interface{}{
|
|
||||||
"api_version": 1,
|
|
||||||
"auth": 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinInts(values []int64) string {
|
|
||||||
var result strings.Builder
|
|
||||||
for i, val := range values {
|
|
||||||
fmt.Fprintf(&result, "%d", val)
|
|
||||||
if i != len(values)-1 {
|
|
||||||
result.WriteString(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func feedGroups(db *storage.Storage) []*FeverFeedsGroup {
|
|
||||||
feeds := db.ListFeeds()
|
|
||||||
|
|
||||||
groupFeeds := make(map[int64][]int64)
|
|
||||||
for _, feed := range feeds {
|
|
||||||
// TODO: what about top-level feeds?
|
|
||||||
if feed.FolderId == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
groupFeeds[*feed.FolderId] = append(groupFeeds[*feed.FolderId], feed.Id)
|
|
||||||
}
|
|
||||||
result := make([]*FeverFeedsGroup, 0)
|
|
||||||
for groupId, feedIds := range groupFeeds {
|
|
||||||
result = append(result, &FeverFeedsGroup{
|
|
||||||
GroupID: groupId,
|
|
||||||
FeedIDs: joinInts(feedIds),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverGroupsHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
folders := db(req).ListFolders()
|
|
||||||
groups := make([]*FeverGroup, len(folders))
|
|
||||||
for i, folder := range folders {
|
|
||||||
groups[i] = &FeverGroup{ID: folder.Id, Title: folder.Title}
|
|
||||||
}
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
"groups": groups,
|
|
||||||
"feeds_groups": feedGroups(db(req)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverFeedsHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
feeds := db(req).ListFeeds()
|
|
||||||
httpStates := db(req).ListHTTPStates()
|
|
||||||
|
|
||||||
feverFeeds := make([]*FeverFeed, len(feeds))
|
|
||||||
for i, feed := range feeds {
|
|
||||||
var lastUpdated int64
|
|
||||||
if state, ok := httpStates[feed.Id]; ok {
|
|
||||||
lastUpdated = state.LastRefreshed.Unix()
|
|
||||||
}
|
|
||||||
feverFeeds[i] = &FeverFeed{
|
|
||||||
ID: feed.Id,
|
|
||||||
FaviconID: feed.Id,
|
|
||||||
Title: feed.Title,
|
|
||||||
Url: feed.FeedLink,
|
|
||||||
SiteUrl: feed.Link,
|
|
||||||
IsSpark: 0,
|
|
||||||
LastUpdated: lastUpdated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
"feeds": feverFeeds,
|
|
||||||
"feeds_groups": feedGroups(db(req)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverFilteredItemIDsHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
var status storage.ItemStatus
|
|
||||||
var filter string
|
|
||||||
if _, ok := req.URL.Query()["unread_item_ids"]; ok {
|
|
||||||
status = storage.UNREAD
|
|
||||||
filter = "unread_item_ids"
|
|
||||||
} else {
|
|
||||||
status = storage.STARRED
|
|
||||||
filter = "saved_item_ids"
|
|
||||||
}
|
|
||||||
|
|
||||||
itemIds := make([]int64, 0, 4000)
|
|
||||||
batch := 1000
|
|
||||||
index := 0
|
|
||||||
for {
|
|
||||||
items := db(req).ListItems(storage.ItemFilter{Status: &status}, index*batch, batch, true)
|
|
||||||
if len(items) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, item := range items {
|
|
||||||
itemIds = append(itemIds, item.Id)
|
|
||||||
}
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
filter: joinInts(itemIds),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverFaviconsHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
feeds := db(req).ListFeeds()
|
|
||||||
favicons := make([]*FeverFavicon, len(feeds))
|
|
||||||
for i, feed := range feeds {
|
|
||||||
data := "data:image/gif;base64,R0lGODlhAQABAAAAACw="
|
|
||||||
if feed.HasIcon {
|
|
||||||
icon := db(req).GetFeed(feed.Id).Icon
|
|
||||||
data = fmt.Sprintf(
|
|
||||||
"data:%s;base64,%s",
|
|
||||||
http.DetectContentType(*icon),
|
|
||||||
base64.StdEncoding.EncodeToString(*icon),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
favicons[i] = &FeverFavicon{ID: feed.Id, Data: data}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
"favicons": favicons,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverItemsHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
filter := storage.ItemFilter{}
|
|
||||||
query := req.URL.Query()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case query.Get("with_ids") != "":
|
|
||||||
ids := make([]int64, 0)
|
|
||||||
for _, idstr := range strings.Split(query.Get("with_ids"), ",") {
|
|
||||||
if idnum, err := strconv.ParseInt(idstr, 10, 64); err == nil {
|
|
||||||
ids = append(ids, idnum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter.IDs = &ids
|
|
||||||
case query.Get("since_id") != "":
|
|
||||||
idstr := query.Get("since_id")
|
|
||||||
if idnum, err := strconv.ParseInt(idstr, 10, 64); err == nil {
|
|
||||||
filter.SinceID = &idnum
|
|
||||||
}
|
|
||||||
case query.Get("max_id") != "":
|
|
||||||
idstr := query.Get("max_id")
|
|
||||||
if idnum, err := strconv.ParseInt(idstr, 10, 64); err == nil {
|
|
||||||
filter.MaxID = &idnum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items := db(req).ListItems(filter, 0, 50, true)
|
|
||||||
|
|
||||||
feverItems := make([]FeverItem, len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
date := item.Date
|
|
||||||
if date == nil {
|
|
||||||
date = item.DateUpdated
|
|
||||||
}
|
|
||||||
time := int64(0)
|
|
||||||
if date != nil {
|
|
||||||
time = date.Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
isSaved := 0
|
|
||||||
if item.Status == storage.STARRED {
|
|
||||||
isSaved = 1
|
|
||||||
}
|
|
||||||
isRead := 0
|
|
||||||
if item.Status == storage.READ {
|
|
||||||
isRead = 1
|
|
||||||
}
|
|
||||||
feverItems[i] = FeverItem{
|
|
||||||
ID: item.Id,
|
|
||||||
FeedID: item.FeedId,
|
|
||||||
Title: item.Title,
|
|
||||||
Author: item.Author,
|
|
||||||
HTML: item.Content,
|
|
||||||
Url: item.Link,
|
|
||||||
IsSaved: isSaved,
|
|
||||||
IsRead: isRead,
|
|
||||||
CreatedAt: time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
"items": feverItems,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverLinksHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
writeFeverJSON(rw, map[string]interface{}{
|
|
||||||
"links": make([]interface{}, 0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeverMarkHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
query := req.URL.Query()
|
|
||||||
|
|
||||||
id, err := strconv.ParseInt(query.Get("id"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
handler(req).log.Print("invalid id:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch query.Get("mark") {
|
|
||||||
case "item":
|
|
||||||
var status storage.ItemStatus
|
|
||||||
switch query.Get("as") {
|
|
||||||
case "read":
|
|
||||||
status = storage.READ
|
|
||||||
case "unread":
|
|
||||||
status = storage.UNREAD
|
|
||||||
case "saved":
|
|
||||||
status = storage.STARRED
|
|
||||||
case "unsaved":
|
|
||||||
status = storage.READ
|
|
||||||
default:
|
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
db(req).UpdateItemStatus(id, status)
|
|
||||||
case "feed":
|
|
||||||
x, _ := strconv.ParseInt(query.Get("before"), 10, 64)
|
|
||||||
before := time.Unix(x, 0)
|
|
||||||
db(req).MarkItemsRead(storage.MarkFilter{FeedID: &id, Before: &before})
|
|
||||||
case "group":
|
|
||||||
x, _ := strconv.ParseInt(query.Get("before"), 10, 64)
|
|
||||||
before := time.Unix(x, 0)
|
|
||||||
db(req).MarkItemsRead(storage.MarkFilter{FolderID: &id, Before: &before})
|
|
||||||
default:
|
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,6 @@ import (
|
|||||||
var routes []Route = []Route{
|
var routes []Route = []Route{
|
||||||
p("/", IndexHandler).ManualAuth(),
|
p("/", IndexHandler).ManualAuth(),
|
||||||
p("/static/*path", StaticHandler).ManualAuth(),
|
p("/static/*path", StaticHandler).ManualAuth(),
|
||||||
p("/fever", FeverHandler).ManualAuth(),
|
|
||||||
|
|
||||||
p("/api/status", StatusHandler),
|
p("/api/status", StatusHandler),
|
||||||
p("/api/folders", FolderListHandler),
|
p("/api/folders", FolderListHandler),
|
||||||
|
@ -62,17 +62,11 @@ type ItemFilter struct {
|
|||||||
FeedID *int64
|
FeedID *int64
|
||||||
Status *ItemStatus
|
Status *ItemStatus
|
||||||
Search *string
|
Search *string
|
||||||
|
|
||||||
IDs *[]int64
|
|
||||||
SinceID *int64
|
|
||||||
MaxID *int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarkFilter struct {
|
type MarkFilter struct {
|
||||||
FolderID *int64
|
FolderID *int64
|
||||||
FeedID *int64
|
FeedID *int64
|
||||||
|
|
||||||
Before *time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) CreateItems(items []Item) bool {
|
func (s *Storage) CreateItems(items []Item) bool {
|
||||||
@ -151,25 +145,6 @@ func listQueryPredicate(filter ItemFilter) (string, []interface{}) {
|
|||||||
args = append(args, strings.Join(terms, " "))
|
args = append(args, strings.Join(terms, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter.IDs != nil && len(*filter.IDs) > 0 {
|
|
||||||
qmarks := make([]string, len(*filter.IDs))
|
|
||||||
idargs := make([]interface{}, len(*filter.IDs))
|
|
||||||
for i, id := range *filter.IDs {
|
|
||||||
qmarks[i] = "?"
|
|
||||||
idargs[i] = id
|
|
||||||
}
|
|
||||||
cond = append(cond, "i.id in (" + strings.Join(qmarks, ",") + ")")
|
|
||||||
args = append(args, idargs...)
|
|
||||||
}
|
|
||||||
if filter.SinceID != nil {
|
|
||||||
cond = append(cond, "i.id > ?")
|
|
||||||
args = append(args, filter.SinceID)
|
|
||||||
}
|
|
||||||
if filter.MaxID != nil {
|
|
||||||
cond = append(cond, "i.id < ?")
|
|
||||||
args = append(args, filter.MaxID)
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate := "1"
|
predicate := "1"
|
||||||
if len(cond) > 0 {
|
if len(cond) > 0 {
|
||||||
predicate = strings.Join(cond, " and ")
|
predicate = strings.Join(cond, " and ")
|
||||||
@ -187,10 +162,6 @@ func (s *Storage) ListItems(filter ItemFilter, offset, limit int, newestFirst bo
|
|||||||
order = "date asc"
|
order = "date asc"
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter.IDs != nil || filter.SinceID != nil || filter.MaxID != nil {
|
|
||||||
order = "i.id asc"
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
select
|
select
|
||||||
i.id, i.guid, i.feed_id, i.title, i.link, i.description,
|
i.id, i.guid, i.feed_id, i.title, i.link, i.description,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user