gitoriaLog in with ident

tickets

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commite9d5c618e9d5c618mission 010 (code order) 2/4: one lib/ file per topic — store.hl split into projects / tickets (+ relations) / events / tickets-helpers, util.hl shared helpers (env, storage dir, URLs, sorts, Vienna time), the function routes out of project.hl into lib/api.hl (thin; auth/filters/Accept in api-helpers.hl), invite + member-removal logic out of the faces/routes into invites.hl / tickets.hl; project.hl is the map. /login/callback gets req + the session store by reference. gate 249/0, connect 60/0, real-data reads identicalmree9d5c618/project.hl

9.7 KB

  1. // project.hl — tickets.worldapi.org: THE MAP. Config, routes and WHO HEARS WHAT; the logic lives in lib/.
  2. //
  3. // WHERE THINGS ARE (one line per feature; README.md "Files" has the full list):
  4. // tickets, their states and relations (parent / blocked-by) lib/tickets.hl (state names, page shapes: lib/tickets-helpers.hl)
  5. // the history of a ticket (events) lib/events.hl
  6. // projects, members, roles, slugs lib/projects.hl
  7. // users, login through ident, API tokens, sessions lib/users.hl
  8. // invites to a project (ident#22) lib/invites.hl
  9. // connecting an app to a project (ticket #21) lib/connections.hl
  10. // Markdown in text fields / the Markdown read view lib/markdown.hl / lib/mdview.hl
  11. // the JSON API + /login/callback, /connect, /tickets/:ref lib/api.hl (plumbing: lib/api-helpers.hl, lib/jsoncheck.hl)
  12. // the ticket #20 data migration (every start) lib/migrate.hl
  13. // the ticket-file format of the import lib/import.hl (the import tool: tools/import.hl)
  14. // shared small helpers (env, storage dir, URLs, sorting, Vienna time) lib/util.hl
  15. // pages: shell, list, ticket, inbox, you, new project, settings, connect components/*.hl; CSS components/styles.hl
  16. import WebFramework from 'hl:web'
  17. import { env } from 'hl:proc'
  18. import Styles from './components/styles.hl'
  19. import { dark, darker } from './shared/tokens.hl'
  20. import { migrateAll } from './lib/migrate.hl'
  21. import { tagOf } from './lib/users.hl'
  22. import { postTicket, getTicket, postComment, postState, postEdit, postAssign, postParent, postBlockers, getProjects, getInbox, getProject, postMembers, postMemberRemove, postInvites, connectExchange, projectConnections, projectDisconnect, connectStart, projectTickets, getProjectTicket, postProjectComment, postProjectState, postProjectEdit, postProjectAssign, postProjectParent, postProjectBlockers, oldTicketPage, loginCallback } from './lib/api.hl'
  23. import TicketList from './components/ticket_list.hl'
  24. import Ticket from './components/ticket.hl'
  25. import Inbox from './components/inbox.hl'
  26. import You from './components/you.hl'
  27. import NewProject from './components/new_project.hl'
  28. import Settings from './components/settings.hl'
  29. import Connect from './components/connect.hl'
  30. static siteName = "tickets"
  31. appTitle = siteName
  32. styles = Styles
  33. // ---- THE INSTALLABLE APP (mission 046), the same way calendar.worldapi.org does it: hl:web's own web app manifest
  34. // (/__hl/manifest.webmanifest, linked from every head with the apple-touch-icon and theme-color) and service worker
  35. // (/__hl/sw.js) from these settings — no JavaScript of ours. Icons in icons/ (icon.svg, a ticket stub, is the source;
  36. // the PNGs and favicon.ico are rendered from it with rsvg-convert / magick, README "PWA"). The theme colour is the
  37. // header's background (darker, as tracker), the splash background the page's (dark) — both off the tokens, not written out again.
  38. appThemeColor = darker.value
  39. appBackgroundColor = dark.value
  40. appIcons = [
  41. { src = '/icons/icon-192.png' sizes = '192x192' purpose = 'any' }
  42. { src = '/icons/icon-512.png' sizes = '512x512' purpose = 'any' }
  43. { src = '/icons/icon-192.png' sizes = '192x192' purpose = 'maskable' }
  44. { src = '/icons/icon-512.png' sizes = '512x512' purpose = 'maskable' }
  45. ]
  46. appTouchIcon = '/icons/apple-touch-icon.png'
  47. appFavicon = '/icons/icon.svg'
  48. // OFFLINE: the shell and `/` — `/` IS the ticket list, so offline it shows the list as this browser last loaded it,
  49. // and the shell (components/main.hl) says "You are offline". Its value-form emits are never queued. Other pages are
  50. // not kept, except a project list (/projects/<slug>, same component) this browser visited; the rest get hl:web's
  51. // "Unavailable offline" page.
  52. offline = [ TicketList ]
  53. // ---- THE ROUTES: files, the function routes (lib/api.hl), the pages (components/) ----------------
  54. routes = [
  55. { pattern = "/favicon.ico" file = "./icons/favicon.ico" headers = { 'Cache-Control' = 'no-cache' } }
  56. // the installable app (mission 046): the icons (manifest and service worker are hl:web's own, from appIcons / offline)
  57. { pattern = "/icons/icon-192.png" file = "./icons/icon-192.png" headers = { 'Cache-Control' = 'no-cache' } }
  58. { pattern = "/icons/icon-512.png" file = "./icons/icon-512.png" headers = { 'Cache-Control' = 'no-cache' } }
  59. { pattern = "/icons/apple-touch-icon.png" file = "./icons/apple-touch-icon.png" headers = { 'Cache-Control' = 'no-cache' } }
  60. { pattern = "/icons/icon.svg" file = "./icons/icon.svg" headers = { 'Cache-Control' = 'no-cache' } }
  61. // the login button's return (ticket #7): it needs the request's session and the framework's session store, BY REFERENCE
  62. { pattern = "/login/callback" function = (route, req) => { return loginCallback(route, &req, &server.sessions) } }
  63. { pattern = "/login.js" file = "./login.js" headers = { 'Cache-Control' = 'no-cache' } }
  64. // ticket #12 (mission 024): the Markdown editor <md-editor>, vendored from worldapi-components
  65. { pattern = "/md-editor.js" file = "./shared/md-editor.js" headers = { 'Cache-Control' = 'no-cache' } }
  66. { pattern = "/api/tickets" function = postTicket }
  67. { pattern = "/api/tickets/:ref" function = getTicket }
  68. { pattern = "/api/tickets/:ref/comments" function = postComment }
  69. { pattern = "/api/tickets/:ref/state" function = postState }
  70. { pattern = "/api/tickets/:ref/edit" function = postEdit }
  71. { pattern = "/api/tickets/:ref/assign" function = postAssign }
  72. { pattern = "/api/tickets/:ref/parent" function = postParent }
  73. { pattern = "/api/tickets/:ref/blocked-by" function = postBlockers }
  74. { pattern = "/api/projects" function = getProjects }
  75. { pattern = "/api/inbox" function = getInbox }
  76. { pattern = "/api/projects/:slug" function = getProject }
  77. { pattern = "/api/projects/:slug/members" function = postMembers }
  78. { pattern = "/api/projects/:slug/members/remove" function = postMemberRemove }
  79. { pattern = "/api/projects/:slug/invites" function = postInvites }
  80. { pattern = "/api/connect/exchange" function = connectExchange }
  81. { pattern = "/api/projects/:slug/connections" function = projectConnections }
  82. { pattern = "/api/projects/:slug/connections/remove" function = projectDisconnect }
  83. { pattern = "/connect" function = connectStart }
  84. { pattern = "/api/projects/:slug/tickets" function = projectTickets }
  85. { pattern = "/api/projects/:slug/tickets/:number" function = getProjectTicket }
  86. { pattern = "/api/projects/:slug/tickets/:number/comments" function = postProjectComment }
  87. { pattern = "/api/projects/:slug/tickets/:number/state" function = postProjectState }
  88. { pattern = "/api/projects/:slug/tickets/:number/edit" function = postProjectEdit }
  89. { pattern = "/api/projects/:slug/tickets/:number/assign" function = postProjectAssign }
  90. { pattern = "/api/projects/:slug/tickets/:number/parent" function = postProjectParent }
  91. { pattern = "/api/projects/:slug/tickets/:number/blocked-by" function = postProjectBlockers }
  92. { pattern = "/" component = TicketList }
  93. { pattern = "/project/:projectName" component = TicketList }
  94. { pattern = "/projects/:projectName" component = TicketList }
  95. { pattern = "/state/:stateSlug" component = TicketList }
  96. { pattern = "/project/:projectName/state/:stateSlug" component = TicketList }
  97. { pattern = "/inbox" component = Inbox }
  98. { pattern = "/new-project" component = NewProject }
  99. { pattern = "/connect/:nonce" component = Connect }
  100. { pattern = "/projects/:projectSlug/settings" component = Settings }
  101. { pattern = "/you" component = You }
  102. { pattern = "/projects/:projectSlug/:ticketNumber" component = Ticket }
  103. // OLD PAGE URLS (`/tickets/<old global number>`, also `/tickets/<uuid>`) → 301 to `/projects/<slug>/<number>`
  104. { pattern = "/tickets/:ref" function = oldTicketPage }
  105. ]
  106. // WHO GETS THE PUSH: tickets and events are public (reading needs no login). A frame
  107. // reaches a connection only if a mounted component listens for it.
  108. // `signedIn` / `signedOut` (ticket #7) go to the tabs of ONE session: the one whose login
  109. // carries that random tag (session.data.tag, set at login) — every page of it switches to
  110. // logged in / out without a reload.
  111. audience = {
  112. ticketCreated = (row, session) => { return true }
  113. ticketEvent = (ticketId, ev, row, session) => { return true }
  114. // ticket #4: the relation views (tickets-helpers.hl relationViewOf) of a ticket and its related ones
  115. ticketsRelated = (views, session) => { return true }
  116. // ticket #21: an app was connected / disconnected — the project page refreshes its line
  117. connectionsChanged = (slug, session) => { return true }
  118. signedIn = (tag, info, session) => { return tag != null && tagOf(session) == tag }
  119. signedOut = (tag, session) => { return tag != null && tagOf(session) == tag }
  120. }
  121. sessionDir = env('TICKETS_SESSIONS') != null ? env('TICKETS_SESSIONS') : null
  122. port = env('TICKETS_PORT') != null ? toNumber(env('TICKETS_PORT')) : 8350
  123. // THE LISTENER's interface (mission 010, deploy to Byrodin): hl:web reads HL_HOST (or HOST)
  124. // itself (hybriel#24): 127.0.0.1 on Byrodin behind nginx; unset = 0.0.0.0 (dev on Loreana).
  125. // TICKETS_WATCH=0 turns the dev watcher off (the container: a deploy is an rsync + restart,
  126. // half-copied .hl files must not be re-analysed); unset = on, as before.
  127. watching = env('TICKETS_WATCH') != '0'
  128. // THE MIGRATION of ticket #20 (lib/migrate.hl): idempotent, runs at every start
  129. for (line of migrateAll()) { console.log('migrated: ' + line) }
  130. server = new WebFramework(routes = routes, styles = styles, minify = true, port = port, watchMode = watching)
  131. // THE GLOBAL ERROR HANDLER (ticket #7): a plugin's failure — above all hl:fetch's refused /
  132. // timed-out exchange with ident (users.hl exchangeCode, invites.hl) — arrives here instead of aborting the
  133. // request with a 500 that shows source paths; the failed call yields null and the caller answers
  134. // it ("ident did not answer"). Every absorbed error is logged.
  135. on Error(e) { console.log('error absorbed: ' + e.message) }

Branches

Latest commits

  • e9d5c618mission 010 (code order) 2/4: one lib/ file per topic — store.hl split into projects / tickets (+ relations) / events / tickets-helpers, util.hl shared helpers (env, storage dir, URLs, sorts, Vienna time), the function routes out of project.hl into lib/api.hl (thin; auth/filters/Accept in api-helpers.hl), invite + member-removal logic out of the faces/routes into invites.hl / tickets.hl; project.hl is the map. /login/callback gets req + the session store by reference. gate 249/0, connect 60/0, real-data reads identicalmre
  • 97e269b5mission 010 (code order) 1/4: .hl files out of the root — lib/ (store, users, connections, invites, migrate, markdown, mdview, import = ticketfile, util = localtime, jsoncheck, api-helpers = api), tools/import.hl, components/styles.hl; import paths only. gate 249/0, connect 60/0, real-data reads identicalmre
  • 38f9d10ftickets: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); gate 249/0, connect 60/0mre
  • d3db6139tickets: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 249/0, connect 60/0mre
  • bce182e3tickets: Hybriel master 7eea0d32 (#126 memory, #48 lambda copies its argument); migrate.hl lambdas take &logmre
  • 4137be0fantcolony#40: mission references point to the moved missionsmre
  • 9bfba36aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • c7bd2645tickets: Hybriel master 73267707 (#122 fixed); compactNow workaround removed (#110 covered)mre
  • 2ab91ee9tickets: gate checks rows appear once (session sync); re-vendor to ff51cf46 stopped on hybriel#122, stays 837fe120mre
  • e01c2b1dtickets#24: installable app (manifest, service worker, offline list), own icon; gate waits for the hello's pongmre
  • 752fbb7fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 38bdd5e4deploy.sh: never send .git or .gitignore to Byrodinmre
  • f12fa1bcState of 2026-09-27, before the move to gitoriamre