gitoriaLog in with ident

calendar

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Main branchmain2881119fcalendar mission 001 (4/4): code order — README layout + same-output test, STATUS, LOG, report; tests/letcount.py (sub-folders), tests/realdata-baseline.mjs, tests/realdata-compare.pymremain/lib/events.hl

5.0 KB

  1. // lib/events.hl — THE EVENTS (ticket calendar#2). One hl:mpackdb table, storage/mpackdb/events.db, pk @id, index owner.
  2. // A record IS a WorldAPI Event (https://worldapi.org/standards/event): the `$`-keys `$type $version $kind $title
  3. // $description $start $end $timezone $location $status $visibility $created $updated`, plus `owner` (the users @id)
  4. // and the @id. Appointment: `$start`/`$end` local wall-clock `YYYY-MM-DDTHH:MM:SS` in `$timezone`. All-day:
  5. // `$kind = 'allday'`, `$start` a date, `$end` the EXCLUSIVE end date (left out for one day). Every event is
  6. // `$visibility = 'private'`: every function takes the owner's user id and reaches only that user's events.
  7. // Every write to the table is here. Looked up in lib/events-helpers.hl: build (page fields → record), viewOf (record →
  8. // what the page shows), isLocalId, byStart. The default reminders are lib/settings.hl.
  9. import { MPackDB } from 'hl:mpackdb'
  10. import { timestamp } from 'hl:time'
  11. import { storageDir, countOfList } from './util.hl'
  12. import { build, viewOf, isLocalId, byStart } from './events-helpers.hl'
  13. import { defaultsOf } from './settings.hl'
  14. import { before } from '../components/month-view/dates.hl'
  15. import { describe, shift } from '../components/month-view/reminders.hl'
  16. import { serverToday } from '../components/month-view/clock.hl'
  17. static eventsTable = new MPackDB(file = storageDir + '/events.db', primaryKey = '@id', indexes = ['owner'])
  18. static ownEvent = (userId, id) => {
  19. if (id == null || hlTypeName(id) != 'String' || id == '') { return null }
  20. e = eventsTable.fetch(id)
  21. if (e == null || e.owner != userId) { return null }
  22. return e
  23. }
  24. // the owner's events that touch first..last (dates), by start
  25. static eventsBetween = (userId, first, last) => {
  26. found = eventsTable.find('owner', userId)
  27. n = countOfList(found)
  28. views = []
  29. let i = 0
  30. while (i < n) {
  31. let v = viewOf(found[i])
  32. if (!before(v.last, first) && !before(last, v.first)) { views.push(v) }
  33. i = i + 1
  34. }
  35. return byStart(views)
  36. }
  37. static realId = (userId, id) => {
  38. if (!isLocalId(id)) { return id }
  39. found = eventsTable.find('owner', userId)
  40. n = countOfList(found)
  41. let i = 0
  42. while (i < n) {
  43. if (found[i].ref == id) { return found[i].id }
  44. i = i + 1
  45. }
  46. return null
  47. }
  48. // create, or change when the fields name an event (real or temporary id)
  49. static saveEvent = (userId, b) => {
  50. if (b != null && hlTypeName(b) == 'Hybrid' && b.id != null && b.id != '') {
  51. id = realId(userId, b.id)
  52. if (id != null) { return changeEvent(userId, id, b) }
  53. if (!isLocalId(b.id)) { return changeEvent(userId, b.id, b) }
  54. }
  55. return createEvent(userId, b)
  56. }
  57. static createEvent = (userId, b) => {
  58. x = build(b)
  59. if (x.error != null) { return x }
  60. r = x.record
  61. t = timestamp()
  62. r['$created'] = t
  63. r['$updated'] = t
  64. r.owner = userId
  65. if (isLocalId(b.id)) { r.ref = b.id }
  66. let id = eventsTable.put(r)
  67. if (id == null) { return { error = 'could not store the event: ' + eventsTable.lastError() } }
  68. return { id = id }
  69. }
  70. static changeEvent = (userId, id, b) => {
  71. old = ownEvent(userId, id)
  72. if (old == null) { return { error = 'no such event' } }
  73. x = build(b)
  74. if (x.error != null) { return x }
  75. r = x.record
  76. r['$created'] = old['$created']
  77. r['$updated'] = timestamp()
  78. r.owner = userId
  79. if (old.ref != null) { r.ref = old.ref }
  80. r.id = id
  81. eventsTable.update(id, r)
  82. return { id = id }
  83. }
  84. static deleteEvent = (userId, given) => {
  85. id = realId(userId, given)
  86. old = ownEvent(userId, id)
  87. if (old == null) { return { error = 'no such event' } }
  88. eventsTable.delete(id)
  89. return { ok = true }
  90. }
  91. // ---- the reminders still to come ------------------------------------------------------------------------
  92. // the owner's events starting first..last with the reminders that apply (own, else the defaults); timed and all-day
  93. static upcoming = (userId, first, last) => {
  94. defaults = defaultsOf(userId)
  95. found = eventsTable.find('owner', userId)
  96. n = countOfList(found)
  97. out = []
  98. let i = 0
  99. while (i < n) {
  100. let v = viewOf(found[i])
  101. if (!before(v.first, first) && !before(last, v.first)) {
  102. let list = { id = v.id title = v.title allDay = v.allDay startDate = v.startDate startTime = v.startTime place = v.place zone = v.zone rows = v.custom ? v.rows : defaults }
  103. out.push(list)
  104. }
  105. i = i + 1
  106. }
  107. return out
  108. }
  109. // the events with the reminders that apply, from two days back to nine days ahead (what the browser plans its notifications from)
  110. static soonOf = (userId) => {
  111. t = serverToday()
  112. return upcoming(userId, shift(t, -2), shift(t, 9))
  113. }
  114. // ---- what the faces answer (components/calendar.hl) -----------------------------------------------------
  115. // the events of the six weeks on screen, the defaults (text and rows) and the reminders still to come
  116. static calendarView = (userId, first, last) => {
  117. return { events = eventsBetween(userId, first, last) defaults = describe(defaultsOf(userId)) soon = soonOf(userId) defaultRows = defaultsOf(userId) }
  118. }
  119. // the settings editor: the default reminders as rows, their text and the reminders still to come
  120. static settingsView = (userId) => {
  121. return { rows = defaultsOf(userId) defaults = describe(defaultsOf(userId)) soon = soonOf(userId) defaultRows = defaultsOf(userId) }
  122. }

Branches

Latest commits

  • 2881119fcalendar mission 001 (4/4): code order — README layout + same-output test, STATUS, LOG, report; tests/letcount.py (sub-folders), tests/realdata-baseline.mjs, tests/realdata-compare.pymre
  • 3b2a4cd0calendar mission 001 (3/4): code order — let only where reassigned (147 dropped incl. components/month-view; 110 left: 67 reassigned, 43 loop-bound); gates 78/0 (3 of 4 runs; 1 known 'next month' flake) + 18/0, live-data run = step 2mre
  • f5ce6b1dcalendar mission 001 (2/4): code order — topics, map, thin faces: lib/util.hl, lib/events(-helpers).hl (+calendarView/settingsView/soonOf), lib/settings.hl, lib/users.hl (+userOfLoginCode, tagOf), lib/api(-helpers).hl; project.hl = map; login route takes &req/&sessions (failed-login reason now kept); gates 78/0 + 18/0mre
  • c6fbd011calendar mission 001 (1/4): code order — files moved: lib/events.hl, lib/users.hl, components/styles.hl (imports only); gates 78/0 + 18/0, live-data run identicalmre
  • d6c59290calendar: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gates 78/0 + 18/0mre
  • 7bd0337ccalendar: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gates 78/0 + 18/0mre
  • ff41310ccalendar: Hybriel master 8efba065 (#126 memory, #48 lambda copy; audit: no & needed)mre
  • 14ba08c7antcolony#40: mission references point to the moved missionsmre
  • 99c73346antcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 76edaa62calendar: Hybriel master ff51cf46 (re-vendor round, static workaround removed)mre
  • 90a3fc2cdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 6722b72ddeploy.sh: never send .git or .gitignore to Byrodinmre
  • be099807State of 2026-09-27, before the move to gitoriamre