mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-24 00:33:14 +00:00
Add non-root url path support
This commit is contained in:
parent
63b265fa04
commit
4f79c919f0
@ -90,12 +90,12 @@
|
|||||||
Import
|
Import
|
||||||
</label>
|
</label>
|
||||||
</b-dropdown-form>
|
</b-dropdown-form>
|
||||||
<b-dropdown-item href="/opml/export">
|
<b-dropdown-item href="./opml/export">
|
||||||
<span class="icon mr-1">{% inline "upload.svg" %}</span>
|
<span class="icon mr-1">{% inline "upload.svg" %}</span>
|
||||||
Export
|
Export
|
||||||
</b-dropdown-item>
|
</b-dropdown-item>
|
||||||
<b-dropdown-divider v-if="authenticated"></b-dropdown-divider>
|
<b-dropdown-divider v-if="authenticated"></b-dropdown-divider>
|
||||||
<b-dropdown-item-button v-if="authenticated">
|
<b-dropdown-item-button v-if="authenticated" @click="logout()">
|
||||||
<span class="icon mr-1">{% inline "log-out.svg" %}</span>
|
<span class="icon mr-1">{% inline "log-out.svg" %}</span>
|
||||||
Log out
|
Log out
|
||||||
</b-dropdown-item-button>
|
</b-dropdown-item-button>
|
||||||
@ -137,7 +137,7 @@
|
|||||||
<input type="radio" name="feed" :value="'feed:'+feed.id" v-model="feedSelected">
|
<input type="radio" name="feed" :value="'feed:'+feed.id" v-model="feedSelected">
|
||||||
<div class="selectgroup-label d-flex align-items-center w-100">
|
<div class="selectgroup-label d-flex align-items-center w-100">
|
||||||
<span class="icon mr-2" v-if="!feed.has_icon">{% inline "rss.svg" %}</span>
|
<span class="icon mr-2" v-if="!feed.has_icon">{% inline "rss.svg" %}</span>
|
||||||
<span class="icon mr-2" v-else><img v-lazy="'/api/feeds/'+feed.id+'/icon'" alt=""></span>
|
<span class="icon mr-2" v-else><img v-lazy="'./api/feeds/'+feed.id+'/icon'" alt=""></span>
|
||||||
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
|
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
|
||||||
<span class="counter text-right">{{ filteredFeedStats[feed.id] || '' }}</span>
|
<span class="counter text-right">{{ filteredFeedStats[feed.id] || '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -334,7 +334,7 @@
|
|||||||
<div v-for="feed in folder.feeds" class="list-row d-flex align-items-center" :key="feed.id">
|
<div v-for="feed in folder.feeds" class="list-row d-flex align-items-center" :key="feed.id">
|
||||||
<div class="w-100 text-truncate">
|
<div class="w-100 text-truncate">
|
||||||
<span class="icon mr-2" v-if="!feed.has_icon">{% inline "rss.svg" %}</span>
|
<span class="icon mr-2" v-if="!feed.has_icon">{% inline "rss.svg" %}</span>
|
||||||
<span class="icon mr-2" v-else><img v-lazy="'/api/feeds/'+feed.id+'/icon'" alt=""></span>
|
<span class="icon mr-2" v-else><img v-lazy="'./api/feeds/'+feed.id+'/icon'" alt=""></span>
|
||||||
{{ feed.title }}
|
{{ feed.title }}
|
||||||
</div>
|
</div>
|
||||||
<span class="icon flex-shrink-0 mx-2"
|
<span class="icon flex-shrink-0 mx-2"
|
||||||
|
@ -26,74 +26,74 @@
|
|||||||
window.api = {
|
window.api = {
|
||||||
feeds: {
|
feeds: {
|
||||||
list: function() {
|
list: function() {
|
||||||
return api('get', '/api/feeds').then(json)
|
return api('get', './api/feeds').then(json)
|
||||||
},
|
},
|
||||||
create: function(data) {
|
create: function(data) {
|
||||||
return api('post', '/api/feeds', data).then(json)
|
return api('post', './api/feeds', data).then(json)
|
||||||
},
|
},
|
||||||
update: function(id, data) {
|
update: function(id, data) {
|
||||||
return api('put', '/api/feeds/' + id, data)
|
return api('put', './api/feeds/' + id, data)
|
||||||
},
|
},
|
||||||
delete: function(id) {
|
delete: function(id) {
|
||||||
return api('delete', '/api/feeds/' + id)
|
return api('delete', './api/feeds/' + id)
|
||||||
},
|
},
|
||||||
list_items: function(id) {
|
list_items: function(id) {
|
||||||
return api('get', '/api/feeds/' + id + '/items').then(json)
|
return api('get', './api/feeds/' + id + '/items').then(json)
|
||||||
},
|
},
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
return api('post', '/api/feeds/refresh')
|
return api('post', './api/feeds/refresh')
|
||||||
},
|
},
|
||||||
list_errors: function() {
|
list_errors: function() {
|
||||||
return api('get', '/api/feeds/errors').then(json)
|
return api('get', './api/feeds/errors').then(json)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
folders: {
|
folders: {
|
||||||
list: function() {
|
list: function() {
|
||||||
return api('get', '/api/folders').then(json)
|
return api('get', './api/folders').then(json)
|
||||||
},
|
},
|
||||||
create: function(data) {
|
create: function(data) {
|
||||||
return api('post', '/api/folders', data).then(json)
|
return api('post', './api/folders', data).then(json)
|
||||||
},
|
},
|
||||||
update: function(id, data) {
|
update: function(id, data) {
|
||||||
return api('put', '/api/folders/' + id, data)
|
return api('put', './api/folders/' + id, data)
|
||||||
},
|
},
|
||||||
delete: function(id) {
|
delete: function(id) {
|
||||||
return api('delete', '/api/folders/' + id)
|
return api('delete', './api/folders/' + id)
|
||||||
},
|
},
|
||||||
list_items: function(id) {
|
list_items: function(id) {
|
||||||
return api('get', '/api/folders/' + id + '/items').then(json)
|
return api('get', './api/folders/' + id + '/items').then(json)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
list: function(query) {
|
list: function(query) {
|
||||||
return api('get', '/api/items' + param(query)).then(json)
|
return api('get', './api/items' + param(query)).then(json)
|
||||||
},
|
},
|
||||||
update: function(id, data) {
|
update: function(id, data) {
|
||||||
return api('put', '/api/items/' + id, data)
|
return api('put', './api/items/' + id, data)
|
||||||
},
|
},
|
||||||
mark_read: function(query) {
|
mark_read: function(query) {
|
||||||
return api('put', '/api/items' + param(query))
|
return api('put', './api/items' + param(query))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
get: function() {
|
get: function() {
|
||||||
return api('get', '/api/settings').then(json)
|
return api('get', './api/settings').then(json)
|
||||||
},
|
},
|
||||||
update: function(data) {
|
update: function(data) {
|
||||||
return api('put', '/api/settings', data)
|
return api('put', './api/settings', data)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
status: function() {
|
status: function() {
|
||||||
return api('get', '/api/status').then(json)
|
return api('get', './api/status').then(json)
|
||||||
},
|
},
|
||||||
upload_opml: function(form) {
|
upload_opml: function(form) {
|
||||||
return fetch('/opml/import', {
|
return fetch('./opml/import', {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: new FormData(form),
|
body: new FormData(form),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
crawl: function(url) {
|
crawl: function(url) {
|
||||||
return fetch('/page?url=' + url).then(function(res) {
|
return fetch('./page?url=' + url).then(function(res) {
|
||||||
return res.text()
|
return res.text()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
20
main.go
20
main.go
@ -4,14 +4,15 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/nkanaev/yarr/platform"
|
|
||||||
"github.com/nkanaev/yarr/server"
|
|
||||||
"github.com/nkanaev/yarr/storage"
|
|
||||||
sdopen "github.com/skratchdot/open-golang/open"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nkanaev/yarr/platform"
|
||||||
|
"github.com/nkanaev/yarr/server"
|
||||||
|
"github.com/nkanaev/yarr/storage"
|
||||||
|
sdopen "github.com/skratchdot/open-golang/open"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string = "0.0"
|
var Version string = "0.0"
|
||||||
@ -22,6 +23,7 @@ func main() {
|
|||||||
var ver, open bool
|
var ver, open bool
|
||||||
flag.StringVar(&addr, "addr", "127.0.0.1:7070", "address to run server on")
|
flag.StringVar(&addr, "addr", "127.0.0.1:7070", "address to run server on")
|
||||||
flag.StringVar(&authfile, "auth-file", "", "path to a file containing username:password")
|
flag.StringVar(&authfile, "auth-file", "", "path to a file containing username:password")
|
||||||
|
flag.StringVar(&server.BasePath, "base", "", "base path of the service url")
|
||||||
flag.StringVar(&certfile, "cert-file", "", "path to cert file for https")
|
flag.StringVar(&certfile, "cert-file", "", "path to cert file for https")
|
||||||
flag.StringVar(&keyfile, "key-file", "", "path to key file for https")
|
flag.StringVar(&keyfile, "key-file", "", "path to key file for https")
|
||||||
flag.StringVar(&db, "db", "", "storage file path")
|
flag.StringVar(&db, "db", "", "storage file path")
|
||||||
@ -34,6 +36,16 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if server.BasePath != "" && !strings.HasPrefix(server.BasePath, "/") {
|
||||||
|
server.BasePath = "/" + server.BasePath
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.BasePath != "" && strings.HasSuffix(server.BasePath, "/") {
|
||||||
|
server.BasePath = strings.TrimSuffix(server.BasePath, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
server.BasePathReady <- true
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
|
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
configPath, err := os.UserConfigDir()
|
configPath, err := os.UserConfigDir()
|
||||||
|
@ -24,10 +24,17 @@ func userIsAuthenticated(req *http.Request, username, password string) bool {
|
|||||||
|
|
||||||
func userAuthenticate(rw http.ResponseWriter, username, password string) {
|
func userAuthenticate(rw http.ResponseWriter, username, password string) {
|
||||||
expires := time.Now().Add(time.Hour * 24 * 7) // 1 week
|
expires := time.Now().Add(time.Hour * 24 * 7) // 1 week
|
||||||
|
var cookiePath string
|
||||||
|
if BasePath != "" {
|
||||||
|
cookiePath = BasePath
|
||||||
|
} else {
|
||||||
|
cookiePath = "/"
|
||||||
|
}
|
||||||
cookie := http.Cookie{
|
cookie := http.Cookie{
|
||||||
Name: "auth",
|
Name: "auth",
|
||||||
Value: username + ":" + secret(username, password),
|
Value: username + ":" + secret(username, password),
|
||||||
Expires: expires,
|
Expires: expires,
|
||||||
|
Path: cookiePath,
|
||||||
}
|
}
|
||||||
http.SetCookie(rw, &cookie)
|
http.SetCookie(rw, &cookie)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/nkanaev/yarr/storage"
|
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
@ -19,27 +18,41 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nkanaev/yarr/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var routes []Route = []Route{
|
var routes []Route
|
||||||
p("/", IndexHandler).ManualAuth(),
|
|
||||||
p("/static/*path", StaticHandler).ManualAuth(),
|
|
||||||
|
|
||||||
p("/api/status", StatusHandler),
|
var BasePathReady = make(chan bool)
|
||||||
p("/api/folders", FolderListHandler),
|
|
||||||
p("/api/folders/:id", FolderHandler),
|
func init() {
|
||||||
p("/api/feeds", FeedListHandler),
|
go func() {
|
||||||
p("/api/feeds/find", FeedHandler),
|
<-BasePathReady
|
||||||
p("/api/feeds/refresh", FeedRefreshHandler),
|
routes = []Route{
|
||||||
p("/api/feeds/errors", FeedErrorsHandler),
|
p(BasePath+"/", IndexHandler).ManualAuth(),
|
||||||
p("/api/feeds/:id/icon", FeedIconHandler),
|
p(BasePath+"/static/*path", StaticHandler).ManualAuth(),
|
||||||
p("/api/feeds/:id", FeedHandler),
|
|
||||||
p("/api/items", ItemListHandler),
|
p(BasePath+"/api/status", StatusHandler),
|
||||||
p("/api/items/:id", ItemHandler),
|
p(BasePath+"/api/folders", FolderListHandler),
|
||||||
p("/api/settings", SettingsHandler),
|
p(BasePath+"/api/folders/:id", FolderHandler),
|
||||||
p("/opml/import", OPMLImportHandler),
|
p(BasePath+"/api/feeds", FeedListHandler),
|
||||||
p("/opml/export", OPMLExportHandler),
|
p(BasePath+"/api/feeds/find", FeedHandler),
|
||||||
p("/page", PageCrawlHandler),
|
p(BasePath+"/api/feeds/refresh", FeedRefreshHandler),
|
||||||
|
p(BasePath+"/api/feeds/errors", FeedErrorsHandler),
|
||||||
|
p(BasePath+"/api/feeds/:id/icon", FeedIconHandler),
|
||||||
|
p(BasePath+"/api/feeds/:id", FeedHandler),
|
||||||
|
p(BasePath+"/api/items", ItemListHandler),
|
||||||
|
p(BasePath+"/api/items/:id", ItemHandler),
|
||||||
|
p(BasePath+"/api/settings", SettingsHandler),
|
||||||
|
p(BasePath+"/opml/import", OPMLImportHandler),
|
||||||
|
p(BasePath+"/opml/export", OPMLExportHandler),
|
||||||
|
p(BasePath+"/page", PageCrawlHandler),
|
||||||
|
}
|
||||||
|
if BasePath != "" {
|
||||||
|
routes = append(routes, p(BasePath, RedirectBasePathHandler).ManualAuth())
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
type asset struct {
|
type asset struct {
|
||||||
@ -140,6 +153,11 @@ func IndexHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
func StaticHandler(rw http.ResponseWriter, req *http.Request) {
|
func StaticHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
path := Vars(req)["path"]
|
path := Vars(req)["path"]
|
||||||
|
|
||||||
|
if BasePath != "" {
|
||||||
|
path = strings.TrimPrefix(path, BasePath)
|
||||||
|
}
|
||||||
|
|
||||||
ctype := mime.TypeByExtension(filepath.Ext(path))
|
ctype := mime.TypeByExtension(filepath.Ext(path))
|
||||||
|
|
||||||
if assets != nil {
|
if assets != nil {
|
||||||
@ -528,3 +546,7 @@ func PageCrawlHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RedirectBasePathHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
http.Redirect(rw, req, req.URL.Path+"/", http.StatusFound)
|
||||||
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var BasePath string = ""
|
||||||
|
|
||||||
type Route struct {
|
type Route struct {
|
||||||
url string
|
url string
|
||||||
urlRegex *regexp.Regexp
|
urlRegex *regexp.Regexp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user