Notes/schedule.go

290 lines
6.3 KiB
Go
Raw Permalink Normal View History

2024-03-28 21:49:48 +01:00
package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
2024-03-28 21:49:48 +01:00
// Standard
"encoding/json"
"fmt"
"regexp"
2024-03-30 17:43:21 +01:00
"strconv"
2024-03-28 21:49:48 +01:00
"strings"
"time"
)
func init() {
fmt.Printf("")
}
type Schedule struct {
2024-03-30 17:43:21 +01:00
ID int
UserID int `json:"user_id" db:"user_id"`
Node Node
ScheduleUUID string `db:"schedule_uuid"`
Time time.Time
RemindMinutes int `db:"remind_minutes"`
Description string
Acknowledged bool
2024-03-28 21:49:48 +01:00
}
2024-04-17 18:43:24 +02:00
func scheduleHandler() { // {{{
// Wait for the approximate minute.
wait := 60000 - time.Now().Sub(time.Now().Truncate(time.Minute)).Milliseconds()
logger.Info("schedule", "wait", wait/1000)
time.Sleep(time.Millisecond * time.Duration(wait))
tick := time.NewTicker(time.Minute)
for {
schedules := ExpiredSchedules()
for _, event := range schedules {
notificationManager.Send(
event.UserID,
event.ScheduleUUID,
[]byte(
fmt.Sprintf(
"%s\n%s",
event.Time.Format("2006-01-02 15:04"),
event.Description,
),
),
)
}
<-tick.C
}
} // }}}
2024-04-03 17:37:32 +02:00
func ScanForSchedules(timezone string, content string) (schedules []Schedule) { // {{{
2024-03-28 21:49:48 +01:00
schedules = []Schedule{}
2024-03-30 17:43:21 +01:00
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*(?:(\d+)\s*(h|min)\s*,)?\s*([^\]]+?)\s*\}`)
2024-03-28 21:49:48 +01:00
foundSchedules := rxp.FindAllStringSubmatch(content, -1)
for _, data := range foundSchedules {
// Missing seconds
if strings.Count(data[1], ":") == 1 {
data[1] = data[1] + ":00"
}
2024-04-03 17:37:32 +02:00
logger.Info("time", "parse", data[1])
location, err := time.LoadLocation(timezone)
if err != nil {
err = werr.Wrap(err).WithCode("002-00010")
return
2024-03-30 09:46:48 +01:00
}
2024-04-03 17:37:32 +02:00
timestamp, err := time.ParseInLocation("2006-01-02 15:04:05", data[1], location)
2024-03-28 21:49:48 +01:00
if err != nil {
continue
}
2024-03-30 17:43:21 +01:00
// Reminder
var remindMinutes int
if data[2] != "" && data[3] != "" {
value, _ := strconv.Atoi(data[2])
unit := strings.ToLower(data[3])
switch unit {
case "min":
remindMinutes = value
case "h":
remindMinutes = value * 60
}
}
2024-03-28 21:49:48 +01:00
schedule := Schedule{
2024-03-30 17:43:21 +01:00
Time: timestamp,
RemindMinutes: remindMinutes,
Description: data[4],
2024-03-28 21:49:48 +01:00
}
schedules = append(schedules, schedule)
}
return
2024-03-30 17:43:21 +01:00
} // }}}
func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error) { // {{{
2024-03-28 21:49:48 +01:00
schedules = []Schedule{}
res := service.Db.Conn.QueryRow(`
WITH schedule_events AS (
SELECT
2024-04-03 17:37:32 +02:00
s.id,
s.user_id,
json_build_object('id', s.node_id) AS node,
s.schedule_uuid,
(time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
s.description,
s.acknowledged
FROM schedule s
2024-04-17 18:43:24 +02:00
INNER JOIN _webservice.user u ON s.user_id = u.id
2024-03-28 21:49:48 +01:00
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 {
2024-04-03 17:37:32 +02:00
err = werr.Wrap(err).WithCode("002-000E")
2024-03-28 21:49:48 +01:00
return
}
err = json.Unmarshal(data, &schedules)
return
2024-03-30 17:43:21 +01:00
} // }}}
2024-03-30 17:43:21 +01:00
func (a Schedule) IsEqual(b Schedule) bool { // {{{
return a.UserID == b.UserID &&
a.Node.ID == b.Node.ID &&
a.Time.Equal(b.Time) &&
2024-04-17 18:43:24 +02:00
a.RemindMinutes == b.RemindMinutes &&
a.Description == b.Description
2024-03-30 17:43:21 +01:00
} // }}}
func (s *Schedule) Insert(queryable Queryable) error { // {{{
res := queryable.QueryRow(`
2024-03-30 17:43:21 +01:00
INSERT INTO schedule(user_id, node_id, time, remind_minutes, description)
VALUES($1, $2, $3, $4, $5)
RETURNING id
`,
s.UserID,
s.Node.ID,
2024-04-03 17:37:32 +02:00
s.Time.Format("2006-01-02 15:04:05"),
2024-03-30 17:43:21 +01:00
s.RemindMinutes,
s.Description,
)
2024-03-28 21:49:48 +01:00
2024-04-03 17:37:32 +02:00
err := res.Scan(&s.ID)
if err != nil {
err = werr.Wrap(err).WithCode("002-000D")
}
return err
2024-03-30 17:43:21 +01:00
} // }}}
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
2024-03-30 17:43:21 +01:00
} // }}}
2024-03-30 17:43:21 +01:00
func ExpiredSchedules() (schedules []Schedule) { // {{{
schedules = []Schedule{}
res, err := service.Db.Conn.Queryx(`
2024-03-28 21:49:48 +01:00
SELECT
2024-04-03 17:37:32 +02:00
s.id,
s.user_id,
s.node_id,
s.schedule_uuid,
(s.time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
s.description
FROM schedule s
2024-04-17 18:43:24 +02:00
INNER JOIN _webservice.user u ON s.user_id = u.id
2024-03-28 21:49:48 +01:00
WHERE
2024-04-17 18:43:24 +02:00
(time - MAKE_INTERVAL(mins => remind_minutes)) AT TIME ZONE u.timezone < NOW() AND
2024-03-28 21:49:48 +01:00
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
2024-03-30 17:43:21 +01:00
} // }}}
2024-04-19 18:32:37 +02:00
func FutureSchedules(userID int, nodeID int, start time.Time, end time.Time) (schedules []Schedule, err error) { // {{{
2024-04-03 17:37:32 +02:00
schedules = []Schedule{}
2024-04-19 18:32:37 +02:00
var foo string
row := service.Db.Conn.QueryRow(`SELECT TO_CHAR($1::date AT TIME ZONE 'UTC', 'yyyy-mm-dd HH24:MI')`, start)
err = row.Scan(&foo)
if err != nil {
return
}
logger.Info("FOO", "date", foo)
2024-04-05 08:59:59 +02:00
res := service.Db.Conn.QueryRow(`
WITH schedule_events AS (
SELECT
s.id,
s.user_id,
2024-04-17 18:43:24 +02:00
jsonb_build_object(
'id', n.id,
'name', n.name,
'updated', n.updated
) AS node,
2024-04-05 08:59:59 +02:00
s.schedule_uuid,
2024-04-19 15:35:11 +02:00
time AT TIME ZONE u.timezone AS time,
2024-04-05 08:59:59 +02:00
s.description,
2024-04-17 18:43:24 +02:00
s.acknowledged,
s.remind_minutes AS RemindMinutes
2024-04-05 08:59:59 +02:00
FROM schedule s
2024-04-17 18:43:24 +02:00
INNER JOIN _webservice.user u ON s.user_id = u.id
2024-04-05 08:59:59 +02:00
INNER JOIN node n ON s.node_id = n.id
WHERE
2024-04-17 18:43:24 +02:00
s.user_id = $1 AND
(
CASE
WHEN $2 > 0 THEN n.id = $2
ELSE true
END
2024-04-19 18:32:37 +02:00
) AND (
CASE WHEN TO_CHAR($3::date, 'yyyy-mm-dd HH24:MI') = '0001-01-01 00:00' THEN TRUE
ELSE (s.time AT TIME ZONE u.timezone) >= $3
END
) AND (
CASE WHEN TO_CHAR($4::date, 'yyyy-mm-dd HH24:MI') = '0001-01-01 00:00' THEN TRUE
ELSE (s.time AT TIME ZONE u.timezone) <= $4
END
2024-04-17 18:43:24 +02:00
) AND
2024-04-05 08:59:59 +02:00
time >= NOW() AND
NOT acknowledged
)
2024-04-03 17:37:32 +02:00
SELECT
2024-04-05 08:59:59 +02:00
COALESCE(jsonb_agg(s.*), '[]'::jsonb)
FROM schedule_events s
`,
2024-04-19 18:32:37 +02:00
userID,
nodeID,
start,
end,
2024-04-03 17:37:32 +02:00
)
2024-04-05 08:59:59 +02:00
var j []byte
err = res.Scan(&j)
2024-04-03 17:37:32 +02:00
if err != nil {
2024-04-05 08:59:59 +02:00
err = werr.Wrap(err).WithCode("002-000B").WithData(userID)
2024-04-03 17:37:32 +02:00
return
}
2024-04-05 08:59:59 +02:00
err = json.Unmarshal(j, &schedules)
if err != nil {
err = werr.Wrap(err).WithCode("002-0010").WithData(string(j)).Log()
return
2024-04-03 17:37:32 +02:00
}
2024-04-05 08:59:59 +02:00
2024-04-03 17:37:32 +02:00
return
2024-04-19 18:32:37 +02:00
} // }}}