gitoriaLog in with ident

gitoria

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit4a2d71254a2d7125initial commitmre4a2d7125/tickets.hl

4.3 KB

  1. // tickets.hl — THE TICKETS OF A REPO (ticket gitoria#10; CONCEPT "/tickets"). The tickets are NOT stored in
  2. // gitoria: they live in tickets.worldapi.org, in a project named after the repo. The project is made by
  3. // tickets itself the moment the first ticket is opened (its API creates a project with its first ticket), so
  4. // anyone who logs in to tickets.worldapi.org sees a repo's tickets like those of any tickets project.
  5. //
  6. // project name = <slug>.<host of the public url> (e.g. myrepo.gitoria.worldapi.org). A repo slug has no
  7. // dot, so it never meets another repo's project or one of the dotted app projects of the system.
  8. // read = GET <tickets>/api/projects/<project>/tickets (public; 404 = no ticket yet)
  9. // write = POST <tickets>/api/projects/<project>/tickets with gitoria's own API token: tickets takes the
  10. // author from the token (there is no author field), so the ticket says in its text who opened it here.
  11. // Config (environment, or `.env` beside project.hl — never printed):
  12. // GITORIA_TICKETS_URL default https://tickets.worldapi.org
  13. // GITORIA_TICKETS_TOKEN the API token (tkt_…) of the tickets user "gitoria" — without it the list still shows,
  14. // opening a ticket says it is not set up
  15. import { fetch } from 'hl:fetch'
  16. import { envOr, hostOnly, plainError } from './users.hl'
  17. static NL = "
  18. "
  19. static ticketsRaw = envOr('GITORIA_TICKETS_URL', 'https://tickets.worldapi.org')
  20. static ticketsUrl = ticketsRaw.endsWith('/') ? ticketsRaw.slice(0, ticketsRaw.length - 1) : ticketsRaw
  21. static ticketsToken = envOr('GITORIA_TICKETS_TOKEN', '')
  22. static maxRows = 200
  23. static ticketsProject = (slug) => { return slug + '.' + hostOnly }
  24. // one row for the page (a state's words are shown as tickets writes them)
  25. static viewOf = (t) => {
  26. return { number = t.number subject = t.subject state = t.state stateSlug = t.stateSlug created = t.created updated = t.updated url = ticketsUrl + t.href }
  27. }
  28. // the repo's tickets, newest update first (tickets' order): { tickets, count, truncated, project, url } or { error }
  29. static loadTickets = (slug) => {
  30. let project = ticketsProject(slug)
  31. let r = fetch(ticketsUrl + '/api/projects/' + project + '/tickets', { method = 'GET' headers = { 'user-agent' = 'gitoria.worldapi.org (tickets)' } timeoutMs = 10000 })
  32. if (r == null || r.status == null || r.status == 0) { return { error = 'tickets.worldapi.org did not answer' } }
  33. let base = { project = project url = ticketsUrl + '/projects/' + project }
  34. if (r.status == 404) { base.tickets = [] base.count = 0 base.truncated = false return base }
  35. let j = r.status == 200 ? r.json() : null
  36. if (j == null || j.tickets == null) { return { error = 'tickets.worldapi.org answered ' + r.status } }
  37. let out = []
  38. for (t of j.tickets) { if (out.length < maxRows) { out.push(viewOf(t)) } }
  39. base.tickets = out
  40. base.count = j.tickets.length
  41. base.truncated = j.tickets.length > out.length
  42. return base
  43. }
  44. // open a ticket for a repo as `name` (a display name): { ticket } or { error, field }
  45. static openTicket = (slug, name, subject, summary) => {
  46. let s = subject == null ? '' : ('' + subject).trim()
  47. if (s == '') { return { error = 'the ticket needs a subject' field = 'subject' } }
  48. let bad = plainError(s, 200, 'the subject')
  49. if (bad != null) { return { error = bad field = 'subject' } }
  50. let body = summary == null ? '' : ('' + summary).trim()
  51. if (body.length > 20000) { return { error = 'the text is too long (at most 20000 characters)' field = 'summary' } }
  52. if (ticketsToken == '') { return { error = 'opening tickets is not set up on this server (GITORIA_TICKETS_TOKEN missing)' field = '' } }
  53. let text = (body == '' ? '' : body + NL + NL) + '— opened by ' + name + ' in gitoria'
  54. let r = fetch(ticketsUrl + '/api/projects/' + ticketsProject(slug) + '/tickets', { method = 'POST' json = { subject = s summary = text } headers = { 'user-agent' = 'gitoria.worldapi.org (tickets)' 'authorization' = 'Bearer ' + ticketsToken } timeoutMs = 10000 })
  55. if (r == null || r.status == null || r.status == 0) { return { error = 'tickets.worldapi.org did not answer' field = '' } }
  56. let j = r.json()
  57. if (r.status != 201 || j == null || j.ticket == null) {
  58. let why = j != null && j.error != null ? ': ' + j.error : ''
  59. return { error = 'tickets.worldapi.org refused the ticket (' + r.status + why + ')' field = '' }
  60. }
  61. return { ticket = viewOf(j.ticket) }
  62. }

Branches

Latest commits

  • 4a2d7125initial commitmre