Compare commits
No commits in common. "a6c94ac7ca8144db894d83d29fd1aa2ba0341a88" and "fd01e751e2c791ea827057961828250c032b8c38" have entirely different histories.
a6c94ac7ca
...
fd01e751e2
@ -28,7 +28,6 @@ type Config struct {
|
|||||||
Static string
|
Static string
|
||||||
Upload string
|
Upload string
|
||||||
}
|
}
|
||||||
NotificationBaseURL string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Session struct {
|
Session struct {
|
||||||
|
13
db.go
13
db.go
@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// Standard
|
|
||||||
"database/sql"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Queryable can take both a Db and a transaction
|
|
||||||
type Queryable interface {
|
|
||||||
Exec(string, ...any) (sql.Result, error)
|
|
||||||
Query(string, ...any) (*sql.Rows, error)
|
|
||||||
QueryRow(string, ...any) *sql.Row
|
|
||||||
}
|
|
1
go.mod
1
go.mod
@ -4,7 +4,6 @@ go 1.21.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
git.gibonuddevalla.se/go/webservice v0.2.2
|
git.gibonuddevalla.se/go/webservice v0.2.2
|
||||||
git.gibonuddevalla.se/go/wrappederror v0.3.3
|
|
||||||
github.com/google/uuid v1.5.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
|
2
go.sum
2
go.sum
@ -2,8 +2,6 @@ git.gibonuddevalla.se/go/dbschema v1.3.0 h1:HzFMR29tWfy/ibIjltTbIMI4inVktj/rh8bE
|
|||||||
git.gibonuddevalla.se/go/dbschema v1.3.0/go.mod h1:BNw3q/574nXbGoeWyK+tLhRfggVkw2j2aXZzrBKC3ig=
|
git.gibonuddevalla.se/go/dbschema v1.3.0/go.mod h1:BNw3q/574nXbGoeWyK+tLhRfggVkw2j2aXZzrBKC3ig=
|
||||||
git.gibonuddevalla.se/go/webservice v0.2.2 h1:pmfeLa7c9pSPbuu6TuzcJ6yuVwdMLJ8SSPm1IkusThk=
|
git.gibonuddevalla.se/go/webservice v0.2.2 h1:pmfeLa7c9pSPbuu6TuzcJ6yuVwdMLJ8SSPm1IkusThk=
|
||||||
git.gibonuddevalla.se/go/webservice v0.2.2/go.mod h1:3uBS6nLbK9qbuGzDls8MZD5Xr9ORY1Srbj6v06BIhws=
|
git.gibonuddevalla.se/go/webservice v0.2.2/go.mod h1:3uBS6nLbK9qbuGzDls8MZD5Xr9ORY1Srbj6v06BIhws=
|
||||||
git.gibonuddevalla.se/go/wrappederror v0.3.3 h1:pdIy3/daSY3zMmUr9PXW6ffIt8iYonOv64mgJBpKz+0=
|
|
||||||
git.gibonuddevalla.se/go/wrappederror v0.3.3/go.mod h1:j4w320Hk1wvhOPjUaK4GgLvmtnjUUM5yVu6JFO1OCSc=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
|
129
main.go
129
main.go
@ -3,17 +3,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
// External
|
// External
|
||||||
"git.gibonuddevalla.se/go/webservice"
|
"git.gibonuddevalla.se/go/webservice"
|
||||||
"git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
"git.gibonuddevalla.se/go/webservice/session"
|
"git.gibonuddevalla.se/go/webservice/session"
|
||||||
"notes/notification"
|
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -23,7 +20,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const LISTEN_HOST = "0.0.0.0"
|
const LISTEN_HOST = "0.0.0.0"
|
||||||
@ -35,14 +31,12 @@ var (
|
|||||||
flagCheckLocal bool
|
flagCheckLocal bool
|
||||||
flagConfig string
|
flagConfig string
|
||||||
|
|
||||||
service *webservice.Service
|
service *webservice.Service
|
||||||
connectionManager ConnectionManager
|
connectionManager ConnectionManager
|
||||||
notificationManager notification.Manager
|
static http.Handler
|
||||||
static http.Handler
|
config Config
|
||||||
config Config
|
logger *slog.Logger
|
||||||
logger *slog.Logger
|
VERSION string
|
||||||
schedulers map[int]Schedule
|
|
||||||
VERSION string
|
|
||||||
|
|
||||||
//go:embed version sql/*
|
//go:embed version sql/*
|
||||||
embeddedSQL embed.FS
|
embeddedSQL embed.FS
|
||||||
@ -51,7 +45,7 @@ var (
|
|||||||
staticFS embed.FS
|
staticFS embed.FS
|
||||||
)
|
)
|
||||||
|
|
||||||
func sqlProvider(dbname string, version int) (sql []byte, found bool) { // {{{
|
func sqlProvider(dbname string, version int) (sql []byte, found bool) {
|
||||||
var err error
|
var err error
|
||||||
sql, err = embeddedSQL.ReadFile(fmt.Sprintf("sql/%05d.sql", version))
|
sql, err = embeddedSQL.ReadFile(fmt.Sprintf("sql/%05d.sql", version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,27 +53,7 @@ func sqlProvider(dbname string, version int) (sql []byte, found bool) { // {{{
|
|||||||
}
|
}
|
||||||
found = true
|
found = true
|
||||||
return
|
return
|
||||||
} // }}}
|
}
|
||||||
func logCallback(e WrappedError.Error) { // {{{
|
|
||||||
now := time.Now()
|
|
||||||
out := struct {
|
|
||||||
Year int
|
|
||||||
Month int
|
|
||||||
Day int
|
|
||||||
Time string
|
|
||||||
Error any
|
|
||||||
}{now.Year(), int(now.Month()), now.Day(), now.Format("15:04:05"), e}
|
|
||||||
|
|
||||||
j, _ := json.Marshal(out)
|
|
||||||
file, err := os.OpenFile("/tmp/notes.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("log", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file.Write(j)
|
|
||||||
file.Write([]byte("\n"))
|
|
||||||
file.Close()
|
|
||||||
} // }}}
|
|
||||||
|
|
||||||
func init() { // {{{
|
func init() { // {{{
|
||||||
version, _ := embeddedSQL.ReadFile("version")
|
version, _ := embeddedSQL.ReadFile("version")
|
||||||
@ -89,8 +63,6 @@ func init() { // {{{
|
|||||||
opt.Level = slog.LevelDebug
|
opt.Level = slog.LevelDebug
|
||||||
logger = slog.New(slog.NewJSONHandler(os.Stdout, &opt))
|
logger = slog.New(slog.NewJSONHandler(os.Stdout, &opt))
|
||||||
|
|
||||||
schedulers = make(map[int]Schedule, 512)
|
|
||||||
|
|
||||||
configFilename := os.Getenv("HOME") + "/.config/notes.yaml"
|
configFilename := os.Getenv("HOME") + "/.config/notes.yaml"
|
||||||
flag.IntVar(&flagPort, "port", 1371, "TCP port to listen on")
|
flag.IntVar(&flagPort, "port", 1371, "TCP port to listen on")
|
||||||
flag.BoolVar(&flagVersion, "version", false, "Shows Notes version and exists")
|
flag.BoolVar(&flagVersion, "version", false, "Shows Notes version and exists")
|
||||||
@ -100,9 +72,6 @@ func init() { // {{{
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
} // }}}
|
} // }}}
|
||||||
func main() { // {{{
|
func main() { // {{{
|
||||||
WrappedError.Init()
|
|
||||||
WrappedError.SetLogCallback(logCallback)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if flagVersion {
|
if flagVersion {
|
||||||
@ -146,7 +115,6 @@ func main() { // {{{
|
|||||||
service.Register("/key/retrieve", true, true, keyRetrieve)
|
service.Register("/key/retrieve", true, true, keyRetrieve)
|
||||||
service.Register("/key/create", true, true, keyCreate)
|
service.Register("/key/create", true, true, keyCreate)
|
||||||
service.Register("/key/counter", true, true, keyCounter)
|
service.Register("/key/counter", true, true, keyCounter)
|
||||||
service.Register("/notification/ack", false, false, notificationAcknowledge)
|
|
||||||
service.Register("/", false, false, service.StaticHandler)
|
service.Register("/", false, false, service.StaticHandler)
|
||||||
|
|
||||||
if flagCreateUser {
|
if flagCreateUser {
|
||||||
@ -154,48 +122,45 @@ func main() { // {{{
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
go scheduleHandler()
|
|
||||||
|
|
||||||
if err = service.InitDatabaseConnection(); err != nil {
|
|
||||||
logger.Error("application", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = InitNotificationManager()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("application", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("webserver", "error", err)
|
logger.Error("webserver", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
func scheduleHandler() { // {{{
|
|
||||||
// Wait for the approximate minute.
|
/*
|
||||||
//wait := 60000 - time.Now().Sub(time.Now().Truncate(time.Minute)).Milliseconds()
|
func userPassword(w http.ResponseWriter, r *http.Request) { // {{{
|
||||||
//time.Sleep(time.Millisecond * time.Duration(wait))
|
var err error
|
||||||
tick := time.NewTicker(time.Minute)
|
var ok bool
|
||||||
tick = time.NewTicker(time.Second*5)
|
var session Session
|
||||||
for {
|
|
||||||
<-tick.C
|
if session, _, err = ValidateSession(r, true); err != nil {
|
||||||
for _, event := range ExpiredSchedules() {
|
responseError(w, err)
|
||||||
notificationManager.Send(
|
return
|
||||||
event.UserID,
|
|
||||||
event.ScheduleUUID,
|
|
||||||
[]byte(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"%s\n%s",
|
|
||||||
event.Time.Format("2006-01-02 15:04"),
|
|
||||||
event.Description,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req := struct {
|
||||||
|
CurrentPassword string
|
||||||
|
NewPassword string
|
||||||
|
}{}
|
||||||
|
if err = parseRequest(r, &req); err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = session.UpdatePassword(req.CurrentPassword, req.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData(w, map[string]interface{}{
|
||||||
|
"OK": true,
|
||||||
|
"CurrentPasswordOK": ok,
|
||||||
|
})
|
||||||
} // }}}
|
} // }}}
|
||||||
|
*/
|
||||||
|
|
||||||
func nodeTree(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
func nodeTree(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||||
logger.Info("webserver", "request", "/node/tree")
|
logger.Info("webserver", "request", "/node/tree")
|
||||||
@ -761,20 +726,4 @@ func keyCounter(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{
|
|||||||
})
|
})
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
|
|
||||||
func notificationAcknowledge(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
|
||||||
logger.Info("webserver", "request", "/notification/ack")
|
|
||||||
var err error
|
|
||||||
|
|
||||||
|
|
||||||
err = AcknowledgeNotification(r.URL.Query().Get("uuid"))
|
|
||||||
if err != nil {
|
|
||||||
responseError(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responseData(w, map[string]interface{}{
|
|
||||||
"OK": true,
|
|
||||||
})
|
|
||||||
} // }}}
|
|
||||||
// vim: foldmethod=marker
|
// vim: foldmethod=marker
|
||||||
|
72
node.go
72
node.go
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"time"
|
"time"
|
||||||
"database/sql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChecklistItem struct {
|
type ChecklistItem struct {
|
||||||
@ -324,69 +323,8 @@ func CreateNode(userID, parentID int, name string) (node Node, err error) { // {
|
|||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{
|
func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{
|
||||||
var scannedSchedules, dbSchedules, add, remove []Schedule
|
|
||||||
scannedSchedules = ScanForSchedules(content)
|
|
||||||
for i := range scannedSchedules {
|
|
||||||
scannedSchedules[i].Node.ID = nodeID
|
|
||||||
scannedSchedules[i].UserID = userID
|
|
||||||
}
|
|
||||||
|
|
||||||
var tsx *sql.Tx
|
|
||||||
tsx, err = service.Db.Conn.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dbSchedules, err = RetrieveSchedules(userID, nodeID)
|
|
||||||
if err != nil {
|
|
||||||
tsx.Rollback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, scanned := range scannedSchedules {
|
|
||||||
found := false
|
|
||||||
for _, db := range dbSchedules {
|
|
||||||
if scanned.IsEqual(db) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
add = append(add, scanned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, db := range dbSchedules {
|
|
||||||
found := false
|
|
||||||
for _, scanned := range scannedSchedules {
|
|
||||||
if db.IsEqual(scanned) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
remove = append(remove, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range remove {
|
|
||||||
err = event.Delete(tsx)
|
|
||||||
if err != nil {
|
|
||||||
tsx.Rollback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range add {
|
|
||||||
err = event.Insert(tsx)
|
|
||||||
if err != nil {
|
|
||||||
tsx.Rollback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cryptoKeyID > 0 {
|
if cryptoKeyID > 0 {
|
||||||
_, err = tsx.Exec(`
|
_, err = service.Db.Conn.Exec(`
|
||||||
UPDATE node
|
UPDATE node
|
||||||
SET
|
SET
|
||||||
content = '',
|
content = '',
|
||||||
@ -407,7 +345,7 @@ func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bo
|
|||||||
markdown,
|
markdown,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
_, err = tsx.Exec(`
|
_, err = service.Db.Conn.Exec(`
|
||||||
UPDATE node
|
UPDATE node
|
||||||
SET
|
SET
|
||||||
content = $1,
|
content = $1,
|
||||||
@ -428,12 +366,6 @@ func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bo
|
|||||||
markdown,
|
markdown,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
tsx.Rollback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tsx.Commit()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServiceFactory(t string, config []byte, prio int, ackURL string) (Service, error) {
|
|
||||||
switch t {
|
|
||||||
case "NTFY":
|
|
||||||
return NewNTFY(config, prio, ackURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, werr.New("Unknown notification service, '%s'", t).WithCode("002-0000")
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Standard
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NTFY struct {
|
|
||||||
URL string
|
|
||||||
Prio int
|
|
||||||
AcknowledgeURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNTFY(config []byte, prio int, ackURL string) (instance NTFY, err error) {
|
|
||||||
err = json.Unmarshal(config, &instance)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0001").WithData(config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
instance.Prio = prio
|
|
||||||
instance.AcknowledgeURL = ackURL
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ntfy NTFY) GetPrio() int {
|
|
||||||
return ntfy.Prio
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ntfy NTFY) Send(uuid string, msg []byte) (err error) {
|
|
||||||
var req *http.Request
|
|
||||||
var res *http.Response
|
|
||||||
req, err = http.NewRequest("POST", ntfy.URL, bytes.NewReader(msg))
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0002").WithData(ntfy.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ackURL := fmt.Sprintf("http, OK, %s/notification/ack?uuid=%s", ntfy.AcknowledgeURL, uuid)
|
|
||||||
req.Header.Add("X-Actions", ackURL)
|
|
||||||
|
|
||||||
res, err = http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0003")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := io.ReadAll(res.Body)
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
err = werr.New("Invalid NTFY response").WithCode("002-0004").WithData(body)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ntfyResp := struct {
|
|
||||||
ID string
|
|
||||||
}{}
|
|
||||||
err = json.Unmarshal(body, &ntfyResp)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0005").WithData(body)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Standard
|
|
||||||
_ "fmt"
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service interface {
|
|
||||||
GetPrio() int
|
|
||||||
Send(string, []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
services map[int][]Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager() (nm Manager) {
|
|
||||||
nm.services = make(map[int][]Service, 32)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *Manager) AddService(userID int, service Service) {
|
|
||||||
var services []Service
|
|
||||||
var found bool
|
|
||||||
if services, found = nm.services[userID]; !found {
|
|
||||||
services = []Service{}
|
|
||||||
}
|
|
||||||
|
|
||||||
services = append(services, service)
|
|
||||||
slices.SortFunc(services, func(a, b Service) int {
|
|
||||||
if a.GetPrio() < b.GetPrio() {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if a.GetPrio() > b.GetPrio() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
nm.services[userID] = services
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *Manager) Send(userID int, uuid string, msg []byte) (err error) {
|
|
||||||
services, found := nm.services[userID]
|
|
||||||
if !found {
|
|
||||||
return werr.New("No notification services defined for user ID %d", userID).WithCode("002-0008")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, service := range services {
|
|
||||||
if err = service.Send(uuid, msg); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Internal
|
|
||||||
"notes/notification"
|
|
||||||
|
|
||||||
// Standard
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DbNotificationService struct {
|
|
||||||
ID int
|
|
||||||
UserID int `json:"user_id"`
|
|
||||||
Service string
|
|
||||||
Configuration string
|
|
||||||
Prio int
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitNotificationManager() (err error) {// {{{
|
|
||||||
var dbServices []DbNotificationService
|
|
||||||
var row *sql.Row
|
|
||||||
|
|
||||||
row = service.Db.Conn.QueryRow(`
|
|
||||||
WITH services AS (
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
prio,
|
|
||||||
service,
|
|
||||||
configuration::varchar
|
|
||||||
FROM notification n
|
|
||||||
ORDER BY
|
|
||||||
user_id ASC,
|
|
||||||
prio ASC
|
|
||||||
)
|
|
||||||
SELECT jsonb_agg(s.*)
|
|
||||||
FROM services s
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
var dbData []byte
|
|
||||||
err = row.Scan(&dbData)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0006")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(dbData, &dbServices)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0007")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager = notification.NewManager()
|
|
||||||
var service notification.Service
|
|
||||||
for _, dbService := range dbServices {
|
|
||||||
service, err = notification.ServiceFactory(
|
|
||||||
dbService.Service,
|
|
||||||
[]byte(dbService.Configuration),
|
|
||||||
dbService.Prio,
|
|
||||||
config.Application.NotificationBaseURL,
|
|
||||||
)
|
|
||||||
notificationManager.AddService(dbService.UserID, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}// }}}
|
|
||||||
|
|
||||||
func AcknowledgeNotification(uuid string) (err error) {// {{{
|
|
||||||
_, err = service.Db.Conn.Exec(`UPDATE schedule SET acknowledged=true WHERE schedule_uuid=$1`, uuid)
|
|
||||||
return
|
|
||||||
}// }}}
|
|
160
schedule.go
160
schedule.go
@ -1,160 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Standard
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
fmt.Printf("")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Schedule struct {
|
|
||||||
ID int
|
|
||||||
UserID int `json:"user_id" db:"user_id"`
|
|
||||||
Node Node
|
|
||||||
ScheduleUUID string `db:"schedule_uuid"`
|
|
||||||
Time time.Time
|
|
||||||
Description string
|
|
||||||
Acknowledged bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScanForSchedules(content string) (schedules []Schedule) {// {{{
|
|
||||||
schedules = []Schedule{}
|
|
||||||
|
|
||||||
rxp := regexp.MustCompile(`\{\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(?::[0-9]{2})?)\s*\,\s*([^\]]+?)\s*\}`)
|
|
||||||
foundSchedules := rxp.FindAllStringSubmatch(content, -1)
|
|
||||||
|
|
||||||
for _, data := range foundSchedules {
|
|
||||||
// Missing seconds
|
|
||||||
if strings.Count(data[1], ":") == 1 {
|
|
||||||
data[1] = data[1] + ":00"
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp, err := time.Parse("2006-01-02 15:04:05", data[1])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule := Schedule{
|
|
||||||
Time: timestamp,
|
|
||||||
Description: data[2],
|
|
||||||
}
|
|
||||||
schedules = append(schedules, schedule)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}// }}}
|
|
||||||
func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error) {// {{{
|
|
||||||
schedules = []Schedule{}
|
|
||||||
|
|
||||||
res := service.Db.Conn.QueryRow(`
|
|
||||||
WITH schedule_events AS (
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
json_build_object('id', node_id) AS node,
|
|
||||||
schedule_uuid,
|
|
||||||
time::timestamptz,
|
|
||||||
description,
|
|
||||||
acknowledged
|
|
||||||
FROM schedule
|
|
||||||
WHERE
|
|
||||||
user_id=$1 AND
|
|
||||||
CASE
|
|
||||||
WHEN $2 > 0 THEN node_id = $2
|
|
||||||
ELSE true
|
|
||||||
END
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
COALESCE(jsonb_agg(s.*), '[]'::jsonb)
|
|
||||||
FROM schedule_events s
|
|
||||||
`,
|
|
||||||
userID,
|
|
||||||
nodeID,
|
|
||||||
)
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
err = res.Scan(&data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &schedules)
|
|
||||||
return
|
|
||||||
}// }}}
|
|
||||||
|
|
||||||
func (a Schedule) IsEqual(b Schedule) bool {// {{{
|
|
||||||
return a.UserID == b.UserID &&
|
|
||||||
a.Node.ID == b.Node.ID &&
|
|
||||||
a.Time.Equal(b.Time) &&
|
|
||||||
a.Description == b.Description
|
|
||||||
}// }}}
|
|
||||||
func (s *Schedule) Insert(queryable Queryable) error {// {{{
|
|
||||||
res := queryable.QueryRow(`
|
|
||||||
INSERT INTO schedule(user_id, node_id, time, description)
|
|
||||||
VALUES($1, $2, $3, $4)
|
|
||||||
RETURNING id
|
|
||||||
`,
|
|
||||||
s.UserID,
|
|
||||||
s.Node.ID,
|
|
||||||
s.Time,
|
|
||||||
s.Description,
|
|
||||||
)
|
|
||||||
|
|
||||||
return res.Scan(&s.ID)
|
|
||||||
}// }}}
|
|
||||||
func (s *Schedule) Delete(queryable Queryable) error {// {{{
|
|
||||||
_, err := queryable.Exec(`
|
|
||||||
DELETE FROM schedule
|
|
||||||
WHERE
|
|
||||||
user_id = $1 AND
|
|
||||||
id = $2
|
|
||||||
`,
|
|
||||||
s.UserID,
|
|
||||||
s.ID,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}// }}}
|
|
||||||
|
|
||||||
func ExpiredSchedules() (schedules []Schedule) {// {{{
|
|
||||||
schedules = []Schedule{}
|
|
||||||
|
|
||||||
res, err := service.Db.Conn.Queryx(`
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
node_id,
|
|
||||||
schedule_uuid,
|
|
||||||
time,
|
|
||||||
description
|
|
||||||
FROM schedule
|
|
||||||
WHERE
|
|
||||||
time < NOW() AND
|
|
||||||
NOT acknowledged
|
|
||||||
ORDER BY
|
|
||||||
time ASC
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithCode("002-0009")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer res.Close()
|
|
||||||
|
|
||||||
for res.Next() {
|
|
||||||
s := Schedule{}
|
|
||||||
if err = res.Scan(&s.ID, &s.UserID, &s.Node.ID, &s.ScheduleUUID, &s.Time, &s.Description); err != nil {
|
|
||||||
werr.Wrap(err).WithCode("002-000a")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
schedules = append(schedules, s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}// }}}
|
|
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE public.schedule (
|
|
||||||
id SERIAL NOT NULL,
|
|
||||||
user_id INT4 NOT NULL,
|
|
||||||
node_id INT4 NOT NULL,
|
|
||||||
schedule_uuid CHAR(36) DEFAULT GEN_RANDOM_UUID() NOT NULL,
|
|
||||||
"time" TIMESTAMP NOT NULL,
|
|
||||||
description VARCHAR DEFAULT '' NOT NULL,
|
|
||||||
acknowledged BOOL DEFAULT false NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT schedule_pk PRIMARY KEY (id),
|
|
||||||
CONSTRAINT schedule_uuid UNIQUE (schedule_uuid),
|
|
||||||
CONSTRAINT schedule_node_fk FOREIGN KEY (node_id) REFERENCES public.node(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
CONSTRAINT schedule_user_fk FOREIGN KEY (user_id) REFERENCES "_webservice"."user"(id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
@ -1 +0,0 @@
|
|||||||
ALTER TABLE public.schedule ADD CONSTRAINT schedule_event UNIQUE (user_id, node_id, "time", description);
|
|
@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE public.notification (
|
|
||||||
id SERIAl NOT NULL,
|
|
||||||
user_id INT4 NOT NULL,
|
|
||||||
service VARCHAR DEFAULT 'NTFY' NOT NULL,
|
|
||||||
"configuration" JSONB DEFAULT '{}' NOT NULL,
|
|
||||||
prio INT DEFAULT 0 NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT notification_pk PRIMARY KEY (id),
|
|
||||||
CONSTRAINT notification_unique UNIQUE (user_id,prio),
|
|
||||||
CONSTRAINT notification_user_fk FOREIGN KEY (user_id) REFERENCES "_webservice"."user"(id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
Loading…
Reference in New Issue
Block a user