forked from wgroeneveld/go-jamming
admin html dashboard and handler test
This commit is contained in:
parent
6cc83620ba
commit
e361567eed
|
@ -52,7 +52,7 @@ Accepted form format:
|
||||||
target=https://aaronpk.example/post-by-aaron
|
target=https://aaronpk.example/post-by-aaron
|
||||||
```
|
```
|
||||||
|
|
||||||
Will result in a `202 Accepted` - it handles things async. Stores in `.json` files in `[dataPath]/domain`.
|
Will result in a `202 Accepted` - it handles things async. Stores mentions in a **to approve** and **approved** database separately.
|
||||||
|
|
||||||
This also saves the author picture/avatar locally - if present in the microformat. It does _not_ resize images, however, if it's bigger than 5 MB, it falls back to a default one.
|
This also saves the author picture/avatar locally - if present in the microformat. It does _not_ resize images, however, if it's bigger than 5 MB, it falls back to a default one.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Go-Jamming admin dashboard</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
thead tr td {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>🥞 Go-Jamming Admin</h1>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2>Mentions To Approve</h2>
|
||||||
|
|
||||||
|
{{ range $domain, $mentions := .Mentions }}
|
||||||
|
<h3> 🌐 Domain <em>{{ $domain }}</em> »</h3>
|
||||||
|
|
||||||
|
{{ if $mentions }}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Source</td>
|
||||||
|
<td>Target</td>
|
||||||
|
<td>Content</td>
|
||||||
|
<td>Approve?</td>
|
||||||
|
<td>Reject?</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range $mentions }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .Source }}</td>
|
||||||
|
<td>{{ .Target }}</td>
|
||||||
|
<td>{{ .Content }}</td>
|
||||||
|
<td><a href="{{ .ApproveURL }}">✅ Yes!</a></td>
|
||||||
|
<td><a href="{{ .RejectURL }}">❌ Nop!</a></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ else }}
|
||||||
|
<p>No mentions to approve, all done.</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2>Config</h2>
|
||||||
|
|
||||||
|
Current <code>config.json</code> contents:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
{{ .Config }}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,25 +1,108 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"brainbaking.com/go-jamming/app/mf"
|
||||||
"brainbaking.com/go-jamming/common"
|
"brainbaking.com/go-jamming/common"
|
||||||
"brainbaking.com/go-jamming/db"
|
"brainbaking.com/go-jamming/db"
|
||||||
"brainbaking.com/go-jamming/rest"
|
"brainbaking.com/go-jamming/rest"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleGet(repo db.MentionRepo) http.HandlerFunc {
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed dashboard.html
|
||||||
|
var dashboardTemplate []byte
|
||||||
|
|
||||||
|
//go:embed moderated.html
|
||||||
|
var moderatedTemplate []byte
|
||||||
|
|
||||||
|
type dashboardMention struct {
|
||||||
|
Source string
|
||||||
|
Target string
|
||||||
|
Content string
|
||||||
|
ApproveURL string
|
||||||
|
RejectURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardData struct {
|
||||||
|
Config string
|
||||||
|
Mentions map[string][]dashboardMention
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardModerated struct {
|
||||||
|
Action string
|
||||||
|
Item string
|
||||||
|
RedirectURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func indiewebDataToDashboardMention(c *common.Config, dbMentions []*mf.IndiewebData) []dashboardMention {
|
||||||
|
var mentions []dashboardMention
|
||||||
|
for _, dbMention := range dbMentions {
|
||||||
|
wm := dbMention.AsMention()
|
||||||
|
// TODO move this to somewhere else? the wm? duplicate in notifier.go
|
||||||
|
approveUrl := fmt.Sprintf("%sadmin/approve/%s/%s", c.BaseURL, c.Token, wm.Key())
|
||||||
|
rejectUrl := fmt.Sprintf("%sadmin/reject/%s/%s", c.BaseURL, c.Token, wm.Key())
|
||||||
|
|
||||||
|
mentions = append(mentions, dashboardMention{
|
||||||
|
Source: dbMention.Source,
|
||||||
|
Target: dbMention.Target,
|
||||||
|
Content: dbMention.Content,
|
||||||
|
ApproveURL: approveUrl,
|
||||||
|
RejectURL: rejectUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return mentions
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDashboardData(c *common.Config, repo db.MentionRepo) *dashboardData {
|
||||||
|
data := &dashboardData{
|
||||||
|
Config: c.String(),
|
||||||
|
Mentions: map[string][]dashboardMention{},
|
||||||
|
}
|
||||||
|
for _, domain := range c.AllowedWebmentionSources {
|
||||||
|
data.Mentions[domain] = indiewebDataToDashboardMention(c, repo.GetAllToModerate(domain).Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func asTemplate(name string, data []byte) *template.Template {
|
||||||
|
tmpl, err := template.New(name).Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Str("name", name).Msg("Template invalid")
|
||||||
|
}
|
||||||
|
return tmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGet(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
||||||
|
tmpl := asTemplate("dashboard", dashboardTemplate)
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := tmpl.Execute(w, getDashboardData(c, repo))
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Unable to fill in dashboard template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGetToApprove(repo db.MentionRepo) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
domain := mux.Vars(r)["domain"]
|
domain := mux.Vars(r)["domain"]
|
||||||
rest.Json(w, repo.GetAllToModerate(domain))
|
rest.Json(w, repo.GetAllToModerate(domain))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO unit tests
|
|
||||||
// HandleApprove approves the Mention (by key in URL) and adds to the whitelist.
|
// HandleApprove approves the Mention (by key in URL) and adds to the whitelist.
|
||||||
// Returns 200 OK with approved source/target or 404 if key is invalid.
|
// Returns 200 OK with approved source/target or 404 if key is invalid.
|
||||||
func HandleApprove(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
func HandleApprove(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
||||||
|
tmpl := asTemplate("moderated", moderatedTemplate)
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
key := mux.Vars(r)["key"]
|
key := mux.Vars(r)["key"]
|
||||||
|
|
||||||
|
@ -30,14 +113,19 @@ func HandleApprove(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AddToWhitelist(approved.AsMention().SourceDomain())
|
c.AddToWhitelist(approved.AsMention().SourceDomain())
|
||||||
w.WriteHeader(http.StatusOK)
|
err := tmpl.Execute(w, asDashboardModerated("Approved", approved, c))
|
||||||
fmt.Fprintf(w, "Approved: %s", approved.AsMention().String())
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Unable to fill in dashboard template")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleReject rejects the Mention (by key in URL) and adds to the blacklist.
|
// HandleReject rejects the Mention (by key in URL) and adds to the blacklist.
|
||||||
// Returns 200 OK with rejected source/target or 404 if key is invalid.
|
// Returns 200 OK with rejected source/target or 404 if key is invalid.
|
||||||
func HandleReject(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
func HandleReject(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
||||||
|
tmpl := asTemplate("moderated", moderatedTemplate)
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
key := mux.Vars(r)["key"]
|
key := mux.Vars(r)["key"]
|
||||||
|
|
||||||
|
@ -48,7 +136,18 @@ func HandleReject(c *common.Config, repo db.MentionRepo) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.AddToBlacklist(rejected.AsMention().SourceDomain())
|
c.AddToBlacklist(rejected.AsMention().SourceDomain())
|
||||||
w.WriteHeader(http.StatusOK)
|
err := tmpl.Execute(w, asDashboardModerated("Rejected", rejected, c))
|
||||||
fmt.Fprintf(w, "Rejected: %s", rejected.AsMention().String())
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
log.Error().Err(err).Msg("Unable to fill in dashboard template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func asDashboardModerated(action string, mention *mf.IndiewebData, c *common.Config) dashboardModerated {
|
||||||
|
return dashboardModerated{
|
||||||
|
Action: action,
|
||||||
|
Item: mention.AsMention().String(),
|
||||||
|
RedirectURL: fmt.Sprintf("%sadmin/%s", c.BaseURL, c.Token),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"brainbaking.com/go-jamming/app/mf"
|
||||||
|
"brainbaking.com/go-jamming/common"
|
||||||
|
"brainbaking.com/go-jamming/db"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cnf = common.Configure()
|
||||||
|
repo db.MentionRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
repo = db.NewMentionRepo(cnf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleGet(t *testing.T) {
|
||||||
|
wm := mf.Mention{
|
||||||
|
Source: "https://infos.by/markdown-v-nauke/",
|
||||||
|
Target: "https://brainbaking.com/post/2021/02/writing-academic-papers-in-markdown/",
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.InModeration(wm, &mf.IndiewebData{
|
||||||
|
Source: wm.Source,
|
||||||
|
Target: wm.Target,
|
||||||
|
Name: "mytest",
|
||||||
|
})
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/admin/{token}", HandleGet(cnf, repo)).Methods("GET")
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove("config.json")
|
||||||
|
ts.Close()
|
||||||
|
db.Purge()
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/%s", ts.URL, cnf.Token), nil)
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contentBytes, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
content := string(contentBytes)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
assert.Contains(t, content, "admin dashboard")
|
||||||
|
assert.Contains(t, content, wm.Source)
|
||||||
|
assert.Contains(t, content, wm.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleReject(t *testing.T) {
|
||||||
|
wm := mf.Mention{
|
||||||
|
Source: "https://infos.by/markdown-v-nauke/",
|
||||||
|
Target: "https://brainbaking.com/post/2021/02/writing-academic-papers-in-markdown/",
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := repo.InModeration(wm, &mf.IndiewebData{
|
||||||
|
Source: wm.Source,
|
||||||
|
Target: wm.Target,
|
||||||
|
Name: "mytest",
|
||||||
|
})
|
||||||
|
assert.NotEmpty(t, repo.GetAllToModerate("brainbaking.com").Data)
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/admin/reject/{token}/{key}", HandleReject(cnf, repo)).Methods("GET")
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove("config.json")
|
||||||
|
ts.Close()
|
||||||
|
db.Purge()
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/reject/%s/%s", ts.URL, cnf.Token, key), nil)
|
||||||
|
|
||||||
|
_, err = client.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, repo.GetAllToModerate("brainbaking.com").Data)
|
||||||
|
assert.Empty(t, repo.GetAll("brainbaking.com").Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleApprove(t *testing.T) {
|
||||||
|
wm := mf.Mention{
|
||||||
|
Source: "https://infos.by/markdown-v-nauke/",
|
||||||
|
Target: "https://brainbaking.com/post/2021/02/writing-academic-papers-in-markdown/",
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := repo.InModeration(wm, &mf.IndiewebData{
|
||||||
|
Source: wm.Source,
|
||||||
|
Target: wm.Target,
|
||||||
|
Name: "mytest",
|
||||||
|
})
|
||||||
|
assert.NotEmpty(t, repo.GetAllToModerate("brainbaking.com").Data)
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// just using httptest.NewServer(r.HandleFunc("url", HandleApprove(...)) won't get the context vars into the mux
|
||||||
|
r.HandleFunc("/admin/approve/{token}/{key}", HandleApprove(cnf, repo)).Methods("GET")
|
||||||
|
ts := httptest.NewServer(r)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove("config.json")
|
||||||
|
ts.Close()
|
||||||
|
db.Purge()
|
||||||
|
})
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/admin/approve/%s/%s", ts.URL, cnf.Token, key), nil)
|
||||||
|
|
||||||
|
_, err = client.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, repo.GetAllToModerate("brainbaking.com").Data)
|
||||||
|
assert.NotEmpty(t, repo.GetAll("brainbaking.com").Data)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Refresh" content="3; url={{ .RedirectURL }}" />
|
||||||
|
<title>Go-Jamming moderation: {{ .Action }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>🥞 Go-Jamming Moderation done!</h1>
|
||||||
|
|
||||||
|
<h2><em>{{ .Action }}</em>: {{ .Item }}</h2>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p>Thanks 👍. You'll be redirected to the admin dashboard...</p>
|
||||||
|
<p>If you cannot wait, <a href="{{ .RedirectURL }}">please click here</a>.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -24,7 +24,8 @@ func (s *server) routes() {
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.domainAndTokenOnly(webmention.HandlePut(c, db))).Methods("PUT")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.domainAndTokenOnly(webmention.HandlePut(c, db))).Methods("PUT")
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.domainAndTokenOnly(webmention.HandleDelete(db))).Methods("DELETE")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.domainAndTokenOnly(webmention.HandleDelete(db))).Methods("DELETE")
|
||||||
|
|
||||||
s.router.HandleFunc("/admin/{domain}/{token}", s.domainAndTokenOnly(admin.HandleGet(db))).Methods("GET")
|
s.router.HandleFunc("/admin/{token}", s.authorizedOnly(admin.HandleGet(c, db))).Methods("GET")
|
||||||
|
s.router.HandleFunc("/admin/{domain}/{token}", s.domainAndTokenOnly(admin.HandleGetToApprove(db))).Methods("GET")
|
||||||
s.router.HandleFunc("/admin/approve/{token}/{key}", s.authorizedOnly(admin.HandleApprove(c, db))).Methods("GET")
|
s.router.HandleFunc("/admin/approve/{token}/{key}", s.authorizedOnly(admin.HandleApprove(c, db))).Methods("GET")
|
||||||
s.router.HandleFunc("/admin/reject/{token}/{key}", s.authorizedOnly(admin.HandleReject(c, db))).Methods("GET")
|
s.router.HandleFunc("/admin/reject/{token}/{key}", s.authorizedOnly(admin.HandleReject(c, db))).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ func TestHandleDelete(t *testing.T) {
|
||||||
|
|
||||||
ts := httptest.NewServer(HandleDelete(repo))
|
ts := httptest.NewServer(HandleDelete(repo))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
defer db.Purge()
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s?source=%s&target=%s", ts.URL, wm.Source, wm.Target), nil)
|
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s?source=%s&target=%s", ts.URL, wm.Source, wm.Target), nil)
|
||||||
|
@ -59,6 +60,8 @@ func TestHandleDelete(t *testing.T) {
|
||||||
func TestHandlePostWithInvalidUrlsShouldReturnBadRequest(t *testing.T) {
|
func TestHandlePostWithInvalidUrlsShouldReturnBadRequest(t *testing.T) {
|
||||||
ts := httptest.NewServer(HandlePost(cnf, repo))
|
ts := httptest.NewServer(HandlePost(cnf, repo))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
defer db.Purge()
|
||||||
|
|
||||||
res, err := http.PostForm(ts.URL, postWm("https://haha.be/woof/said/the/dog.txt", "https://pussies.nl/mycatjustthrewup/gottacleanup.html"))
|
res, err := http.PostForm(ts.URL, postWm("https://haha.be/woof/said/the/dog.txt", "https://pussies.nl/mycatjustthrewup/gottacleanup.html"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -72,6 +75,8 @@ func TestHandlePostWithInvalidUrlsShouldReturnBadRequest(t *testing.T) {
|
||||||
func TestHandlePostWithTestServer_Parallel(t *testing.T) {
|
func TestHandlePostWithTestServer_Parallel(t *testing.T) {
|
||||||
ts := httptest.NewServer(HandlePost(cnf, repo))
|
ts := httptest.NewServer(HandlePost(cnf, repo))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
defer db.Purge()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
|
|
@ -17,7 +17,6 @@ type Config struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
UtcOffset int `json:"utcOffset"`
|
UtcOffset int `json:"utcOffset"`
|
||||||
DataPath string `json:"dataPath"`
|
|
||||||
AllowedWebmentionSources []string `json:"allowedWebmentionSources"`
|
AllowedWebmentionSources []string `json:"allowedWebmentionSources"`
|
||||||
Blacklist []string `json:"blacklist"`
|
Blacklist []string `json:"blacklist"`
|
||||||
Whitelist []string `json:"whitelist"`
|
Whitelist []string `json:"whitelist"`
|
||||||
|
@ -103,9 +102,13 @@ func addToList(key string, arr []string) []string {
|
||||||
return append(arr, key)
|
return append(arr, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) String() string {
|
||||||
|
bytes, _ := json.MarshalIndent(c, "", " ")
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) Save() {
|
func (c *Config) Save() {
|
||||||
bytes, _ := json.Marshal(c) // we assume a correct internral state here
|
err := ioutil.WriteFile("config.json", []byte(c.String()), fs.ModePerm)
|
||||||
err := ioutil.WriteFile("config.json", bytes, fs.ModePerm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Unable to save config.json to disk!")
|
log.Err(err).Msg("Unable to save config.json to disk!")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue