Add non-root url path support

This commit is contained in:
hcl 2021-01-25 17:52:15 +08:00 committed by nkanaev
parent 63b265fa04
commit 4f79c919f0
6 changed files with 97 additions and 54 deletions

View File

@ -90,12 +90,12 @@
Import
</label>
</b-dropdown-form>
<b-dropdown-item href="/opml/export">
<b-dropdown-item href="./opml/export">
<span class="icon mr-1">{% inline "upload.svg" %}</span>
Export
</b-dropdown-item>
<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>
Log out
</b-dropdown-item-button>
@ -137,7 +137,7 @@
<input type="radio" name="feed" :value="'feed:'+feed.id" v-model="feedSelected">
<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-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="counter text-right">{{ filteredFeedStats[feed.id] || '' }}</span>
</div>
@ -334,7 +334,7 @@
<div v-for="feed in folder.feeds" class="list-row d-flex align-items-center" :key="feed.id">
<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-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 }}
</div>
<span class="icon flex-shrink-0 mx-2"

View File

@ -26,74 +26,74 @@
window.api = {
feeds: {
list: function() {
return api('get', '/api/feeds').then(json)
return api('get', './api/feeds').then(json)
},
create: function(data) {
return api('post', '/api/feeds', data).then(json)
return api('post', './api/feeds', data).then(json)
},
update: function(id, data) {
return api('put', '/api/feeds/' + id, data)
return api('put', './api/feeds/' + id, data)
},
delete: function(id) {
return api('delete', '/api/feeds/' + id)
return api('delete', './api/feeds/' + 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() {
return api('post', '/api/feeds/refresh')
return api('post', './api/feeds/refresh')
},
list_errors: function() {
return api('get', '/api/feeds/errors').then(json)
return api('get', './api/feeds/errors').then(json)
},
},
folders: {
list: function() {
return api('get', '/api/folders').then(json)
return api('get', './api/folders').then(json)
},
create: function(data) {
return api('post', '/api/folders', data).then(json)
return api('post', './api/folders', data).then(json)
},
update: function(id, data) {
return api('put', '/api/folders/' + id, data)
return api('put', './api/folders/' + id, data)
},
delete: function(id) {
return api('delete', '/api/folders/' + id)
return api('delete', './api/folders/' + id)
},
list_items: function(id) {
return api('get', '/api/folders/' + id + '/items').then(json)
return api('get', './api/folders/' + id + '/items').then(json)
}
},
items: {
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) {
return api('put', '/api/items/' + id, data)
return api('put', './api/items/' + id, data)
},
mark_read: function(query) {
return api('put', '/api/items' + param(query))
return api('put', './api/items' + param(query))
},
},
settings: {
get: function() {
return api('get', '/api/settings').then(json)
return api('get', './api/settings').then(json)
},
update: function(data) {
return api('put', '/api/settings', data)
return api('put', './api/settings', data)
},
},
status: function() {
return api('get', '/api/status').then(json)
return api('get', './api/status').then(json)
},
upload_opml: function(form) {
return fetch('/opml/import', {
return fetch('./opml/import', {
method: 'post',
body: new FormData(form),
})
},
crawl: function(url) {
return fetch('/page?url=' + url).then(function(res) {
return fetch('./page?url=' + url).then(function(res) {
return res.text()
})
}

20
main.go
View File

@ -4,14 +4,15 @@ import (
"bufio"
"flag"
"fmt"
"github.com/nkanaev/yarr/platform"
"github.com/nkanaev/yarr/server"
"github.com/nkanaev/yarr/storage"
sdopen "github.com/skratchdot/open-golang/open"
"log"
"os"
"path/filepath"
"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"
@ -22,6 +23,7 @@ func main() {
var ver, open bool
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(&server.BasePath, "base", "", "base path of the service url")
flag.StringVar(&certfile, "cert-file", "", "path to cert file for https")
flag.StringVar(&keyfile, "key-file", "", "path to key file for https")
flag.StringVar(&db, "db", "", "storage file path")
@ -34,6 +36,16 @@ func main() {
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)
configPath, err := os.UserConfigDir()

View File

@ -11,7 +11,7 @@ import (
)
func userIsAuthenticated(req *http.Request, username, password string) bool {
cookie, _ := req.Cookie("auth")
cookie, _ := req.Cookie("auth")
if cookie == nil {
return false
}
@ -23,11 +23,18 @@ func userIsAuthenticated(req *http.Request, username, password string) bool {
}
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{
Name: "auth",
Value: username + ":" + secret(username, password),
Name: "auth",
Value: username + ":" + secret(username, password),
Expires: expires,
Path: cookiePath,
}
http.SetCookie(rw, &cookie)
}

View File

@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/nkanaev/yarr/storage"
"html"
"html/template"
"io"
@ -19,27 +18,41 @@ import (
"reflect"
"strconv"
"strings"
"github.com/nkanaev/yarr/storage"
)
var routes []Route = []Route{
p("/", IndexHandler).ManualAuth(),
p("/static/*path", StaticHandler).ManualAuth(),
var routes []Route
p("/api/status", StatusHandler),
p("/api/folders", FolderListHandler),
p("/api/folders/:id", FolderHandler),
p("/api/feeds", FeedListHandler),
p("/api/feeds/find", FeedHandler),
p("/api/feeds/refresh", FeedRefreshHandler),
p("/api/feeds/errors", FeedErrorsHandler),
p("/api/feeds/:id/icon", FeedIconHandler),
p("/api/feeds/:id", FeedHandler),
p("/api/items", ItemListHandler),
p("/api/items/:id", ItemHandler),
p("/api/settings", SettingsHandler),
p("/opml/import", OPMLImportHandler),
p("/opml/export", OPMLExportHandler),
p("/page", PageCrawlHandler),
var BasePathReady = make(chan bool)
func init() {
go func() {
<-BasePathReady
routes = []Route{
p(BasePath+"/", IndexHandler).ManualAuth(),
p(BasePath+"/static/*path", StaticHandler).ManualAuth(),
p(BasePath+"/api/status", StatusHandler),
p(BasePath+"/api/folders", FolderListHandler),
p(BasePath+"/api/folders/:id", FolderHandler),
p(BasePath+"/api/feeds", FeedListHandler),
p(BasePath+"/api/feeds/find", FeedHandler),
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 {
@ -140,6 +153,11 @@ func IndexHandler(rw http.ResponseWriter, req *http.Request) {
func StaticHandler(rw http.ResponseWriter, req *http.Request) {
path := Vars(req)["path"]
if BasePath != "" {
path = strings.TrimPrefix(path, BasePath)
}
ctype := mime.TypeByExtension(filepath.Ext(path))
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)
}

View File

@ -5,10 +5,12 @@ import (
"regexp"
)
var BasePath string = ""
type Route struct {
url string
urlRegex *regexp.Regexp
handler func(http.ResponseWriter, *http.Request)
url string
urlRegex *regexp.Regexp
handler func(http.ResponseWriter, *http.Request)
manualAuth bool
}