route with query params instead of named routers

Since we don't use the web/router through and through to setup handler
functions, accessing the named route varaibles doesn't work inside those
handler functions.

Since I dont find it acceptable to juggle two concepts here I switch the
url generation to classical get query paramters (route?varA=xyz&varB=2).
This commit is contained in:
Henry 2021-02-15 12:08:33 +01:00
parent d802294418
commit 58f795d5e6
21 changed files with 176 additions and 59 deletions

View File

@ -30,7 +30,7 @@ func (s *Server) initNetwork() error {
return s.master.MakeHandler(conn)
}
if s.authorizer.Has(s.rootCtx, *remote) {
if s.authorizer.HasFeed(s.rootCtx, *remote) {
return s.public.MakeHandler(conn)
}
@ -81,7 +81,7 @@ func (srv *Server) Allow(r refs.FeedRef, yes bool) {
if yes {
srv.authorizer.Add(srv.rootCtx, r)
} else {
srv.authorizer.Remove(srv.rootCtx, r)
srv.authorizer.RemoveFeed(srv.rootCtx, r)
}
}

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb/mockdb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.cryptoscope.co/muxrpc/v2"
@ -78,7 +80,8 @@ func TestJSClient(t *testing.T) {
ts := newRandomSession(t)
// ts := newSession(t, nil)
srv := ts.startGoServer()
var al = &mockdb.FakeAllowListService{}
srv := ts.startGoServer(al)
alice := ts.startJSClient("alice", "./testscripts/simple_client.js",
srv.Network.GetListenAddr(),
@ -107,6 +110,7 @@ func TestJSClient(t *testing.T) {
)
srv.Allow(bob, true)
al.HasFeedReturns(true)
time.Sleep(5 * time.Second)
@ -133,8 +137,10 @@ func TestJSServer(t *testing.T) {
}
// now connect our go client
client := ts.startGoServer()
var al = &mockdb.FakeAllowListService{}
client := ts.startGoServer(al)
client.Allow(*alice, true)
al.HasFeedReturns(true)
var roomHandle bytes.Buffer
roomHandle.WriteString("tunnel:")

View File

@ -17,6 +17,8 @@ import (
"path/filepath"
"testing"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
"golang.org/x/sync/errgroup"
"github.com/go-kit/kit/log"
@ -84,7 +86,7 @@ func newSession(t *testing.T, appKey []byte) *testSession {
return ts
}
func (ts *testSession) startGoServer(opts ...roomsrv.Option) *roomsrv.Server {
func (ts *testSession) startGoServer(al admindb.AllowListService, opts ...roomsrv.Option) *roomsrv.Server {
r := require.New(ts.t)
// prepend defaults
@ -105,7 +107,7 @@ func (ts *testSession) startGoServer(opts ...roomsrv.Option) *roomsrv.Server {
}),
)
srv, err := roomsrv.New(opts...)
srv, err := roomsrv.New(al, opts...)
r.NoError(err, "failed to init tees a server")
ts.t.Logf("go server: %s", srv.Whoami().Ref())
ts.t.Cleanup(func() {

File diff suppressed because one or more lines are too long

View File

@ -1,24 +1,25 @@
// SPDX-License-Identifier: MIT
// Package errors defines some well defined errors, like incomplete/wrong request data or object not found(404), for the purpose of internationalization.
package errors
import (
"fmt"
)
type NotFound struct {
type ErrNotFound struct {
What string
}
func (nf NotFound) Error() string {
func (nf ErrNotFound) Error() string {
return fmt.Sprintf("rooms/web: item not found: %s", nf.What)
}
type BadRequest struct {
type ErrBadRequest struct {
Where string
Details error
}
func (br BadRequest) Error() string {
func (br ErrBadRequest) Error() string {
return fmt.Sprintf("rooms/web: bad request error: %s", br.Details)
}

View File

@ -1,13 +1,16 @@
package admin
import (
"errors"
"fmt"
"net/http"
"strconv"
"go.mindeco.de/http/render"
refs "go.mindeco.de/ssb-refs"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
)
type allowListH struct {
@ -54,7 +57,56 @@ func (h allowListH) overview(rw http.ResponseWriter, req *http.Request) (interfa
}
return struct {
Entries []refs.FeedRef
Entries admindb.ListEntries
Count int
}{lst, len(lst)}, nil
}
// TODO: move to render package so that we can decide to not render a page during the controller
var ErrRedirected = errors.New("render: not rendered but redirected")
func (h allowListH) removeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
if err != nil {
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
return nil, err
}
if !h.al.HasID(req.Context(), id) {
http.Redirect(rw, req, "/admin/allow-list", http.StatusFound)
return nil, ErrRedirected
}
entry, err := h.al.GetByID(req.Context(), id)
if err != nil {
return nil, err
}
return struct {
Entry admindb.ListEntry
}{entry}, nil
}
func (h allowListH) remove(rw http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
// TODO "flash" errors
http.Redirect(rw, req, "/admin/allow-list", http.StatusFound)
return
}
id, err := strconv.ParseInt(req.FormValue("id"), 10, 64)
if err != nil {
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
// TODO "flash" errors
http.Redirect(rw, req, "/admin/allow-list", http.StatusFound)
return
}
err = h.al.RemoveID(req.Context(), id)
if err != nil {
// TODO "flash" errors
}
http.Redirect(rw, req, "/admin/allow-list", http.StatusFound)
}

View File

@ -17,6 +17,7 @@ import (
var HTMLTemplates = []string{
"/admin/dashboard.tmpl",
"/admin/allow-list.tmpl",
"/admin/allow-list-remove-confirm.tmpl",
}
// Handler supplies the elevated access pages to known users.
@ -42,6 +43,8 @@ func Handler(r *render.Renderer, roomState *roomstate.Manager, al admindb.AllowL
mux.HandleFunc("/allow-list", r.HTML("/admin/allow-list.tmpl", ah.overview))
mux.HandleFunc("/allow-list/add", ah.add)
mux.HandleFunc("/allow-list/remove/confirm", r.HTML("/admin/allow-list-remove-confirm.tmpl", ah.removeConfirm))
mux.HandleFunc("/allow-list/remove", ah.remove)
// return mux
return customStripPrefix("/admin", mux)

View File

@ -5,6 +5,7 @@ import (
"net/http"
"testing"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
refs "go.mindeco.de/ssb-refs"
"github.com/PuerkitoBio/goquery"
@ -53,10 +54,10 @@ func TestAllowList(t *testing.T) {
ts := newSession(t)
a := assert.New(t)
lst := []refs.FeedRef{
{ID: bytes.Repeat([]byte{0}, 32), Algo: "fake"},
{ID: bytes.Repeat([]byte("1312"), 8), Algo: "test"},
{ID: bytes.Repeat([]byte("acab"), 8), Algo: "true"},
lst := admindb.ListEntries{
{ID: 1, PubKey: refs.FeedRef{ID: bytes.Repeat([]byte{0}, 32), Algo: "fake"}},
{ID: 2, PubKey: refs.FeedRef{ID: bytes.Repeat([]byte("1312"), 8), Algo: "test"}},
{ID: 3, PubKey: refs.FeedRef{ID: bytes.Repeat([]byte("acab"), 8), Algo: "true"}},
}
ts.AllowListDB.ListReturns(lst, nil)
@ -74,8 +75,8 @@ func TestAllowList(t *testing.T) {
a.EqualValues(html.Find("#theList").Children().Length(), 3)
lst = []refs.FeedRef{
{ID: bytes.Repeat([]byte{1}, 32), Algo: "one"},
lst = admindb.ListEntries{
{ID: 666, PubKey: refs.FeedRef{ID: bytes.Repeat([]byte{1}, 32), Algo: "one"}},
}
ts.AllowListDB.ListReturns(lst, nil)

View File

@ -160,7 +160,8 @@ func New(
m.NotFoundHandler = r.HTML("/error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
rw.WriteHeader(http.StatusNotFound)
return errorTemplateData{http.StatusNotFound, "Not Found", "the requested page wasnt found.."}, nil
msg := localizerFromRequest(locHelper, req).LocalizeSimple("PageNotFound")
return errorTemplateData{http.StatusNotFound, "Not Found", msg}, nil
})
mainMux.Handle("/", m)

View File

@ -7,8 +7,6 @@ import (
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
type Post struct {
@ -41,7 +39,7 @@ func showOverview(w http.ResponseWriter, req *http.Request) (interface{}, error)
}
func showPost(w http.ResponseWriter, req *http.Request) (interface{}, error) {
i, err := strconv.Atoi(mux.Vars(req)["PostID"])
i, err := strconv.Atoi(req.URL.Query().Get("id"))
if err != nil {
return nil, fmt.Errorf("argument parsing failed: %w", err)
}

View File

@ -25,8 +25,9 @@ func TestOverview(t *testing.T) {
func TestPost(t *testing.T) {
ts := newSession(t)
a := assert.New(t)
url, err := router.News(nil).Get(router.NewsPost).URL("PostID", "1")
url, err := router.News(nil).Get(router.NewsPost).URL()
a.Nil(err)
url.RawQuery = "id=1"
html, resp := ts.Client.GetHTML(url.String(), nil)
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
a.Equal(html.Find("h1").Text(), db[1].Name)
@ -35,8 +36,9 @@ func TestPost(t *testing.T) {
func TestURLTo(t *testing.T) {
ts := newSession(t)
a := assert.New(t)
url, err := router.News(nil).Get(router.NewsPost).URL("PostID", "1")
url, err := router.News(nil).Get(router.NewsPost).URL()
a.Nil(err)
url.RawQuery = "id=1"
html, resp := ts.Client.GetHTML(url.String(), nil)
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
a.Equal(html.Find("h1").Text(), db[1].Name)
@ -45,5 +47,5 @@ func TestURLTo(t *testing.T) {
a.Equal("/", lnk)
lnk, ok = html.Find("#next").Attr("href")
a.True(ok, "did not find href attribute")
a.Equal("/post/2", lnk)
a.Equal("/post?id=2", lnk)
}

View File

@ -1,14 +1,25 @@
Confirm = "Confirm"
PageNotFound = "The requested page was not found."
LandingTitle = "ohai my room"
LandingWelcome = "Landing welcome here"
NewsWelcome = "so, what happend (recently)"
NewsTitle = "News"
NewsOverview = "News - Overview"
AuthFallbackWelcome = "You really shouldn't be here.... Let's get you through this."
AuthFallbackTitle = "The place of last resort"
AuthSignIn = "Sign in"
AuthSignOut = "Sign out"
AdminDashboardWelcome = "Welcome to your dashboard"
AdminDashboardTitle = "Room Admin Dashboard"
AdminAllowListTitle = "List of allowed people"
AdminAllowListWelcome = "Here you can see the whole list and add or remove people from it."
AdminAllowListAdd = "Add to list"
AdminAllowListRemoveConfirmWelcome = "Just to verify that this is the entry you want deleted"
AdminAllowListRemoveConfirmTitle = "Really remove this Entry from the Allow list?"
NavAdminDashboard = "Admin Dasboard"

View File

@ -21,14 +21,14 @@ var Defaults = func() http.FileSystem {
fs := vfsgen۰FS{
"/": &vfsgen۰DirInfo{
name: "/",
modTime: time.Date(2021, 2, 11, 13, 14, 21, 93350678, time.UTC),
modTime: time.Date(2021, 2, 11, 15, 43, 30, 498222603, time.UTC),
},
"/active.en.toml": &vfsgen۰CompressedFileInfo{
name: "active.en.toml",
modTime: time.Date(2021, 2, 11, 13, 14, 21, 93350678, time.UTC),
uncompressedSize: 345,
modTime: time.Date(2021, 2, 15, 10, 58, 38, 310605935, time.UTC),
uncompressedSize: 1053,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x5c\x92\xdf\xaa\xdb\x30\x0c\xc6\xef\xf3\x14\x1f\xb9\xe9\x06\x5b\xde\xa0\x17\x63\x65\x30\x28\x2d\x74\x85\x31\xc6\x2e\x9c\x44\x8b\xcd\x71\xac\x60\xcb\x0d\xa1\xf4\xdd\x0f\xce\xbf\xa6\xe7\x4e\xfa\xf4\x93\xf4\xc9\xf8\xa8\x5c\x6d\x5c\x73\x35\x62\x09\x7b\xe4\xac\x95\x41\x3b\xc0\x33\xb7\x79\x36\x57\x7f\x93\xad\xb8\x1d\xeb\xb3\x82\x7e\x96\x34\x79\xca\xb3\x13\xf5\x61\x03\x05\xfe\x82\x5e\x2b\x81\x56\x5d\x47\xae\xc6\x27\x4f\x15\x39\xb1\xc3\xe7\x89\x5d\xd7\xa5\x64\x92\xce\x37\xf2\x37\x43\xfd\xa2\xe2\x2b\x16\x29\xcf\xbe\x45\xd1\x3f\x94\xb5\xa5\xaa\xde\x36\x7b\xfe\x70\x84\x27\x65\xed\x80\xa0\x39\xda\xda\xed\x04\xe5\x64\xaa\x28\x8a\x02\x47\x92\x5d\x40\x43\x82\x81\x23\x44\x7b\x8e\x8d\x86\x68\x13\x8a\xd7\xa1\xab\xa1\xab\x26\x74\x56\x55\x04\xfe\x0f\xab\x82\xc0\x53\x60\x2f\x13\xfe\xcb\x34\xee\xa7\x4b\x5c\x8a\x60\xdc\x53\x3e\x47\x59\x75\x8e\x89\xaf\x5b\xe3\x0e\x2a\xe8\x92\x95\xaf\x37\xae\x97\x50\x38\xb9\xf2\xa8\x17\xe6\x63\xcf\x6a\xea\xc2\xdc\x62\xac\xe1\xf0\x84\xb3\x93\xba\xbd\x36\x24\x76\xc5\x16\x2a\xfb\x3b\x4a\x69\xc6\x77\x8e\x4e\xfe\x65\x35\x85\xca\x9b\x4e\x0c\xbb\xe5\x64\x17\xdb\x92\x7c\xba\xb9\x23\xee\x2c\xc1\x38\xa8\xf9\x13\xb0\x5b\x5e\xc6\xc3\x04\xa4\xb4\x23\x1f\x38\xdd\x0f\xd1\x84\xcb\x84\x49\x02\xf6\x98\x48\x82\xf2\x84\xfb\xbd\x18\x77\x3e\x1e\x9b\xb9\x6b\xcb\x7b\x00\x00\x00\xff\xff\x4b\x47\xdc\x45\x7c\x02\x00\x00"),
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x7c\x53\xdb\x6a\xdc\x30\x10\x7d\xd7\x57\x0c\x7e\x49\x0b\xad\xff\x20\x94\x90\x34\xd0\xb2\x24\x65\x1b\x28\x25\xf4\x41\x6b\xcd\x5a\xa2\xb2\xc6\x1d\x8d\xd6\x98\x90\x7f\x2f\x92\x2f\x7b\x21\xe4\x69\xe5\x33\x67\xce\x9c\xb9\xec\x2d\x85\xbd\xe3\x0e\xae\xa1\x9a\x9f\x95\xfa\xa1\x5b\x7c\x20\xb9\xa7\x14\x4c\x0e\x3c\x59\x04\xc6\x7f\x09\xa3\xa0\x81\x5e\xb7\x08\x83\x8e\x10\x48\x60\x9f\x39\x75\xa5\xd4\x46\x07\xe3\x42\xfb\xe4\xc4\x63\xce\x21\xab\x1d\x74\x23\x30\x51\x57\x2d\xd1\x5f\xe8\x1b\xea\x4a\x7c\x46\x60\x98\x21\x8b\x8c\x95\x52\x0f\x38\xc4\x13\x56\xa4\x4f\x30\x58\x2d\x60\x75\xdf\x63\x30\xf0\x81\xb1\xc1\x20\x7e\xfc\x58\x15\xee\x5a\x2f\x7f\x4c\xd0\xe3\x01\xf9\xe0\x70\x58\x50\xf8\x0c\x0b\x54\x29\x75\x93\xc4\xde\x6b\xef\x77\xba\xf9\x7b\x52\xe8\x37\x25\x60\xd4\xde\x8f\x10\x2d\x25\x6f\xc2\x95\xc0\x6e\xb2\x55\xd7\x75\x0d\x1b\x94\xab\x08\x2d\x0a\x8c\x94\x40\x2c\x53\x6a\x2d\x88\x75\xb1\xae\xce\x44\x57\x47\x79\x6a\xbd\xd7\x0d\x02\xed\xc1\xeb\x28\xc0\x18\x89\x65\xa2\xff\x74\x6d\xf8\x16\x32\x2f\xbf\xc0\x85\x23\xfc\x98\x64\xc5\x29\x49\x36\x6d\x3a\x17\xee\x74\xb4\x3b\xd2\x6c\x4e\x6c\x2f\x4f\xa1\x6c\x8b\xc1\x2c\x9c\xea\x22\x67\x75\xb5\x25\xea\xa0\xc4\xe0\xee\x82\x7c\xe3\x3d\x0d\x1b\x17\xe5\xc6\x94\xb5\xe7\x1f\x21\xf0\x2e\xca\x25\x63\x8b\x1d\x1d\x70\x3e\x98\x13\x3f\xdf\x53\x94\x9c\x73\x40\x76\xfb\x11\x24\xaf\x2e\xcf\x08\x5c\x04\xb1\x08\x18\x84\xc7\x32\xc1\x41\x07\x01\x83\x1e\x05\xcd\xbb\xea\x47\xe7\xd3\x7a\xb8\x04\x27\xd5\xaf\x45\x6e\xcf\xd4\x15\xf5\x22\x50\xfc\x7e\xc9\xa7\xa4\x0f\xe7\x33\x98\x7a\x9a\x3b\x9f\x1b\x57\xea\x39\x57\xbc\xa5\x14\xe4\x8f\x32\x18\x1b\x76\xbd\x38\x2a\x9b\x69\x31\x20\xbb\x66\x9e\x00\x85\x65\xad\x8c\xb9\x9f\xfc\xed\x04\x3b\xa0\x50\xaa\x6f\x26\x96\x58\x64\xb8\x86\x85\xa8\x19\xe1\xe5\xa5\x2e\x05\x5e\x5f\x4b\x42\x3c\xcf\x50\xcf\xc5\x54\x5e\xcc\xdb\x36\xf2\x21\x85\xd4\xed\x90\xf3\x25\xf5\x48\xbd\x47\x70\x01\xf4\xfc\xe7\x7a\xcb\x58\x8f\x1c\x29\x9f\x55\x29\xb4\x9d\x78\xef\x5b\x3b\x0a\xaf\x29\xff\x03\x00\x00\xff\xff\x94\xbb\x13\x52\x1d\x04\x00\x00"),
},
}
fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{

View File

@ -10,8 +10,8 @@ const (
AdminAllowListOverview = "admin:allow-list:overview"
AdminAllowListRemoveAdd = "admin:allow-list:add"
AdminAllowListRemoveAsk = "admin:allow-list:remove:ask"
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirmed"
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm"
AdminAllowListRemove = "admin:allow-list:remove"
)
// Admin constructs a mux.Router containing the routes for the admin dashboard and settings pages
@ -24,10 +24,8 @@ func Admin(m *mux.Router) *mux.Router {
m.Path("/allow-list").Methods("GET").Name(AdminAllowListOverview)
m.Path("/allow-list/add").Methods("POST").Name(AdminAllowListRemoveAdd)
m.Path("/allow-list/remove").Methods("GET").Name(AdminAllowListRemoveAsk)
m.Path("/allow-list/remove").Methods("POST").Name(AdminAllowListRemoveConfirm)
// m.Path("/settings").Methods("GET").Name(AdminSettings)
m.Path("/allow-list/remove/confirm").Methods("GET").Name(AdminAllowListRemoveConfirm)
m.Path("/allow-list/remove").Methods("POST").Name(AdminAllowListRemove)
return m
}

View File

@ -17,7 +17,7 @@ func News(m *mux.Router) *mux.Router {
}
m.Path("/").Methods("GET").Name(NewsOverview)
m.Path("/post/{PostID:[0-9]+}").Methods("GET").Name(NewsPost)
m.Path("/post").Methods("GET").Name(NewsPost)
return m
}

View File

@ -0,0 +1,13 @@
{{ define "title" }}{{i18n "AdminAllowListRemoveConfirmTitle"}}{{ end }}
{{ define "content" }}
<h1 id="welcome">{{i18n "AdminAllowListRemoveConfirmWelcome"}}</h1>
<pre>{{.Entry.PubKey.Ref}}</pre>
<form action="{{urlTo "admin:allow-list:remove"}}" method="POST">
<input type="hidden" name="id", value={{.Entry.ID}}>
<input type="submit" value="{{i18n "Confirm"}}">
</form>
{{end}}

View File

@ -8,7 +8,7 @@
<p id="allowListCount">{{i18npl "ListCount" .Count}}</p>
<ul id="theList">
{{range .Entries}}
<li>{{.Ref}} (<a href="{{urlTo "admin:allow-list:remove:ask" "Entry" .}}">Remove</a>) </li>
<li>{{.PubKey.Ref}} (<a href="{{urlTo "admin:allow-list:remove:confirm" "id" .ID}}">Remove</a>) </li>
{{end}}
</ul>
</div>

View File

@ -5,7 +5,7 @@
</div>
<div class="row">
<div class="col-sm-12">
<pre id="errBody">{{.Err}}</pre>
<p id="errBody">{{.Err}}</p>
<p>
<a href="javascript:history.back()" class="btn btn-primary">Back</a>
</p>

View File

@ -9,5 +9,5 @@
</div>
</div> <!-- /row -->
<a href="{{urlTo "news:overview"}}" id="overview">Overview</a>
<a href="{{urlTo "news:post" "PostID" (inc .Post.ID)}}" id="next">Next...</a>
<a href="{{urlTo "news:post" "id" (inc .Post.ID)}}" id="next">Next...</a>
{{end}}

View File

@ -25,14 +25,21 @@ var Templates = func() http.FileSystem {
},
"/admin": &vfsgen۰DirInfo{
name: "admin",
modTime: time.Date(2021, 2, 13, 14, 6, 37, 200660480, time.UTC),
modTime: time.Date(2021, 2, 15, 10, 42, 43, 668945718, time.UTC),
},
"/admin/allow-list-remove-confirm.tmpl": &vfsgen۰CompressedFileInfo{
name: "allow-list-remove-confirm.tmpl",
modTime: time.Date(2021, 2, 15, 11, 3, 12, 580285061, time.UTC),
uncompressedSize: 389,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x8c\x8f\x41\x4b\x03\x41\x0c\x85\xef\xf3\x2b\x42\xce\xda\xd2\x9b\x94\xdd\x85\xa2\x1e\x44\xc1\x52\x0b\x9e\xb7\x9d\x2c\x1b\x98\xc9\x94\x69\xb6\xa5\x0c\xf9\xef\x32\xa5\x8a\xe0\xc5\x73\xbe\xbc\xef\xbd\x52\xc0\xd3\xc0\x42\x80\xca\x1a\x08\xc1\xac\x14\x5e\x3c\x08\xe0\xca\x47\x96\x55\x08\xe9\xfc\xc6\x47\xdd\x50\x4c\x27\x7a\x4c\x32\x70\x8e\xdb\x2b\x5b\x51\x20\xf1\x60\xe6\x7e\x05\xed\x93\x28\x89\xd6\x28\xd7\x8c\x0b\x60\xdf\xe2\x99\xc2\x3e\x45\xc2\xee\x1f\xe1\x9f\x37\xd6\xac\x99\x8f\x8b\xce\xb9\xe6\x90\xa9\x2b\x65\xf6\x2c\x9a\x2f\xb3\xf5\xb4\x7b\xa5\xcb\x6c\x43\x43\x05\xea\xc9\x39\xd7\x0c\x29\x47\xe8\xf7\xca\x49\x5a\x2c\x65\xca\x61\x9b\x00\xfb\x6a\x59\xf6\x55\x73\x1f\xf8\xa8\xcb\x7c\x15\xa1\x19\x42\x24\x1d\x93\x6f\x71\xfd\xfe\xb1\xc5\xce\x01\x34\x2c\x87\x49\x41\x2f\x07\x6a\x71\x64\xef\x49\x10\xa4\x8f\xd4\x22\x7b\xbc\x83\x53\x1f\x26\x6a\x7f\x7a\xbc\x3c\x99\xfd\x79\x3b\x4e\xbb\xc8\x8a\x37\x16\xbf\xd7\xde\x96\x55\x6f\xe7\x9a\x79\x2d\xdb\x39\x57\x0a\x89\x37\x73\x5f\x01\x00\x00\xff\xff\x09\x68\xfa\x06\x85\x01\x00\x00"),
},
"/admin/allow-list.tmpl": &vfsgen۰CompressedFileInfo{
name: "allow-list.tmpl",
modTime: time.Date(2021, 2, 13, 14, 39, 19, 265000251, time.UTC),
uncompressedSize: 501,
modTime: time.Date(2021, 2, 15, 13, 9, 28, 681823234, time.UTC),
uncompressedSize: 727,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x6c\x90\xc1\x4a\xec\x30\x14\x86\xf7\x7d\x8a\x73\x03\x17\xee\x45\xd2\x5a\x57\x5a\x32\x81\x41\x5d\x08\xae\x64\xc0\x85\xb8\x08\xcd\x99\x69\x30\x4d\x4a\x92\x4e\x91\x90\x77\x97\x86\x14\x47\x70\x75\xe0\xfc\xf9\xbf\xef\x90\x18\x41\xe2\x51\x19\x04\x12\x54\xd0\x48\x20\xa5\x18\x55\x7b\x6b\x80\xec\xe5\xa8\xcc\x5e\x6b\xbb\x3c\x2b\x1f\x0e\x39\x5e\x53\x40\x23\x21\xa5\xea\xa2\xdb\x5b\x13\xd0\x84\xb5\x5d\x31\xa9\xce\xd0\x6b\xe1\xfd\x8e\x4c\xe2\x84\x74\x40\x21\xd1\x11\x5e\x01\xb0\xa1\x05\x25\x77\x64\x41\xdd\xdb\x11\x09\xff\xdd\xf5\x5a\xe2\x94\x58\x33\xb4\xbc\x62\x8d\x54\x67\xfe\x83\xec\xec\x92\x89\x00\x97\xdb\xde\x6a\x3a\x4a\xda\xde\x6c\xd9\x94\x75\x62\x03\xdf\xdb\xd9\x84\xcd\x3a\xe9\xe2\xfd\x4e\xa0\xce\x73\xf5\x4e\x85\x30\xeb\x8c\x08\x03\xae\xcf\x0a\x37\x46\x27\xcc\x09\xa1\x7e\x34\xc1\x29\xf4\x29\xe5\x35\x30\xad\x78\x8c\x75\x4a\xf0\x8f\x09\x18\x1c\x1e\x77\x24\xc6\xd9\xe9\x83\x05\x22\x56\x57\x97\x6f\xa1\x5a\xf9\xd0\x39\x1c\xed\x19\x3b\xe1\x3f\x08\x90\x95\xf4\x49\x40\xd4\x4f\x0f\x29\x11\xfe\x92\x33\xd6\x08\xfe\x1f\x58\xa3\xd5\xe6\x45\x23\x8b\x8c\x35\xb3\x2e\x37\x96\xef\xc9\x03\xd8\x1f\x4a\xa1\x71\x76\x01\x4a\x79\xb5\x35\xaa\xf8\xd7\x77\x6f\xd7\xf4\xee\xfd\x2a\x7d\x05\x00\x00\xff\xff\x1b\xbe\xde\x6b\xf5\x01\x00\x00"),
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x7c\x52\x4d\x8b\xdb\x30\x10\xbd\xfb\x57\x4c\xe7\xd4\x1e\x6c\x93\xee\xa5\x04\xd9\x10\xda\x1e\x4a\x0b\x5d\xb6\x81\x1e\x8b\x62\x8d\xd7\x43\x65\xc9\xc8\x52\xd2\x20\xf4\xdf\x8b\x15\x9b\x6e\x61\x77\x4f\xb6\xe7\xcd\xfb\xf0\x93\x62\x04\x45\x3d\x1b\x02\xf4\xec\x35\x21\xa4\x14\x23\xef\x3e\x18\xc0\x83\x1a\xd9\x1c\xb4\xb6\x97\x6f\x3c\xfb\x63\x86\x17\x14\xc8\x28\x48\xa9\x78\xc2\xed\xac\xf1\x64\xfc\xc2\x2e\x84\xe2\x33\x74\x5a\xce\x73\x83\x93\x7c\xa4\x72\x20\xa9\xc8\x61\x5b\x00\x88\x61\x07\xac\x1a\xbc\x90\xee\xec\x48\xd8\x3e\xef\xf5\x73\x85\x53\x12\xf5\xb0\x6b\x0b\x51\x2b\x3e\xb7\xff\x29\x3b\x7b\xc9\x8a\x00\x4f\xa7\x9d\xd5\xe5\xa8\xca\xdd\xfb\x0d\x9b\xb2\x9d\xdc\x84\x3f\xda\x60\xfc\xe6\x3a\x69\xc0\x7f\x43\xa8\xf2\x73\xb1\x9c\x56\x72\xd0\x99\xed\x07\x5a\xd6\x56\xc9\x18\x9d\x34\x8f\x04\xd5\x67\xe3\x1d\xd3\x9c\x52\x1e\x03\x08\xcd\x6d\x8c\xd5\x7d\x38\x7d\xa5\x6b\xf5\x40\x7d\x4a\xf0\x56\x48\x18\x1c\xf5\x0d\xc6\x18\x9c\x3e\x5a\x40\xb9\xfc\xe9\x3e\x27\x2a\x35\xcf\x7e\xef\x68\xb4\x67\xda\x77\xd6\xf4\xec\x46\x04\x64\x85\x50\x7d\xf9\x94\x12\xb6\x0f\x19\x13\xb5\x6c\xdf\x81\xa8\x35\x6f\x11\xc8\xa8\xd5\x57\xd4\x41\xaf\x71\x6f\x25\x2d\xaf\xb7\xef\xe1\xee\x85\x7a\x0f\x4a\xdd\xaa\xbd\x5b\x99\xbd\x75\x23\xc8\xce\xb3\x35\xaf\x25\x95\x99\x87\x30\x92\x1f\xac\x6a\xf0\xfe\xfb\x8f\xe3\xda\x0a\x80\x60\x33\x05\x0f\xfe\x3a\x51\x83\x9e\xfe\x78\x04\x23\x47\x6a\x70\x0a\xa7\x5f\xbf\xe9\xfa\xfc\xe2\x1c\x4e\x23\x7b\x84\xb3\xd4\x81\x16\xef\x97\xf3\x6e\x47\x5a\x2f\x69\xb7\x3b\x01\xe2\x4d\x59\x42\xed\xec\x05\xca\xb2\x2d\xb6\x66\xfe\x06\x00\x00\xff\xff\xd5\x1a\x55\x28\xd7\x02\x00\x00"),
},
"/admin/dashboard.tmpl": &vfsgen۰CompressedFileInfo{
name: "dashboard.tmpl",
@ -54,17 +61,17 @@ var Templates = func() http.FileSystem {
},
"/base.tmpl": &vfsgen۰CompressedFileInfo{
name: "base.tmpl",
modTime: time.Date(2021, 2, 12, 9, 56, 41, 60426021, time.UTC),
uncompressedSize: 1001,
modTime: time.Date(2021, 2, 13, 15, 46, 16, 260863572, time.UTC),
uncompressedSize: 1101,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xbc\x93\x41\x6f\xdb\x30\x0c\x85\xef\xf9\x15\xac\xb0\xe3\x1c\xaf\xb7\xc2\x90\x0d\x74\xeb\xb0\xed\xd2\x0e\x4b\x2e\x3b\x15\x8a\xcd\xc4\x42\x68\x29\x90\x68\x67\x85\xa0\xff\x3e\x58\xb1\x93\xa5\x18\xd0\x9d\x76\x4a\x48\x3e\x7e\x06\x1f\x9e\xe4\xcd\xc3\xd3\xa7\xf5\xcf\xef\x9f\xa1\xe5\x8e\xaa\x85\x9c\x7f\x50\x35\xd5\x02\x40\xde\x64\x19\xd4\xb6\x3b\x10\x32\xc2\x46\x79\x04\xc6\xee\x40\x8a\x11\xb2\x2c\x29\x3a\x64\x05\x75\xab\x9c\x47\x2e\x45\xcf\xdb\xec\x4e\x40\x7e\x19\x19\xd5\x61\x29\x06\x8d\xc7\x83\x75\x2c\xa0\xb6\x86\xd1\x70\x29\x8e\xba\xe1\xb6\x6c\x70\xd0\x35\x66\xa9\x78\x0f\xda\x68\xd6\x8a\x32\x5f\x2b\xc2\xf2\x76\xf9\x61\x46\x91\x36\x7b\x68\x1d\x6e\x4b\x91\x2b\xef\x91\x7d\xee\xf9\x85\x70\x59\x7b\x2f\xc0\x21\x95\x22\xd5\xbe\x45\x64\x91\x56\x58\x33\x61\x15\xc2\x86\x6c\xbd\x07\x91\x4a\x01\xcb\x18\xbf\xd8\x6c\xb5\xfa\x08\x3f\xac\xed\x60\x85\x6e\x40\x17\x02\x9a\x26\x46\x99\x9f\x76\x16\x32\x3f\x19\x20\x37\xb6\x79\x19\x61\x67\x0a\xfe\x62\xa7\x12\x65\xda\x59\x8c\x9f\x6a\xf4\x30\xaa\x46\xdd\xbb\xde\xa3\x83\xa2\x04\xed\x9f\xc9\xee\x76\xd8\x3c\x6b\x13\xe3\x34\xd5\x5b\x48\x82\xa9\x01\x20\x0f\xd5\xf4\x0f\xe0\x2b\x12\xd9\x19\xb1\x7c\x54\x1d\xc6\x78\x33\xeb\xf2\xb3\x50\xf6\x74\xd9\x91\xa4\x2b\xa9\x26\x67\x42\xe8\x1d\xad\x2d\x08\x83\x47\x5f\xd8\x01\xdd\x68\xbb\x88\x51\x54\x21\xe8\xdb\x3b\x03\xe2\x11\x8f\x7e\x9d\x9c\x18\xcf\x55\x95\xcc\x49\xbf\x45\x53\x4d\xa7\x4d\xd1\x28\xdf\x6e\xac\x72\xcd\x35\x4f\x0d\xf7\xe3\xf8\xe1\x8f\xe9\x3f\x73\x7b\x6e\x8b\xad\x22\xda\xa8\x7a\x5f\x90\xdd\xd9\x9e\xaf\xe0\xf7\x3d\xb7\x2b\xbd\x33\x4f\xa9\xff\x1a\x2b\xf3\xd9\x88\x10\x90\x3c\x5e\x3c\xfd\xef\x06\x5d\x1d\xe2\xf5\xce\x68\x53\x6c\xad\xeb\xfe\x7a\xcd\x37\xf3\xd6\x31\x29\x57\x63\x2f\xe5\xea\x55\xc0\x60\x4a\xe2\xf4\x8e\x04\x2c\xe1\x12\xc6\xf3\x92\xcc\x4f\xd1\x95\xf9\xe9\x45\xff\x0e\x00\x00\xff\xff\x3b\x0e\xae\xe2\xe9\x03\x00\x00"),
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xbc\x94\x41\x6f\xdb\x3c\x0c\x86\xef\xf9\x15\x8c\xf0\x1d\x3f\xc7\xeb\xad\x30\x64\x03\xdd\x3a\x6c\x03\x86\x76\x58\x72\xd9\xa9\x50\x6c\x26\x26\x42\x4b\x81\x44\x3b\x2b\x0c\xff\xf7\xc1\x8a\x93\x2c\x45\x81\xa2\x97\x9d\x6c\x91\x2f\x1f\x8b\xd4\x2b\xeb\xf9\xfd\xe3\xa7\xd5\xaf\x1f\x9f\xa1\x96\x86\x8b\x99\x3e\x3d\xd0\x54\xc5\x0c\x40\xcf\x93\x04\x4a\xd7\xec\x19\x05\x61\x6d\x02\x82\x60\xb3\x67\x23\x08\x49\x12\x15\x0d\x8a\x81\xb2\x36\x3e\xa0\xe4\xaa\x95\x4d\x72\xab\x20\xbd\xa4\xac\x69\x30\x57\x1d\xe1\x61\xef\xbc\x28\x28\x9d\x15\xb4\x92\xab\x03\x55\x52\xe7\x15\x76\x54\x62\x12\x17\xff\x03\x59\x12\x32\x9c\x84\xd2\x30\xe6\x37\x8b\x0f\x27\x14\x93\xdd\x41\xed\x71\x93\xab\xd4\x84\x80\x12\xd2\x20\xcf\x8c\x8b\x32\x04\x05\x1e\x39\x57\x71\x1d\x6a\x44\x51\xb1\x44\x48\x18\x8b\xbe\x5f\xb3\x2b\x77\xa0\xe2\x52\xc1\x62\x18\xbe\xb8\x64\xb9\xfc\x08\x3f\x9d\x6b\x60\x89\xbe\x43\xdf\xf7\x68\xab\x61\xd0\xe9\xb1\x66\xa6\xd3\xe3\x00\xf4\xda\x55\xcf\x23\xec\x4c\xc1\xdf\xe2\x4d\xa4\x4c\x35\xb3\xf1\x53\x15\x75\xa3\x6a\xd4\xfd\xd7\x06\xf4\x90\xe5\x40\xe1\x89\xdd\x76\x8b\xd5\x13\xd9\x61\x98\xb2\xb4\x81\x28\x98\x02\x00\x7a\x5f\x4c\x6f\x00\x5f\x91\xd9\x9d\x10\x8b\x07\xd3\xe0\x30\xcc\x4f\xba\xf4\x2c\xd4\x2d\x5f\x6a\x34\x53\xa1\xcd\x34\x99\xbe\x6f\x3d\xaf\x1c\x28\x8b\x87\x90\xb9\x0e\xfd\x38\x76\x35\x0c\xaa\xe8\x7b\xba\xb9\xb5\xa0\x1e\xf0\x10\x56\x71\x12\x63\xbb\xa6\xd0\x29\xd3\x5b\x34\x53\x35\x64\xb3\xca\x84\x7a\xed\x8c\xaf\xae\x79\xa6\xbb\x1b\xd3\xf7\x7f\x65\xdf\xc7\x35\xcc\xee\x90\x30\x05\x79\x7d\xc7\x11\x7f\x37\x8a\xbe\x53\x90\x77\xef\xbd\x95\x3a\xdb\x18\xe6\xb5\x29\x77\x19\xbb\xad\x6b\xe5\x1a\xdf\x4a\xbd\xa4\xad\x7d\x8c\xf1\x97\x58\x9d\x9e\x86\xdd\xf7\xc8\x01\x2f\xe7\xf6\xcf\x0f\xe1\xaa\x91\x40\x5b\x4b\x36\xdb\x38\xdf\xbc\xda\xcd\x37\xfb\x56\x33\xd1\xbb\x63\x2c\x7a\xf7\x85\x89\x61\x72\xfb\x74\x57\x15\x2c\xe0\x62\xf8\x73\x91\x4e\x8f\xd7\x43\xa7\xc7\xbf\xc6\x9f\x00\x00\x00\xff\xff\xc1\x40\x14\x8a\x4d\x04\x00\x00"),
},
"/error.tmpl": &vfsgen۰CompressedFileInfo{
name: "error.tmpl",
modTime: time.Date(2021, 2, 9, 14, 51, 23, 670842269, time.UTC),
uncompressedSize: 369,
modTime: time.Date(2021, 2, 15, 11, 3, 37, 909071369, time.UTC),
uncompressedSize: 365,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x5c\xd0\xc1\x4e\xc4\x20\x10\x06\xe0\x7b\x9f\x62\x82\x17\x3d\xb0\x4d\x3d\x1a\xca\x61\xcd\x3e\x81\x4f\x30\x85\x59\x8b\xdb\x42\x33\x60\x4d\x43\x78\x77\xe3\xb2\xcd\xaa\xa7\x49\x7e\xe0\xfb\xc9\xe4\x6c\xe9\xec\x3c\x81\x48\x2e\x4d\x24\x4a\x61\xf2\x96\x18\x24\x9c\x98\x03\x43\xce\x87\xb7\x84\xe9\x33\xbe\x06\x4b\xa5\xe4\x4c\xde\x96\xd2\xdc\xdf\x99\xe0\x13\xf9\x24\x4a\x69\x94\x75\x2b\x98\x09\x63\xec\xc5\x82\xef\x24\x47\x42\x4b\x2c\x74\x03\xa0\xc6\x4e\x57\xf1\xe1\x1f\xa9\xe2\x8c\xd3\xa4\x41\xde\xbb\x4a\x51\x6d\x4d\x55\x3b\x76\xba\x51\xad\x75\xab\xfe\xe3\x73\xf8\xaa\xee\xaf\xcc\x84\x49\xc6\x59\x76\xcf\xd7\x13\x00\xb5\x30\x81\xb3\xbd\x20\xe6\x63\xb0\x9b\xd0\x39\x1f\x4e\xcc\x3f\xfc\xc2\xb4\x5f\xaa\x13\x40\x21\x8c\x4c\xe7\x5e\x7c\xe0\x8a\xd1\xb0\x5b\xd2\xcb\xe8\x62\x0a\xbc\x1d\x06\x34\x97\xc7\x27\xb1\x37\x0d\xc9\xc3\x90\xbc\x5c\xd8\xcd\xc8\x9b\xd0\x47\x34\x17\xd5\xe2\x8d\x6c\xaf\xe6\xfe\xeb\x3a\x6e\x9b\xfb\x0e\x00\x00\xff\xff\x6b\x12\x36\x5b\x71\x01\x00\x00"),
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x5c\xd0\xc1\x4e\xf4\x20\x10\x07\xf0\x7b\x9f\x62\xc2\x77\xf9\x3c\xd0\xa6\x1e\x0d\xe5\xb0\x66\x9f\xc0\x27\x98\xc2\xac\xc5\x6d\xa1\x19\xb0\xa6\x21\xbc\xbb\x51\xb6\x59\xf5\x34\xc9\x1f\xf8\xfd\xc9\xe4\x6c\xe9\xe2\x3c\x81\x48\x2e\xcd\x24\x4a\x61\xf2\x96\x18\x24\x9c\x99\x03\x43\xce\xed\x4b\xc2\xf4\x1e\x9f\x83\xa5\x52\x72\x26\x6f\x4b\x69\xee\xef\x4c\xf0\x89\x7c\x12\xa5\x34\xca\xba\x0d\xcc\x8c\x31\x0e\x62\xc5\x57\x92\x13\xa1\x25\x16\xba\x01\x50\x53\xaf\xab\xf8\xef\x0f\xa9\xe2\x82\xf3\xac\x41\xde\xbb\x4a\x51\x5d\x4d\x55\x37\xf5\xba\x51\x9d\x75\x9b\xfe\xe5\x73\xf8\xa8\xee\x8f\xcc\x84\x59\xc6\x45\xf6\x8f\xdf\x27\x00\x6a\x05\x67\x07\x41\xcc\xa7\x60\x77\xa1\x73\x6e\xcf\xcc\x5f\xf8\x7a\x5c\xa8\x13\x40\x21\x4c\x4c\x97\x41\xbc\xe1\x86\xd1\xb0\x5b\xd3\xd3\xe4\x62\x0a\xbc\xb7\x23\x9a\xeb\xff\x07\x71\xb4\x8c\xc9\xc3\x98\xbc\x5c\xd9\x2d\xc8\xbb\xd0\x27\x34\x57\xd5\xe1\x8d\xac\xf6\xf1\xe3\x3a\x6e\x5b\xfb\x0c\x00\x00\xff\xff\x9c\xcf\x6d\x54\x6d\x01\x00\x00"),
},
"/landing": &vfsgen۰DirInfo{
name: "landing",
@ -97,10 +104,10 @@ var Templates = func() http.FileSystem {
},
"/news/post.tmpl": &vfsgen۰CompressedFileInfo{
name: "post.tmpl",
modTime: time.Date(2021, 2, 9, 14, 51, 23, 670842269, time.UTC),
uncompressedSize: 388,
modTime: time.Date(2021, 2, 15, 13, 15, 23, 990632460, time.UTC),
uncompressedSize: 384,
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x74\xce\xb1\x6e\x84\x30\x0c\x80\xe1\x9d\xa7\x70\x3d\xb5\x43\x40\x74\xaa\x4e\x21\xd3\x2d\xb7\xd0\x0e\xbc\x40\x44\x7c\x25\x12\x97\xa0\x24\x05\xa4\xc8\xef\x5e\x41\xdb\x6b\x19\x3a\x25\xfa\x65\x7f\x72\xce\x60\xe8\x6a\x1d\x01\x26\x9b\x46\x42\x60\xce\xd9\xd6\x2f\x0e\xb0\xa5\x25\x76\x7b\x64\x3e\x41\xce\xe5\x9b\x8f\xa9\x6c\xf5\x8d\xb6\x19\x20\x67\x80\xb9\xf8\x23\xf4\xde\x25\x72\x69\x33\x0a\x69\xec\x0c\xfd\xa8\x63\x6c\x70\xd2\xef\x24\x06\xd2\x86\x02\xaa\x02\x40\x0e\xb5\x3a\x72\xb2\x1a\x6a\x55\xc8\xca\xd8\x59\x1d\x56\x83\x5f\xf0\x58\x7a\x3f\x8a\x9b\x11\xf5\xf3\xd6\xa7\xbb\xd3\xd1\x9a\x36\x67\xfa\x65\xf6\x07\xe4\x83\x10\x50\x05\xbf\x80\x10\xaa\x90\x1a\x86\x40\xd7\x06\x73\xfe\x08\x63\xe7\x01\x1d\x2d\xf1\xe4\x67\x0a\xb3\xa5\x05\x99\x11\xac\x69\xf0\x1e\xd4\xeb\xf7\x4f\x56\xfa\xdf\xf5\xc9\xc7\x84\x80\xdb\x21\x97\x33\xc2\xa3\x75\x3d\x7c\x9d\x75\x39\x3f\xfd\x90\x8e\xd6\x84\xaa\xa5\x35\x95\x65\xb9\x6b\x39\x93\x33\xcc\x9f\x01\x00\x00\xff\xff\xc9\x34\x6e\xa1\x84\x01\x00\x00"),
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x74\xce\xb1\x4e\xc5\x20\x14\xc6\xf1\xbd\x4f\x71\x3c\x93\x0e\xb4\xa9\x93\xb9\xe1\x32\xb9\xb8\x54\x87\xbe\x00\x29\xe7\x5a\x92\x5e\x68\x00\x4b\x13\x72\xde\xdd\xb4\xea\xd5\x0e\x4e\x90\x7f\xf8\x7e\xa1\x14\x30\x74\xb1\x8e\x00\x93\x4d\x13\x21\x30\x97\x62\xdb\x27\x07\xd8\x51\x8e\xfd\x1e\x99\x4f\x50\x4a\xfd\xe6\x63\xaa\x3b\x7d\xa5\xed\x0d\x90\x33\xc0\x5c\xfd\x11\x06\xef\x12\xb9\xb4\x19\x95\x34\x76\x81\x61\xd2\x31\x9e\x71\xd6\xef\x24\x46\xd2\x86\x02\xaa\x0a\x40\x8e\xad\x3a\x72\xb2\x19\x5b\x55\xc9\xc6\xd8\x45\x1d\xa6\xc1\x67\x3c\x96\xc1\x4f\xe2\x6a\x44\xfb\xb8\xf5\xf9\xe6\xf4\xb4\xa6\xcd\x99\x7f\x99\xfd\x00\x79\x27\x04\x34\xc1\x67\x10\x42\x55\x52\xc3\x18\xe8\x72\xc6\x52\x3e\xc2\xd4\x7b\x40\x47\x39\x9e\xfc\x42\x61\xb1\x94\x91\x19\xc1\x9a\x33\xde\x82\x7a\xfd\xbe\xc9\x46\xff\x3b\x9f\x7d\x4c\x08\x68\x0d\xc2\xbd\x75\x03\x7c\x7d\xe9\xe5\xf9\xe1\x87\x73\xb4\x26\x54\x1d\xad\xa9\xae\xeb\x5d\x2a\x85\x9c\x61\xfe\x0c\x00\x00\xff\xff\xec\x11\x39\x89\x80\x01\x00\x00"),
},
}
fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
@ -112,6 +119,7 @@ var Templates = func() http.FileSystem {
fs["/news"].(os.FileInfo),
}
fs["/admin"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
fs["/admin/allow-list-remove-confirm.tmpl"].(os.FileInfo),
fs["/admin/allow-list.tmpl"].(os.FileInfo),
fs["/admin/dashboard.tmpl"].(os.FileInfo),
}

View File

@ -3,7 +3,6 @@
package web
import (
"errors"
"fmt"
"html/template"
"io/ioutil"
@ -11,6 +10,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
refs "go.mindeco.de/ssb-refs"
@ -24,6 +24,7 @@ import (
//go:generate go run -tags=dev embedded_generate.go
// TemplateFuncs returns a map of template functions
func TemplateFuncs(m *mux.Router) template.FuncMap {
return template.FuncMap{
"urlTo": NewURLTo(m),
@ -31,12 +32,22 @@ func TemplateFuncs(m *mux.Router) template.FuncMap {
}
}
// NewURLTo returns a template helper function for a router.
// It is usually called with one parameter, the route name, which should be defined in the router package.
// If it's called with more then one, it has a to be a pair of two values. (1, 3, 5, 7, etc.)
// The first value of such a pair is the placeholder name in the router (i.e. in '/our/routes/{id:[0-9]+}/test' it would be id )
// and the 2nd value is the actual value that should be put in place of the placeholder.
func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
l := logging.Logger("helper.URLTo") // TOOD: inject in a scoped way
return func(routeName string, ps ...interface{}) *url.URL {
route := appRouter.Get(routeName)
if route == nil {
level.Warn(l).Log("msg", "no such route", "route", routeName, "params", ps)
level.Warn(l).Log("msg", "no such route", "route", routeName, "params", fmt.Sprintf("%v", ps))
return &url.URL{}
}
if len(ps)%2 != 0 {
level.Warn(l).Log("msg", "expected even number of params (name-value pairs)", "route", routeName, "params", fmt.Sprintf("%v", ps))
return &url.URL{}
}
@ -53,11 +64,11 @@ func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
params = append(params, v.Ref())
default:
level.Error(l).Log("msg", "invalid param type", "param", fmt.Sprintf("%T", p), "route", routeName)
logging.CheckFatal(errors.New("invalid param"))
}
}
u, err := route.URLPath(params...)
// named vars in routes don't work because we cant use the mux.router with middleware correctly
u, err := route.URLPath()
if err != nil {
level.Error(l).Log("msg", "failed to create URL",
"route", routeName,
@ -65,6 +76,16 @@ func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
"error", err)
return &url.URL{}
}
urlVals := u.Query()
n := len(params)
for i := 0; i < n; i += 2 {
key, value := strings.ToLower(params[i]), params[i+1]
urlVals.Set(key, value)
}
u.RawQuery = urlVals.Encode()
return u
}
}