mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-25 05:29:20 +00:00
switch to the new router
This commit is contained in:
parent
e53265472f
commit
1a490a8e7a
21
src/server/forms.go
Normal file
21
src/server/forms.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import "github.com/nkanaev/yarr/src/storage"
|
||||||
|
|
||||||
|
type ItemUpdateForm struct {
|
||||||
|
Status *storage.ItemStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FolderCreateForm struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FolderUpdateForm struct {
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
IsExpanded *bool `json:"is_expanded,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeedCreateForm struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
FolderID *int64 `json:"folder_id,omitempty"`
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/nkanaev/yarr/src/storage"
|
"github.com/nkanaev/yarr/src/storage"
|
||||||
"github.com/nkanaev/yarr/src/assets"
|
"github.com/nkanaev/yarr/src/assets"
|
||||||
|
"github.com/nkanaev/yarr/src/router"
|
||||||
"html"
|
"html"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -15,272 +16,257 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var routes []Route = []Route{
|
func (s *Server) handler() http.Handler {
|
||||||
p("/", IndexHandler).ManualAuth(),
|
r := router.NewRouter()
|
||||||
p("/static/*path", StaticHandler).ManualAuth(),
|
|
||||||
|
|
||||||
p("/api/status", StatusHandler),
|
// TODO: auth, base, security
|
||||||
p("/api/folders", FolderListHandler),
|
|
||||||
p("/api/folders/:id", FolderHandler),
|
r.For("/", s.handleIndex)
|
||||||
p("/api/feeds", FeedListHandler),
|
r.For("/static/*path", s.handleStatic)
|
||||||
p("/api/feeds/find", FeedHandler),
|
r.For("/api/status", s.handleStatus)
|
||||||
p("/api/feeds/refresh", FeedRefreshHandler),
|
r.For("/api/folders", s.handleFolderList)
|
||||||
p("/api/feeds/errors", FeedErrorsHandler),
|
r.For("/api/folders/:id", s.handleFolder)
|
||||||
p("/api/feeds/:id/icon", FeedIconHandler),
|
r.For("/api/feeds", s.handleFeedList)
|
||||||
p("/api/feeds/:id", FeedHandler),
|
r.For("/api/feeds/refresh", s.handleFeedRefresh)
|
||||||
p("/api/items", ItemListHandler),
|
r.For("/api/feeds/errors", s.handleFeedErrors)
|
||||||
p("/api/items/:id", ItemHandler),
|
r.For("/api/feeds/:id/icon", s.handleFeedIcon)
|
||||||
p("/api/settings", SettingsHandler),
|
r.For("/api/feeds/:id", s.handleFeed)
|
||||||
p("/opml/import", OPMLImportHandler),
|
r.For("/api/items", s.handleItemList)
|
||||||
p("/opml/export", OPMLExportHandler),
|
r.For("/api/items/:id", s.handleItem)
|
||||||
p("/page", PageCrawlHandler),
|
r.For("/api/settings", s.handleSettings)
|
||||||
p("/logout", LogoutHandler),
|
r.For("/opml/import", s.handleOPMLImport)
|
||||||
|
r.For("/opml/export", s.handleOPMLExport)
|
||||||
|
r.For("/page", s.handlePageCrawl)
|
||||||
|
r.For("/logout", s.handleLogout)
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
type FolderCreateForm struct {
|
func (s *Server) handleIndex(c *router.Context) {
|
||||||
Title string `json:"title"`
|
if s.requiresAuth() && !userIsAuthenticated(c.Req, s.Username, s.Password) {
|
||||||
}
|
if c.Req.Method == "POST" {
|
||||||
|
username := c.Req.FormValue("username")
|
||||||
type FolderUpdateForm struct {
|
password := c.Req.FormValue("password")
|
||||||
Title *string `json:"title,omitempty"`
|
if stringsEqual(username, s.Username) && stringsEqual(password, s.Password) {
|
||||||
IsExpanded *bool `json:"is_expanded,omitempty"`
|
userAuthenticate(c.Out, username, password)
|
||||||
}
|
http.Redirect(c.Out, c.Req, c.Req.URL.Path, http.StatusFound)
|
||||||
|
|
||||||
type FeedCreateForm struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
FolderID *int64 `json:"folder_id,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemUpdateForm struct {
|
|
||||||
Status *storage.ItemStatus `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func IndexHandler(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
h := handler(req)
|
|
||||||
if h.requiresAuth() && !userIsAuthenticated(req, h.Username, h.Password) {
|
|
||||||
if req.Method == "POST" {
|
|
||||||
username := req.FormValue("username")
|
|
||||||
password := req.FormValue("password")
|
|
||||||
if stringsEqual(username, h.Username) && stringsEqual(password, h.Password) {
|
|
||||||
userAuthenticate(rw, username, password)
|
|
||||||
http.Redirect(rw, req, req.URL.Path, http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
c.Out.Header().Set("Content-Type", "text/html")
|
||||||
assets.Render("login.html", rw, nil)
|
assets.Render("login.html", c.Out, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
c.Out.Header().Set("Content-Type", "text/html")
|
||||||
assets.Render("index.html", rw, nil)
|
assets.Render("index.html", c.Out, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StaticHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleStatic(c *router.Context) {
|
||||||
// TODO: gzip?
|
// TODO: gzip?
|
||||||
http.StripPrefix(BasePath+"/static/", http.FileServer(http.FS(assets.FS))).ServeHTTP(rw, req)
|
http.StripPrefix(BasePath+"/static/", http.FileServer(http.FS(assets.FS))).ServeHTTP(c.Out, c.Req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatusHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleStatus(c *router.Context) {
|
||||||
writeJSON(rw, map[string]interface{}{
|
writeJSON(c.Out, map[string]interface{}{
|
||||||
"running": *handler(req).queueSize,
|
"running": *s.queueSize,
|
||||||
"stats": db(req).FeedStats(),
|
"stats": s.db.FeedStats(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func FolderListHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFolderList(c *router.Context) {
|
||||||
if req.Method == "GET" {
|
if c.Req.Method == "GET" {
|
||||||
list := db(req).ListFolders()
|
list := s.db.ListFolders()
|
||||||
writeJSON(rw, list)
|
writeJSON(c.Out, list)
|
||||||
} else if req.Method == "POST" {
|
} else if c.Req.Method == "POST" {
|
||||||
var body FolderCreateForm
|
var body FolderCreateForm
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&body); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(body.Title) == 0 {
|
if len(body.Title) == 0 {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
writeJSON(rw, map[string]string{"error": "Folder title missing."})
|
writeJSON(c.Out, map[string]string{"error": "Folder title missing."})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
folder := db(req).CreateFolder(body.Title)
|
folder := s.db.CreateFolder(body.Title)
|
||||||
rw.WriteHeader(http.StatusCreated)
|
c.Out.WriteHeader(http.StatusCreated)
|
||||||
writeJSON(rw, folder)
|
writeJSON(c.Out, folder)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FolderHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFolder(c *router.Context) {
|
||||||
id, err := strconv.ParseInt(Vars(req)["id"], 10, 64)
|
id, err := strconv.ParseInt(c.Vars["id"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Method == "PUT" {
|
if c.Req.Method == "PUT" {
|
||||||
var body FolderUpdateForm
|
var body FolderUpdateForm
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&body); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if body.Title != nil {
|
if body.Title != nil {
|
||||||
db(req).RenameFolder(id, *body.Title)
|
s.db.RenameFolder(id, *body.Title)
|
||||||
}
|
}
|
||||||
if body.IsExpanded != nil {
|
if body.IsExpanded != nil {
|
||||||
db(req).ToggleFolderExpanded(id, *body.IsExpanded)
|
s.db.ToggleFolderExpanded(id, *body.IsExpanded)
|
||||||
}
|
}
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else if req.Method == "DELETE" {
|
} else if c.Req.Method == "DELETE" {
|
||||||
db(req).DeleteFolder(id)
|
s.db.DeleteFolder(id)
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
c.Out.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeedRefreshHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFeedRefresh(c *router.Context) {
|
||||||
if req.Method == "POST" {
|
if c.Req.Method == "POST" {
|
||||||
handler(req).fetchAllFeeds()
|
s.fetchAllFeeds()
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeedErrorsHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFeedErrors(c *router.Context) {
|
||||||
errors := db(req).GetFeedErrors()
|
errors := s.db.GetFeedErrors()
|
||||||
writeJSON(rw, errors)
|
writeJSON(c.Out, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeedIconHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFeedIcon(c *router.Context) {
|
||||||
id, err := strconv.ParseInt(Vars(req)["id"], 10, 64)
|
id, err := strconv.ParseInt(c.Vars["id"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
feed := db(req).GetFeed(id)
|
feed := s.db.GetFeed(id)
|
||||||
if feed != nil && feed.Icon != nil {
|
if feed != nil && feed.Icon != nil {
|
||||||
rw.Header().Set("Content-Type", http.DetectContentType(*feed.Icon))
|
c.Out.Header().Set("Content-Type", http.DetectContentType(*feed.Icon))
|
||||||
rw.Header().Set("Content-Length", strconv.Itoa(len(*feed.Icon)))
|
c.Out.Header().Set("Content-Length", strconv.Itoa(len(*feed.Icon)))
|
||||||
rw.Write(*feed.Icon)
|
c.Out.Write(*feed.Icon)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
c.Out.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeedListHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFeedList(c *router.Context) {
|
||||||
if req.Method == "GET" {
|
if c.Req.Method == "GET" {
|
||||||
list := db(req).ListFeeds()
|
list := s.db.ListFeeds()
|
||||||
writeJSON(rw, list)
|
writeJSON(c.Out, list)
|
||||||
} else if req.Method == "POST" {
|
} else if c.Req.Method == "POST" {
|
||||||
var form FeedCreateForm
|
var form FeedCreateForm
|
||||||
if err := json.NewDecoder(req.Body).Decode(&form); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&form); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
feed, sources, err := discoverFeed(form.Url)
|
feed, sources, err := discoverFeed(form.Url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
writeJSON(rw, map[string]string{"status": "notfound"})
|
writeJSON(c.Out, map[string]string{"status": "notfound"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if feed != nil {
|
if feed != nil {
|
||||||
storedFeed := db(req).CreateFeed(
|
storedFeed := s.db.CreateFeed(
|
||||||
feed.Title,
|
feed.Title,
|
||||||
feed.Description,
|
feed.Description,
|
||||||
feed.Link,
|
feed.Link,
|
||||||
feed.FeedLink,
|
feed.FeedLink,
|
||||||
form.FolderID,
|
form.FolderID,
|
||||||
)
|
)
|
||||||
db(req).CreateItems(convertItems(feed.Items, *storedFeed))
|
s.db.CreateItems(convertItems(feed.Items, *storedFeed))
|
||||||
|
|
||||||
icon, err := findFavicon(storedFeed.Link, storedFeed.FeedLink)
|
icon, err := findFavicon(storedFeed.Link, storedFeed.FeedLink)
|
||||||
if icon != nil {
|
if icon != nil {
|
||||||
db(req).UpdateFeedIcon(storedFeed.Id, icon)
|
s.db.UpdateFeedIcon(storedFeed.Id, icon)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to find favicon for %s (%d): %s", storedFeed.FeedLink, storedFeed.Id, err)
|
log.Printf("Failed to find favicon for %s (%d): %s", storedFeed.FeedLink, storedFeed.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(rw, map[string]string{"status": "success"})
|
writeJSON(c.Out, map[string]string{"status": "success"})
|
||||||
} else if sources != nil {
|
} else if sources != nil {
|
||||||
writeJSON(rw, map[string]interface{}{"status": "multiple", "choice": sources})
|
writeJSON(c.Out, map[string]interface{}{"status": "multiple", "choice": sources})
|
||||||
} else {
|
} else {
|
||||||
writeJSON(rw, map[string]string{"status": "notfound"})
|
writeJSON(c.Out, map[string]string{"status": "notfound"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeedHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleFeed(c *router.Context) {
|
||||||
id, err := strconv.ParseInt(Vars(req)["id"], 10, 64)
|
id, err := strconv.ParseInt(c.Vars["id"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Method == "PUT" {
|
if c.Req.Method == "PUT" {
|
||||||
feed := db(req).GetFeed(id)
|
feed := s.db.GetFeed(id)
|
||||||
if feed == nil {
|
if feed == nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body := make(map[string]interface{})
|
body := make(map[string]interface{})
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&body); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if title, ok := body["title"]; ok {
|
if title, ok := body["title"]; ok {
|
||||||
if reflect.TypeOf(title).Kind() == reflect.String {
|
if reflect.TypeOf(title).Kind() == reflect.String {
|
||||||
db(req).RenameFeed(id, title.(string))
|
s.db.RenameFeed(id, title.(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f_id, ok := body["folder_id"]; ok {
|
if f_id, ok := body["folder_id"]; ok {
|
||||||
if f_id == nil {
|
if f_id == nil {
|
||||||
db(req).UpdateFeedFolder(id, nil)
|
s.db.UpdateFeedFolder(id, nil)
|
||||||
} else if reflect.TypeOf(f_id).Kind() == reflect.Float64 {
|
} else if reflect.TypeOf(f_id).Kind() == reflect.Float64 {
|
||||||
folderId := int64(f_id.(float64))
|
folderId := int64(f_id.(float64))
|
||||||
db(req).UpdateFeedFolder(id, &folderId)
|
s.db.UpdateFeedFolder(id, &folderId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else if req.Method == "DELETE" {
|
} else if c.Req.Method == "DELETE" {
|
||||||
db(req).DeleteFeed(id)
|
s.db.DeleteFeed(id)
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
c.Out.WriteHeader(http.StatusNoContent)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ItemHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleItem(c *router.Context) {
|
||||||
if req.Method == "PUT" {
|
if c.Req.Method == "PUT" {
|
||||||
id, err := strconv.ParseInt(Vars(req)["id"], 10, 64)
|
id, err := strconv.ParseInt(c.Vars["id"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var body ItemUpdateForm
|
var body ItemUpdateForm
|
||||||
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&body); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if body.Status != nil {
|
if body.Status != nil {
|
||||||
db(req).UpdateItemStatus(id, *body.Status)
|
s.db.UpdateItemStatus(id, *body.Status)
|
||||||
}
|
}
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ItemListHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleItemList(c *router.Context) {
|
||||||
if req.Method == "GET" {
|
if c.Req.Method == "GET" {
|
||||||
perPage := 20
|
perPage := 20
|
||||||
curPage := 1
|
curPage := 1
|
||||||
query := req.URL.Query()
|
query := c.Req.URL.Query()
|
||||||
if page, err := strconv.ParseInt(query.Get("page"), 10, 64); err == nil {
|
if page, err := strconv.ParseInt(query.Get("page"), 10, 64); err == nil {
|
||||||
curPage = int(page)
|
curPage = int(page)
|
||||||
}
|
}
|
||||||
@ -299,17 +285,17 @@ func ItemListHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
filter.Search = &search
|
filter.Search = &search
|
||||||
}
|
}
|
||||||
newestFirst := query.Get("oldest_first") != "true"
|
newestFirst := query.Get("oldest_first") != "true"
|
||||||
items := db(req).ListItems(filter, (curPage-1)*perPage, perPage, newestFirst)
|
items := s.db.ListItems(filter, (curPage-1)*perPage, perPage, newestFirst)
|
||||||
count := db(req).CountItems(filter)
|
count := s.db.CountItems(filter)
|
||||||
writeJSON(rw, map[string]interface{}{
|
writeJSON(c.Out, map[string]interface{}{
|
||||||
"page": map[string]int{
|
"page": map[string]int{
|
||||||
"cur": curPage,
|
"cur": curPage,
|
||||||
"num": int(math.Ceil(float64(count) / float64(perPage))),
|
"num": int(math.Ceil(float64(count) / float64(perPage))),
|
||||||
},
|
},
|
||||||
"list": items,
|
"list": items,
|
||||||
})
|
})
|
||||||
} else if req.Method == "PUT" {
|
} else if c.Req.Method == "PUT" {
|
||||||
query := req.URL.Query()
|
query := c.Req.URL.Query()
|
||||||
filter := storage.MarkFilter{}
|
filter := storage.MarkFilter{}
|
||||||
if folderID, err := strconv.ParseInt(query.Get("folder_id"), 10, 64); err == nil {
|
if folderID, err := strconv.ParseInt(query.Get("folder_id"), 10, 64); err == nil {
|
||||||
filter.FolderID = &folderID
|
filter.FolderID = &folderID
|
||||||
@ -317,36 +303,36 @@ func ItemListHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
if feedID, err := strconv.ParseInt(query.Get("feed_id"), 10, 64); err == nil {
|
if feedID, err := strconv.ParseInt(query.Get("feed_id"), 10, 64); err == nil {
|
||||||
filter.FeedID = &feedID
|
filter.FeedID = &feedID
|
||||||
}
|
}
|
||||||
db(req).MarkItemsRead(filter)
|
s.db.MarkItemsRead(filter)
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SettingsHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleSettings(c *router.Context) {
|
||||||
if req.Method == "GET" {
|
if c.Req.Method == "GET" {
|
||||||
writeJSON(rw, db(req).GetSettings())
|
writeJSON(c.Out, s.db.GetSettings())
|
||||||
} else if req.Method == "PUT" {
|
} else if c.Req.Method == "PUT" {
|
||||||
settings := make(map[string]interface{})
|
settings := make(map[string]interface{})
|
||||||
if err := json.NewDecoder(req.Body).Decode(&settings); err != nil {
|
if err := json.NewDecoder(c.Req.Body).Decode(&settings); err != nil {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if db(req).UpdateSettings(settings) {
|
if s.db.UpdateSettings(settings) {
|
||||||
if _, ok := settings["refresh_rate"]; ok {
|
if _, ok := settings["refresh_rate"]; ok {
|
||||||
handler(req).refreshRate <- db(req).GetSettingsValueInt64("refresh_rate")
|
s.refreshRate <- s.db.GetSettingsValueInt64("refresh_rate")
|
||||||
}
|
}
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusBadRequest)
|
c.Out.WriteHeader(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OPMLImportHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleOPMLImport(c *router.Context) {
|
||||||
if req.Method == "POST" {
|
if c.Req.Method == "POST" {
|
||||||
file, _, err := req.FormFile("opml")
|
file, _, err := c.Req.FormFile("opml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return
|
return
|
||||||
@ -358,25 +344,25 @@ func OPMLImportHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
for _, outline := range doc.Outlines {
|
for _, outline := range doc.Outlines {
|
||||||
if outline.Type == "rss" {
|
if outline.Type == "rss" {
|
||||||
db(req).CreateFeed(outline.Title, outline.Description, outline.SiteURL, outline.FeedURL, nil)
|
s.db.CreateFeed(outline.Title, outline.Description, outline.SiteURL, outline.FeedURL, nil)
|
||||||
} else {
|
} else {
|
||||||
folder := db(req).CreateFolder(outline.Title)
|
folder := s.db.CreateFolder(outline.Title)
|
||||||
for _, o := range outline.AllFeeds() {
|
for _, o := range outline.AllFeeds() {
|
||||||
db(req).CreateFeed(o.Title, o.Description, o.SiteURL, o.FeedURL, &folder.Id)
|
s.db.CreateFeed(o.Title, o.Description, o.SiteURL, o.FeedURL, &folder.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler(req).fetchAllFeeds()
|
s.fetchAllFeeds()
|
||||||
rw.WriteHeader(http.StatusOK)
|
c.Out.WriteHeader(http.StatusOK)
|
||||||
} else {
|
} else {
|
||||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
c.Out.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OPMLExportHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleOPMLExport(c *router.Context) {
|
||||||
if req.Method == "GET" {
|
if c.Req.Method == "GET" {
|
||||||
rw.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
c.Out.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
||||||
rw.Header().Set("Content-Disposition", `attachment; filename="subscriptions.opml"`)
|
c.Out.Header().Set("Content-Disposition", `attachment; filename="subscriptions.opml"`)
|
||||||
|
|
||||||
builder := strings.Builder{}
|
builder := strings.Builder{}
|
||||||
|
|
||||||
@ -407,7 +393,7 @@ func OPMLExportHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
line(`</head>`)
|
line(`</head>`)
|
||||||
line(`<body>`)
|
line(`<body>`)
|
||||||
feedsByFolderID := make(map[int64][]storage.Feed)
|
feedsByFolderID := make(map[int64][]storage.Feed)
|
||||||
for _, feed := range db(req).ListFeeds() {
|
for _, feed := range s.db.ListFeeds() {
|
||||||
var folderId = int64(0)
|
var folderId = int64(0)
|
||||||
if feed.FolderId != nil {
|
if feed.FolderId != nil {
|
||||||
folderId = *feed.FolderId
|
folderId = *feed.FolderId
|
||||||
@ -417,7 +403,7 @@ func OPMLExportHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
feedsByFolderID[folderId] = append(feedsByFolderID[folderId], feed)
|
feedsByFolderID[folderId] = append(feedsByFolderID[folderId], feed)
|
||||||
}
|
}
|
||||||
for _, folder := range db(req).ListFolders() {
|
for _, folder := range s.db.ListFolders() {
|
||||||
line(` <outline text="%s">`, folder.Title)
|
line(` <outline text="%s">`, folder.Title)
|
||||||
for _, feed := range feedsByFolderID[folder.Id] {
|
for _, feed := range feedsByFolderID[folder.Id] {
|
||||||
feedline(feed, 4)
|
feedline(feed, 4)
|
||||||
@ -429,24 +415,24 @@ func OPMLExportHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
line(`</body>`)
|
line(`</body>`)
|
||||||
line(`</opml>`)
|
line(`</opml>`)
|
||||||
rw.Write([]byte(builder.String()))
|
c.Out.Write([]byte(builder.String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PageCrawlHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handlePageCrawl(c *router.Context) {
|
||||||
query := req.URL.Query()
|
query := c.Req.URL.Query()
|
||||||
if url := query.Get("url"); len(url) > 0 {
|
if url := query.Get("url"); len(url) > 0 {
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rw.Write(body)
|
c.Out.Write(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogoutHandler(rw http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleLogout(c *router.Context) {
|
||||||
userLogout(rw)
|
userLogout(c.Out)
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
c.Out.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var BasePath string = ""
|
|
||||||
|
|
||||||
type Route struct {
|
|
||||||
url string
|
|
||||||
urlRegex *regexp.Regexp
|
|
||||||
handler func(http.ResponseWriter, *http.Request)
|
|
||||||
manualAuth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Route) ManualAuth() Route {
|
|
||||||
r.manualAuth = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func p(path string, handler http.HandlerFunc) Route {
|
|
||||||
var urlRegexp string
|
|
||||||
urlRegexp = regexp.MustCompile(`[\*\:]\w+`).ReplaceAllStringFunc(path, func(m string) string {
|
|
||||||
if m[0:1] == `*` {
|
|
||||||
return "(?P<" + m[1:] + ">.+)"
|
|
||||||
}
|
|
||||||
return "(?P<" + m[1:] + ">[^/]+)"
|
|
||||||
})
|
|
||||||
urlRegexp = "^" + urlRegexp + "$"
|
|
||||||
return Route{
|
|
||||||
url: path,
|
|
||||||
urlRegex: regexp.MustCompile(urlRegexp),
|
|
||||||
handler: handler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRoute(reqPath string) (*Route, map[string]string) {
|
|
||||||
vars := make(map[string]string)
|
|
||||||
for _, route := range routes {
|
|
||||||
if route.urlRegex.MatchString(reqPath) {
|
|
||||||
matches := route.urlRegex.FindStringSubmatchIndex(reqPath)
|
|
||||||
for i, key := range route.urlRegex.SubexpNames()[1:] {
|
|
||||||
vars[key] = reqPath[matches[i*2+2]:matches[i*2+3]]
|
|
||||||
}
|
|
||||||
return &route, vars
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
@ -1,17 +1,17 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nkanaev/yarr/src/storage"
|
"github.com/nkanaev/yarr/src/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var BasePath string = ""
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Addr string
|
Addr string
|
||||||
db *storage.Storage
|
db *storage.Storage
|
||||||
@ -45,16 +45,16 @@ func (h *Server) GetAddr() string {
|
|||||||
return proto + "://" + h.Addr + BasePath
|
return proto + "://" + h.Addr + BasePath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Server) Start() {
|
func (s *Server) Start() {
|
||||||
h.startJobs()
|
s.startJobs()
|
||||||
|
|
||||||
s := &http.Server{Addr: h.Addr, Handler: h}
|
httpserver := &http.Server{Addr: s.Addr, Handler: s.handler()}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if h.CertFile != "" && h.KeyFile != "" {
|
if s.CertFile != "" && s.KeyFile != "" {
|
||||||
err = s.ListenAndServeTLS(h.CertFile, h.KeyFile)
|
err = httpserver.ListenAndServeTLS(s.CertFile, s.KeyFile)
|
||||||
} else {
|
} else {
|
||||||
err = s.ListenAndServe()
|
err = httpserver.ListenAndServe()
|
||||||
}
|
}
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -65,6 +65,7 @@ func unsafeMethod(method string) bool {
|
|||||||
return method == "POST" || method == "PUT" || method == "DELETE"
|
return method == "POST" || method == "PUT" || method == "DELETE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (h Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (h Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
reqPath := req.URL.Path
|
reqPath := req.URL.Path
|
||||||
if BasePath != "" {
|
if BasePath != "" {
|
||||||
@ -99,6 +100,7 @@ func (h Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
ctx = context.WithValue(ctx, ctxVars, vars)
|
ctx = context.WithValue(ctx, ctxVars, vars)
|
||||||
route.handler(rw, req.WithContext(ctx))
|
route.handler(rw, req.WithContext(ctx))
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (h *Server) startJobs() {
|
func (h *Server) startJobs() {
|
||||||
delTicker := time.NewTicker(time.Hour * 24)
|
delTicker := time.NewTicker(time.Hour * 24)
|
||||||
@ -200,26 +202,3 @@ func (h *Server) fetchFeed(feed storage.Feed) {
|
|||||||
atomic.AddInt32(h.queueSize, 1)
|
atomic.AddInt32(h.queueSize, 1)
|
||||||
h.feedQueue <- feed
|
h.feedQueue <- feed
|
||||||
}
|
}
|
||||||
|
|
||||||
func Vars(req *http.Request) map[string]string {
|
|
||||||
if rv := req.Context().Value(ctxVars); rv != nil {
|
|
||||||
return rv.(map[string]string)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func db(req *http.Request) *storage.Storage {
|
|
||||||
if h := handler(req); h != nil {
|
|
||||||
return h.db
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handler(req *http.Request) *Server {
|
|
||||||
return req.Context().Value(ctxHandler).(*Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ctxVars = 2
|
|
||||||
ctxHandler = 3
|
|
||||||
)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user