Compare commits

...

2 Commits

Author SHA1 Message Date
Magnus Åhall
49fd943110 Added timezone to user 2024-04-03 17:37:32 +02:00
Magnus Åhall
f9f083367e Fixed #1 2024-03-30 22:26:26 +01:00
5 changed files with 123 additions and 36 deletions

18
main.go
View File

@ -147,6 +147,7 @@ func main() { // {{{
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("/notification/ack", false, false, notificationAcknowledge)
service.Register("/schedule/list", true, true, scheduleList)
service.Register("/", false, false, service.StaticHandler) service.Register("/", false, false, service.StaticHandler)
if flagCreateUser { if flagCreateUser {
@ -777,5 +778,22 @@ func notificationAcknowledge(w http.ResponseWriter, r *http.Request, sess *sessi
"OK": true, "OK": true,
}) })
} // }}} } // }}}
func scheduleList(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
logger.Info("webserver", "request", "/schedule/list")
var err error
w.Header().Add("Access-Control-Allow-Origin", "*")
var schedules []Schedule
schedules, err = FutureSchedules(sess.UserID)
if err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"ScheduleEvents": schedules,
})
} // }}}
// vim: foldmethod=marker // vim: foldmethod=marker

11
node.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
// External // External
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
werr "git.gibonuddevalla.se/go/wrappederror"
// Standard // Standard
"time" "time"
@ -324,8 +325,16 @@ func CreateNode(userID, parentID int, name string) (node Node, err error) { // {
return return
} // }}} } // }}}
func UpdateNode(userID, nodeID, timeOffset int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{ func UpdateNode(userID, nodeID, timeOffset int, content string, cryptoKeyID int, markdown bool) (err error) { // {{{
var timezone string
row := service.Db.Conn.QueryRow(`SELECT timezone FROM public.user WHERE id=$1`, userID)
err = row.Scan(&timezone)
if err != nil {
err = werr.Wrap(err).WithCode("002-000F")
return
}
var scannedSchedules, dbSchedules, add, remove []Schedule var scannedSchedules, dbSchedules, add, remove []Schedule
scannedSchedules = ScanForSchedules(timeOffset, content) scannedSchedules = ScanForSchedules(timezone, content)
for i := range scannedSchedules { for i := range scannedSchedules {
scannedSchedules[i].Node.ID = nodeID scannedSchedules[i].Node.ID = nodeID
scannedSchedules[i].UserID = userID scannedSchedules[i].UserID = userID

View File

@ -37,7 +37,7 @@ func InitNotificationManager() (err error) {// {{{
user_id ASC, user_id ASC,
prio ASC prio ASC
) )
SELECT jsonb_agg(s.*) SELECT COALESCE(jsonb_agg(s.*), '[]')
FROM services s FROM services s
`, `,
) )

View File

@ -28,7 +28,7 @@ type Schedule struct {
Acknowledged bool Acknowledged bool
} }
func ScanForSchedules(timeOffset int, content string) (schedules []Schedule) { // {{{ func ScanForSchedules(timezone string, content string) (schedules []Schedule) { // {{{
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*(?:(\d+)\s*(h|min)\s*,)?\s*([^\]]+?)\s*\}`) 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*\}`)
@ -40,19 +40,14 @@ func ScanForSchedules(timeOffset int, content string) (schedules []Schedule) { /
data[1] = data[1] + ":00" data[1] = data[1] + ":00"
} }
// Timezone calculations logger.Info("time", "parse", data[1])
var timeTZ string location, err := time.LoadLocation(timezone)
if timeOffset < 0 { if err != nil {
hours := (-timeOffset) / 60 err = werr.Wrap(err).WithCode("002-00010")
mins := (-timeOffset) % 60 return
timeTZ = fmt.Sprintf("%s -%02d%02d", data[1], hours, mins)
} else {
hours := timeOffset / 60
mins := timeOffset % 60
timeTZ = fmt.Sprintf("%s +%02d%02d", data[1], hours, mins)
} }
timestamp, err := time.Parse("2006-01-02 15:04:05 -0700", timeTZ) timestamp, err := time.ParseInLocation("2006-01-02 15:04:05", data[1], location)
if err != nil { if err != nil {
continue continue
} }
@ -86,14 +81,15 @@ func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error)
res := service.Db.Conn.QueryRow(` res := service.Db.Conn.QueryRow(`
WITH schedule_events AS ( WITH schedule_events AS (
SELECT SELECT
id, s.id,
user_id, s.user_id,
json_build_object('id', node_id) AS node, json_build_object('id', s.node_id) AS node,
schedule_uuid, s.schedule_uuid,
time - MAKE_INTERVAL(mins => remind_minutes) AS time, (time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
description, s.description,
acknowledged s.acknowledged
FROM schedule FROM schedule s
INNER JOIN public.user u ON s.user_id = u.id
WHERE WHERE
user_id=$1 AND user_id=$1 AND
CASE CASE
@ -112,6 +108,7 @@ func RetrieveSchedules(userID int, nodeID int) (schedules []Schedule, err error)
var data []byte var data []byte
err = res.Scan(&data) err = res.Scan(&data)
if err != nil { if err != nil {
err = werr.Wrap(err).WithCode("002-000E")
return return
} }
@ -133,12 +130,16 @@ func (s *Schedule) Insert(queryable Queryable) error { // {{{
`, `,
s.UserID, s.UserID,
s.Node.ID, s.Node.ID,
s.Time, s.Time.Format("2006-01-02 15:04:05"),
s.RemindMinutes, s.RemindMinutes,
s.Description, s.Description,
) )
return res.Scan(&s.ID) err := res.Scan(&s.ID)
if err != nil {
err = werr.Wrap(err).WithCode("002-000D")
}
return err
} // }}} } // }}}
func (s *Schedule) Delete(queryable Queryable) error { // {{{ func (s *Schedule) Delete(queryable Queryable) error { // {{{
_, err := queryable.Exec(` _, err := queryable.Exec(`
@ -158,13 +159,14 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{
res, err := service.Db.Conn.Queryx(` res, err := service.Db.Conn.Queryx(`
SELECT SELECT
id, s.id,
user_id, s.user_id,
node_id, s.node_id,
schedule_uuid, s.schedule_uuid,
time - MAKE_INTERVAL(mins => remind_minutes) AS time, (s.time - MAKE_INTERVAL(mins => s.remind_minutes)) AT TIME ZONE u.timezone AS time,
description s.description
FROM schedule FROM schedule s
INNER JOIN public.user u ON s.user_id = u.id
WHERE WHERE
(time - MAKE_INTERVAL(mins => remind_minutes)) < NOW() AND (time - MAKE_INTERVAL(mins => remind_minutes)) < NOW() AND
NOT acknowledged NOT acknowledged
@ -187,3 +189,40 @@ func ExpiredSchedules() (schedules []Schedule) { // {{{
} }
return return
} // }}} } // }}}
func FutureSchedules(userID int) (schedules []Schedule, err error) {// {{{
schedules = []Schedule{}
res, err := service.Db.Conn.Queryx(`
SELECT
id,
user_id,
node_id,
schedule_uuid,
time,
description
FROM schedule
WHERE
user_id = $1 AND
time >= NOW() AND
NOT acknowledged
ORDER BY
time ASC
`,
userID,
)
if err != nil {
err = werr.Wrap(err).WithCode("002-000B")
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 {
err = werr.Wrap(err).WithCode("002-000C")
return
}
schedules = append(schedules, s)
}
return
}// }}}

View File

@ -59,6 +59,7 @@ export class NodeUI extends Component {
case 'node': case 'node':
if (node.ID == 0) { if (node.ID == 0) {
page = html` page = html`
<div style="cursor: pointer; color: #000; text-align: center;" onclick=${()=>this.page.value = 'schedule-events'}>Schedule events</div>
${children.length > 0 ? html`<div class="child-nodes">${children}</div><div id="notes-version">Notes version ${window._VERSION}</div>` : html``} ${children.length > 0 ? html`<div class="child-nodes">${children}</div><div id="notes-version">Notes version ${window._VERSION}</div>` : html``}
` `
} else { } else {
@ -97,10 +98,14 @@ export class NodeUI extends Component {
case 'search': case 'search':
page = html`<${Search} nodeui=${this} />` page = html`<${Search} nodeui=${this} />`
break break
case 'schedule-events':
page = html`<${ScheduleEventList} nodeui=${this} />`
break
} }
let menu = ()=> (this.menu.value ? html`<${Menu} nodeui=${this} />` : null) let menu = () => (this.menu.value ? html`<${Menu} nodeui=${this} />` : null)
let checklist = ()=> let checklist = () =>
html` html`
<div class="checklist" onclick=${evt => { evt.stopPropagation(); this.toggleChecklist() }}> <div class="checklist" onclick=${evt => { evt.stopPropagation(); this.toggleChecklist() }}>
<img src="/images/${window._VERSION}/${this.showChecklist() ? 'checklist-on.svg' : 'checklist-off.svg'}" /> <img src="/images/${window._VERSION}/${this.showChecklist() ? 'checklist-on.svg' : 'checklist-off.svg'}" />
@ -307,7 +312,7 @@ export class NodeUI extends Component {
this.node.value.ShowChecklist.value = !this.node.value.ShowChecklist.value this.node.value.ShowChecklist.value = !this.node.value.ShowChecklist.value
}//}}} }//}}}
toggleMarkdown() {//{{{ toggleMarkdown() {//{{{
this.node.value.RenderMarkdown.value = !this.node.value.RenderMarkdown.value this.node.value.RenderMarkdown.value = !this.node.value.RenderMarkdown.value
}//}}} }//}}}
} }
@ -618,7 +623,7 @@ export class Node {
initChecklist(checklistData) {//{{{ initChecklist(checklistData) {//{{{
if (checklistData === undefined || checklistData === null) if (checklistData === undefined || checklistData === null)
return return
this.ChecklistGroups = checklistData.map(groupData=>{ this.ChecklistGroups = checklistData.map(groupData => {
return new ChecklistGroup(groupData) return new ChecklistGroup(groupData)
}) })
}//}}} }//}}}
@ -778,7 +783,7 @@ class NodeProperties extends Component {
<div style="margin-bottom: 16px">These properties are only for this note.</div> <div style="margin-bottom: 16px">These properties are only for this note.</div>
<div class="checks"> <div class="checks">
<input type="checkbox" id="render-markdown" checked=${nodeui.node.value.Markdown} onchange=${evt=>nodeui.node.value.Markdown = evt.target.checked} /> <input type="checkbox" id="render-markdown" checked=${nodeui.node.value.Markdown} onchange=${evt => nodeui.node.value.Markdown = evt.target.checked} />
<label for="render-markdown">Markdown view</label> <label for="render-markdown">Markdown view</label>
</div> </div>
@ -959,4 +964,20 @@ class ProfileSettings extends Component {
}//}}} }//}}}
} }
class ScheduleEventList extends Component {
constructor() {
super()
this.retrieveFutureEvents()
}
render() {
}
retrieveFutureEvents() {
_app.current.request('/schedule/list')
.then(foo=>{
console.log(foo)
})
}
}
// vim: foldmethod=marker // vim: foldmethod=marker