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/lib/migrate.hl

5.0 KB

  1. // lib/migrate.hl — THE MIGRATION OF TICKET #20 (projects as own data, members, new states). Statics only.
  2. // Idempotent: it looks at the SHAPE of the data, so a second run (and every start of the app, project.hl
  3. // calls migrateAll) changes nothing. What it does to a store from before:
  4. // 1. a project record `{ name, created }` becomes `{ title = name, slug = name, description = '', oldSlugs = '' }`
  5. // 2. every ticket points at its project by the project's id (`project`, `path` = '<id>/<number>'), not by name;
  6. // `createdBy` = the user of its 'created' event; the state gets its new name (awaiting creator → review,
  7. // a "Question…" ticket → pending; confirmed → done, rejected → reopened, in progress → progress, on hold →
  8. // pending, anything unknown → open); a ticket in review / pending is ASSIGNED to the creator's user
  9. // 3. the state events of the history get the new names too
  10. // 4. a project without members gets: the creator (TICKETS_CREATOR_IDENTITY, users.hl) as admin; the users
  11. // named Architect and AntColonyScheduler that wrote in it as edit; everybody else who wrote in it as use.
  12. // A project with no admin at all gets the creator as admin as soon as the creator is known.
  13. // TICKETS_CREATOR_IDENTITY is read ONLY here (the creator-only workflow is gone).
  14. // The one file that writes the tables of other topics (projects, members, tickets, events) directly: a data migration.
  15. import { now } from 'hl:time'
  16. import { merged, countOf } from './util.hl'
  17. import { ensureCreatorUser, userRecord } from './users.hl'
  18. import { projectsTable, membersTable, addMember, projectRecord, projectBySlug, projectRecords, adminCount } from './projects.hl'
  19. import { stateOf } from './tickets-helpers.hl'
  20. import { eventsTable } from './events.hl'
  21. import { ticketsTable } from './tickets.hl'
  22. static botNames = ['Architect' 'AntColonyScheduler']
  23. static migrateProjects = (&log) => {
  24. let all = projectsTable.find(null, null)
  25. if (countOf(all) == 0) { return 0 }
  26. let n = 0
  27. for (p of all) {
  28. if (p.slug == null) {
  29. projectsTable.update(p.id, { id = p.id title = p.name slug = p.name description = '' oldSlugs = '' created = p.created })
  30. n = n + 1
  31. }
  32. }
  33. if (n > 0) { log.push(n + ' project(s) turned into records') }
  34. return n
  35. }
  36. static createdByOf = (t) => {
  37. let all = eventsTable.find('ticket', t.id)
  38. if (countOf(all) == 0) { return '' }
  39. for (e of all) { if (e.kind == 'created' && e.user != null) { return e.user } }
  40. return ''
  41. }
  42. static migrateTickets = (&log, creator) => {
  43. let all = ticketsTable.find(null, null)
  44. if (countOf(all) == 0) { return 0 }
  45. let n = 0
  46. for (t of all) {
  47. if (projectRecord(t.project) == null) {
  48. let p = projectBySlug(t.project)
  49. if (p == null) {
  50. let pid = projectsTable.put({ title = t.project slug = t.project description = '' oldSlugs = '' created = now() })
  51. p = projectRecord(pid)
  52. }
  53. let s = stateOf(t.state)
  54. if (t.state == 'awaiting creator' && t.subject != null && t.subject.startsWith('Question')) { s = 'pending' }
  55. if (s == null) { s = 'open' }
  56. let who = ''
  57. if ((s == 'review' || s == 'pending') && creator != null) { who = creator.id }
  58. ticketsTable.update(t.id, merged(t, { project = p.id path = p.id + '/' + t.number state = s createdBy = createdByOf(t) assignee = who }))
  59. n = n + 1
  60. }
  61. }
  62. if (n > 0) { log.push(n + ' ticket(s) linked to their project by id, states renamed') }
  63. return n
  64. }
  65. static migrateEvents = (&log) => {
  66. let all = eventsTable.find(null, null)
  67. if (countOf(all) == 0) { return 0 }
  68. let n = 0
  69. for (e of all) {
  70. if (e.kind == 'state') {
  71. let a = stateOf(e.from)
  72. let b = stateOf(e.to)
  73. let nf = a != null ? a : e.from
  74. let nt = b != null ? b : e.to
  75. if (nf != e.from || nt != e.to) {
  76. eventsTable.update(e.id, merged(e, { from = nf to = nt }))
  77. n = n + 1
  78. }
  79. }
  80. }
  81. if (n > 0) { log.push(n + ' state event(s) renamed') }
  82. return n
  83. }
  84. static migrateMembers = (&log, creator) => {
  85. let n = 0
  86. for (p of projectRecords()) {
  87. if (countOf(membersTable.find('project', p.id)) == 0) {
  88. // who wrote in the project: users @id → true
  89. let wrote = {}
  90. let order = []
  91. let tickets = ticketsTable.find('project', p.id)
  92. if (countOf(tickets) > 0) {
  93. for (t of tickets) {
  94. let evs = eventsTable.find('ticket', t.id)
  95. if (countOf(evs) > 0) {
  96. for (e of evs) {
  97. if (e.user != null && e.user != '' && wrote[e.user] != true) {
  98. wrote[e.user] = true
  99. order.push(e.user)
  100. }
  101. }
  102. }
  103. }
  104. }
  105. for (uid of order) {
  106. let u = userRecord(uid)
  107. if (u != null) { addMember(p.id, uid, botNames.includes(u.name) ? 'edit' : 'use') }
  108. }
  109. n = n + 1
  110. }
  111. if (creator != null && adminCount(p.id) == 0) {
  112. addMember(p.id, creator.id, 'admin')
  113. n = n + 1
  114. }
  115. }
  116. if (n > 0) { log.push('members set up in ' + n + ' project(s)') }
  117. return n
  118. }
  119. // answers the lines describing what changed (an empty list = nothing to do)
  120. static migrateAll = () => {
  121. let log = []
  122. let creator = ensureCreatorUser()
  123. migrateProjects(log)
  124. migrateTickets(log, creator)
  125. migrateEvents(log)
  126. migrateMembers(log, creator)
  127. return log
  128. }

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