tickets
All repositories: gitoria
4.8 KB
// migrate.hl — THE MIGRATION OF TICKET #20 (projects as own data, members, new states). Statics only.// Idempotent: it looks at the SHAPE of the data, so a second run (and every start of the app, project.hl// calls migrateAll) changes nothing. What it does to a store from before:// 1. a project record `{ name, created }` becomes `{ title = name, slug = name, description = '', oldSlugs = '' }`// 2. every ticket points at its project by the project's id (`project`, `path` = '<id>/<number>'), not by name;// `createdBy` = the user of its 'created' event; the state gets its new name (awaiting creator → review,// a "Question…" ticket → pending; confirmed → done, rejected → reopened, in progress → progress, on hold →// pending, anything unknown → open); a ticket in review / pending is ASSIGNED to the creator's user// 3. the state events of the history get the new names too// 4. a project without members gets: the creator (TICKETS_CREATOR_IDENTITY, users.hl) as admin; the users// named Architect and AntColonyScheduler that wrote in it as edit; everybody else who wrote in it as use.// A project with no admin at all gets the creator as admin as soon as the creator is known.// TICKETS_CREATOR_IDENTITY is read ONLY here (the creator-only workflow is gone).import { projectsTable, membersTable, ticketsTable, eventsTable, addMember, projectRecord, projectBySlug, projectRecords, memberRows, adminCount, stateOf, merged, countOf, first, roleOf } from './store.hl'import { ensureCreatorUser, usersTable } from './users.hl'import { now } from 'hl:time'static botNames = ['Architect' 'AntColonyScheduler']static migrateProjects = (log) => {let all = projectsTable.find(null, null)if (countOf(all) == 0) { return 0 }let n = 0for (p of all) {if (p.slug == null) {projectsTable.update(p.id, { id = p.id title = p.name slug = p.name description = '' oldSlugs = '' created = p.created })n = n + 1}}if (n > 0) { log.push(n + ' project(s) turned into records') }return n}static createdByOf = (t) => {let all = eventsTable.find('ticket', t.id)if (countOf(all) == 0) { return '' }for (e of all) { if (e.kind == 'created' && e.user != null) { return e.user } }return ''}static migrateTickets = (log, creator) => {let all = ticketsTable.find(null, null)if (countOf(all) == 0) { return 0 }let n = 0for (t of all) {if (projectRecord(t.project) == null) {let p = projectBySlug(t.project)if (p == null) {let pid = projectsTable.put({ title = t.project slug = t.project description = '' oldSlugs = '' created = now() })p = projectRecord(pid)}let s = stateOf(t.state)if (t.state == 'awaiting creator' && t.subject != null && t.subject.startsWith('Question')) { s = 'pending' }if (s == null) { s = 'open' }let who = ''if ((s == 'review' || s == 'pending') && creator != null) { who = creator.id }ticketsTable.update(t.id, merged(t, { project = p.id path = p.id + '/' + t.number state = s createdBy = createdByOf(t) assignee = who }))n = n + 1}}if (n > 0) { log.push(n + ' ticket(s) linked to their project by id, states renamed') }return n}static migrateEvents = (log) => {let all = eventsTable.find(null, null)if (countOf(all) == 0) { return 0 }let n = 0for (e of all) {if (e.kind == 'state') {let a = stateOf(e.from)let b = stateOf(e.to)let nf = a != null ? a : e.fromlet nt = b != null ? b : e.toif (nf != e.from || nt != e.to) {eventsTable.update(e.id, merged(e, { from = nf to = nt }))n = n + 1}}}if (n > 0) { log.push(n + ' state event(s) renamed') }return n}static migrateMembers = (log, creator) => {let n = 0for (p of projectRecords()) {if (countOf(membersTable.find('project', p.id)) == 0) {// who wrote in the project: users @id → truelet wrote = {}let order = []let tickets = ticketsTable.find('project', p.id)if (countOf(tickets) > 0) {for (t of tickets) {let evs = eventsTable.find('ticket', t.id)if (countOf(evs) > 0) {for (e of evs) {if (e.user != null && e.user != '' && wrote[e.user] != true) {wrote[e.user] = trueorder.push(e.user)}}}}}for (uid of order) {let u = usersTable.fetch(uid)if (u != null) { addMember(p.id, uid, botNames.includes(u.name) ? 'edit' : 'use') }}n = n + 1}if (creator != null && adminCount(p.id) == 0) {addMember(p.id, creator.id, 'admin')n = n + 1}}if (n > 0) { log.push('members set up in ' + n + ' project(s)') }return n}// answers the lines describing what changed (an empty list = nothing to do)static migrateAll = () => {let log = []let creator = ensureCreatorUser()migrateProjects(log)migrateTickets(log, creator)migrateEvents(log)migrateMembers(log, creator)return log}
Branches
- mainmain branch
Latest commits
- 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