diff --git a/config.go b/config.go index 4860517..56fd2ff 100644 --- a/config.go +++ b/config.go @@ -28,7 +28,6 @@ type Config struct { Static string Upload string } - NotificationBaseURL string } Session struct { diff --git a/db.go b/db.go deleted file mode 100644 index 2046724..0000000 --- a/db.go +++ /dev/null @@ -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 -} diff --git a/go.mod b/go.mod index 59b3275..a0791a8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.21.0 require ( 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/gorilla/websocket v1.5.0 github.com/jmoiron/sqlx v1.3.5 diff --git a/go.sum b/go.sum index 6466326..bbaf4e1 100644 --- a/go.sum +++ b/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/webservice v0.2.2 h1:pmfeLa7c9pSPbuu6TuzcJ6yuVwdMLJ8SSPm1IkusThk= 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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= diff --git a/main.go b/main.go index b331f2a..eebe749 100644 --- a/main.go +++ b/main.go @@ -3,17 +3,14 @@ package main import ( // External "git.gibonuddevalla.se/go/webservice" - "git.gibonuddevalla.se/go/wrappederror" // Internal "git.gibonuddevalla.se/go/webservice/session" - "notes/notification" // Standard "crypto/md5" "embed" "encoding/hex" - "encoding/json" "flag" "fmt" "io" @@ -23,7 +20,6 @@ import ( "path/filepath" "strconv" "strings" - "time" ) const LISTEN_HOST = "0.0.0.0" @@ -35,14 +31,12 @@ var ( flagCheckLocal bool flagConfig string - service *webservice.Service - connectionManager ConnectionManager - notificationManager notification.Manager - static http.Handler - config Config - logger *slog.Logger - schedulers map[int]Schedule - VERSION string + service *webservice.Service + connectionManager ConnectionManager + static http.Handler + config Config + logger *slog.Logger + VERSION string //go:embed version sql/* embeddedSQL embed.FS @@ -51,7 +45,7 @@ var ( 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 sql, err = embeddedSQL.ReadFile(fmt.Sprintf("sql/%05d.sql", version)) if err != nil { @@ -59,27 +53,7 @@ func sqlProvider(dbname string, version int) (sql []byte, found bool) { // {{{ } found = true 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() { // {{{ version, _ := embeddedSQL.ReadFile("version") @@ -89,8 +63,6 @@ func init() { // {{{ opt.Level = slog.LevelDebug logger = slog.New(slog.NewJSONHandler(os.Stdout, &opt)) - schedulers = make(map[int]Schedule, 512) - configFilename := os.Getenv("HOME") + "/.config/notes.yaml" flag.IntVar(&flagPort, "port", 1371, "TCP port to listen on") flag.BoolVar(&flagVersion, "version", false, "Shows Notes version and exists") @@ -100,9 +72,6 @@ func init() { // {{{ flag.Parse() } // }}} func main() { // {{{ - WrappedError.Init() - WrappedError.SetLogCallback(logCallback) - var err error if flagVersion { @@ -146,7 +115,6 @@ func main() { // {{{ service.Register("/key/retrieve", true, true, keyRetrieve) service.Register("/key/create", true, true, keyCreate) service.Register("/key/counter", true, true, keyCounter) - service.Register("/notification/ack", false, false, notificationAcknowledge) service.Register("/", false, false, service.StaticHandler) if flagCreateUser { @@ -154,48 +122,45 @@ func main() { // {{{ 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() if err != nil { logger.Error("webserver", "error", err) os.Exit(1) } } // }}} -func scheduleHandler() { // {{{ - // Wait for the approximate minute. - //wait := 60000 - time.Now().Sub(time.Now().Truncate(time.Minute)).Milliseconds() - //time.Sleep(time.Millisecond * time.Duration(wait)) - tick := time.NewTicker(time.Minute) - tick = time.NewTicker(time.Second*5) - for { - <-tick.C - for _, event := range ExpiredSchedules() { - notificationManager.Send( - event.UserID, - event.ScheduleUUID, - []byte( - fmt.Sprintf( - "%s\n%s", - event.Time.Format("2006-01-02 15:04"), - event.Description, - ), - ), - ) - } + +/* +func userPassword(w http.ResponseWriter, r *http.Request) { // {{{ + var err error + var ok bool + var session Session + + if session, _, err = ValidateSession(r, true); err != nil { + responseError(w, err) + return } + + 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) { // {{{ 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 diff --git a/node.go b/node.go index 1868122..c65b4b4 100644 --- a/node.go +++ b/node.go @@ -6,7 +6,6 @@ import ( // Standard "time" - "database/sql" ) type ChecklistItem struct { @@ -324,69 +323,8 @@ func CreateNode(userID, parentID int, name string) (node Node, err error) { // { return } // }}} 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 { - _, err = tsx.Exec(` + _, err = service.Db.Conn.Exec(` UPDATE node SET content = '', @@ -407,7 +345,7 @@ func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bo markdown, ) } else { - _, err = tsx.Exec(` + _, err = service.Db.Conn.Exec(` UPDATE node SET content = $1, @@ -428,12 +366,6 @@ func UpdateNode(userID, nodeID int, content string, cryptoKeyID int, markdown bo markdown, ) } - if err != nil { - tsx.Rollback() - return - } - - err = tsx.Commit() return } // }}} diff --git a/notification/factory.go b/notification/factory.go deleted file mode 100644 index cc4d145..0000000 --- a/notification/factory.go +++ /dev/null @@ -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") -} diff --git a/notification/ntfy.go b/notification/ntfy.go deleted file mode 100644 index 3f1b2ef..0000000 --- a/notification/ntfy.go +++ /dev/null @@ -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 -} diff --git a/notification/pkg.go b/notification/pkg.go deleted file mode 100644 index 5732f31..0000000 --- a/notification/pkg.go +++ /dev/null @@ -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 -} diff --git a/notification_manager.go b/notification_manager.go deleted file mode 100644 index 7022af9..0000000 --- a/notification_manager.go +++ /dev/null @@ -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 -}// }}} diff --git a/schedule.go b/schedule.go deleted file mode 100644 index 8b0f9b6..0000000 --- a/schedule.go +++ /dev/null @@ -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 -}// }}} diff --git a/sql/00015.sql b/sql/00015.sql deleted file mode 100644 index c39cc4f..0000000 --- a/sql/00015.sql +++ /dev/null @@ -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 -); diff --git a/sql/00016.sql b/sql/00016.sql deleted file mode 100644 index 8a98bf1..0000000 --- a/sql/00016.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.schedule ADD CONSTRAINT schedule_event UNIQUE (user_id, node_id, "time", description); diff --git a/sql/00017.sql b/sql/00017.sql deleted file mode 100644 index 52009bf..0000000 --- a/sql/00017.sql +++ /dev/null @@ -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 -); diff --git a/version b/version index 53d1c14..1689437 100644 --- a/version +++ b/version @@ -1 +1 @@ -v22 +v21