Browse Source

Save notifications to DB, Notificationsadmin

master
Jan-Lukas Else 3 months ago
parent
commit
be929058cf
  1. 9
      databaseMigrations.go
  2. 9
      go.mod
  3. 19
      go.sum
  4. 9
      http.go
  5. 146
      notifications.go
  6. 37
      render.go
  7. 2
      templates/commentsadmin.gohtml
  8. 26
      templates/notificationsadmin.gohtml
  9. 1
      templates/strings/default.yaml

9
databaseMigrations.go

@ -130,6 +130,15 @@ func migrateDb() error {
return err
},
},
&migrator.Migration{
Name: "00011",
Func: func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table notifications (id integer primary key autoincrement, time integer not null, text text not null);
`)
return err
},
},
),
)
if err != nil {

9
go.mod

@ -43,18 +43,19 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.13
github.com/tdewolff/parse/v2 v2.5.11 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.2
github.com/yuin/goldmark-emoji v1.0.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

19
go.sum

@ -284,8 +284,9 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.13 h1:RrwQhgGoYBhKN/ezStGB+crU64wPK1ZE5Jmkl63lif0=
github.com/tdewolff/minify/v2 v2.9.13/go.mod h1:faNOp+awAoo+fhFHD+NAkBOaXBAvJI2X2SDERGKnARo=
github.com/tdewolff/parse/v2 v2.5.10 h1:vj35n+ljq8LuYUx436s4qB18wuwP7thrLv+t1syE39M=
github.com/tdewolff/parse/v2 v2.5.10/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/parse/v2 v2.5.11 h1:Wq0x026IKZh9GPUB5Fp+v5bki/SNmpIkdltcnm6HrO0=
github.com/tdewolff/parse/v2 v2.5.11/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -324,8 +325,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -370,8 +371,8 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -381,8 +382,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -404,8 +405,8 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=

9
http.go

@ -144,6 +144,15 @@ func buildHandler() (http.Handler, error) {
})
})
// Notifications
notificationsPath := "/notifications"
r.Route(notificationsPath, func(r chi.Router) {
r.Use(authMiddleware)
handler := notificationsAdmin(notificationsPath)
r.Get("/", handler)
r.Get(paginationPath, handler)
})
// Posts
pp, err := allPostPaths(statusPublished)
if err != nil {

146
notifications.go

@ -1,15 +1,157 @@
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"reflect"
"strconv"
"time"
"github.com/go-chi/chi"
"github.com/vcraescu/go-paginator"
)
type notification struct {
ID int
Time int64
Text string
}
func sendNotification(text string) {
log.Println("Notification:", text)
n := &notification{
Time: time.Now().Unix(),
Text: text,
}
if err := saveNotification(n); err != nil {
log.Println("Failed to save notification:", err.Error())
}
if appConfig.Notifications.Telegram.Enabled {
err := sendTelegramMessage(text, "", appConfig.Notifications.Telegram.BotToken, appConfig.Notifications.Telegram.ChatID)
err := sendTelegramMessage(n.Text, "", appConfig.Notifications.Telegram.BotToken, appConfig.Notifications.Telegram.ChatID)
if err != nil {
log.Println("Failed to send Telegram notification:", err.Error())
}
}
}
func saveNotification(n *notification) error {
if _, err := appDbExec("insert into notifications (time, text) values (@time, @text)", sql.Named("time", n.Time), sql.Named("text", n.Text)); err != nil {
return err
}
return nil
}
type notificationsRequestConfig struct {
offset, limit int
}
func buildNotificationsQuery(config *notificationsRequestConfig) (query string, args []interface{}) {
args = []interface{}{}
query = "select id, time, text from notifications order by id desc"
if config.limit != 0 || config.offset != 0 {
query += " limit @limit offset @offset"
args = append(args, sql.Named("limit", config.limit), sql.Named("offset", config.offset))
}
return
}
func getNotifications(config *notificationsRequestConfig) ([]*notification, error) {
notifications := []*notification{}
query, args := buildNotificationsQuery(config)
rows, err := appDbQuery(query, args...)
if err != nil {
return nil, err
}
for rows.Next() {
n := &notification{}
err = rows.Scan(&n.ID, &n.Time, &n.Text)
if err != nil {
return nil, err
}
notifications = append(notifications, n)
}
return notifications, nil
}
func countNotifications(config *notificationsRequestConfig) (count int, err error) {
query, params := buildNotificationsQuery(config)
query = "select count(*) from (" + query + ")"
row, err := appDbQueryRow(query, params...)
if err != nil {
return
}
err = row.Scan(&count)
return
}
type notificationsPaginationAdapter struct {
config *notificationsRequestConfig
nums int64
}
func (p *notificationsPaginationAdapter) Nums() (int64, error) {
if p.nums == 0 {
nums, _ := countNotifications(p.config)
p.nums = int64(nums)
}
return p.nums, nil
}
func (p *notificationsPaginationAdapter) Slice(offset, length int, data interface{}) error {
modifiedConfig := *p.config
modifiedConfig.offset = offset
modifiedConfig.limit = length
notifications, err := getNotifications(&modifiedConfig)
reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&notifications).Elem())
return err
}
func notificationsAdmin(notificationPath string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Adapter
pageNoString := chi.URLParam(r, "page")
pageNo, _ := strconv.Atoi(pageNoString)
p := paginator.New(&notificationsPaginationAdapter{config: &notificationsRequestConfig{}}, 10)
p.SetPage(pageNo)
var notifications []*notification
err := p.Results(&notifications)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
// Navigation
var hasPrev, hasNext bool
var prevPage, nextPage int
var prevPath, nextPath string
hasPrev, _ = p.HasPrev()
if hasPrev {
prevPage, _ = p.PrevPage()
} else {
prevPage, _ = p.Page()
}
if prevPage < 2 {
prevPath = notificationPath
} else {
prevPath = fmt.Sprintf("%s/page/%d", notificationPath, prevPage)
}
hasNext, _ = p.HasNext()
if hasNext {
nextPage, _ = p.NextPage()
} else {
nextPage, _ = p.Page()
}
nextPath = fmt.Sprintf("%s/page/%d", notificationPath, nextPage)
// Render
render(w, templateNotificationsAdmin, &renderData{
Data: map[string]interface{}{
"Notifications": notifications,
"HasPrev": hasPrev,
"HasNext": hasNext,
"Prev": slashIfEmpty(prevPath),
"Next": slashIfEmpty(nextPath),
},
})
}
}

37
render.go

@ -19,24 +19,27 @@ import (
"github.com/goodsign/monday"
)
const templatesDir = "templates"
const templatesExt = ".gohtml"
const (
templatesDir = "templates"
templatesExt = ".gohtml"
const templateBase = "base"
const templatePost = "post"
const templateError = "error"
const templateIndex = "index"
const templateTaxonomy = "taxonomy"
const templateSearch = "search"
const templateSummary = "summary"
const templatePhotosSummary = "photosummary"
const templateEditor = "editor"
const templateLogin = "login"
const templateStaticHome = "statichome"
const templateBlogStats = "blogstats"
const templateComment = "comment"
const templateCaptcha = "captcha"
const templateCommentsAdmin = "commentsadmin"
templateBase = "base"
templatePost = "post"
templateError = "error"
templateIndex = "index"
templateTaxonomy = "taxonomy"
templateSearch = "search"
templateSummary = "summary"
templatePhotosSummary = "photosummary"
templateEditor = "editor"
templateLogin = "login"
templateStaticHome = "statichome"
templateBlogStats = "blogstats"
templateComment = "comment"
templateCaptcha = "captcha"
templateCommentsAdmin = "commentsadmin"
templateNotificationsAdmin = "notificationsadmin"
)
var templates map[string]*template.Template
var templateFunctions template.FuncMap

2
templates/commentsadmin.gohtml

@ -13,7 +13,7 @@
Target: <a href="{{ $comment.Target }}" target="_blank">{{ $comment.Target }}</a><br/>
Name: {{ if $comment.Website }}<a href="{{ $comment.Website }}" target="_blank" rel="nofollow noopener noreferrer ugc">{{ $comment.Name }}</a>{{ else }}{{ $comment.Name }}{{ end }}
</p>
<p class="e-content">
<p>
{{ html $comment.Comment }}
</p>
<form method="post">

26
templates/notificationsadmin.gohtml

@ -0,0 +1,26 @@
{{ define "title" }}
<title>{{ string .Blog.Lang "notifications" }} - {{ .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
<h1>{{ string .Blog.Lang "notifications" }}</h1>
{{ range $i, $notification := .Data.Notifications }}
<p>
ID: {{ $notification.ID }}<br/>
Time: {{ unixtodate $notification.Time }}<br/>
Text: {{ $notification.Text }}
</p>
{{ end }}
{{ if .Data.HasPrev }}
<p><a href="{{ .Data.Prev }}">{{ string .Blog.Lang "prev" }}</a></p>
{{ end }}
{{ if .Data.HasNext }}
<p><a href="{{ .Data.Next }}">{{ string .Blog.Lang "next" }}</a></p>
{{ end }}
</main>
{{ end }}
{{ define "notificationsadmin" }}
{{ template "base" . }}
{{ end }}

1
templates/strings/default.yaml

@ -19,6 +19,7 @@ likeof: "Like of"
login: "Login"
nameopt: "Name (optional)"
next: "Next"
notifications: "Notifications"
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
password: "Password"
prev: "Previous"

Loading…
Cancel
Save