gitoria
All repositories: gitoria
4.3 KB
// tickets.hl — THE TICKETS OF A REPO (ticket gitoria#10; CONCEPT "/tickets"). The tickets are NOT stored in// gitoria: they live in tickets.worldapi.org, in a project named after the repo. The project is made by// tickets itself the moment the first ticket is opened (its API creates a project with its first ticket), so// anyone who logs in to tickets.worldapi.org sees a repo's tickets like those of any tickets project.//// project name = <slug>.<host of the public url> (e.g. myrepo.gitoria.worldapi.org). A repo slug has no// dot, so it never meets another repo's project or one of the dotted app projects of the system.// read = GET <tickets>/api/projects/<project>/tickets (public; 404 = no ticket yet)// write = POST <tickets>/api/projects/<project>/tickets with gitoria's own API token: tickets takes the// author from the token (there is no author field), so the ticket says in its text who opened it here.// Config (environment, or `.env` beside project.hl — never printed):// GITORIA_TICKETS_URL default https://tickets.worldapi.org// GITORIA_TICKETS_TOKEN the API token (tkt_…) of the tickets user "gitoria" — without it the list still shows,// opening a ticket says it is not set upimport { fetch } from 'hl:fetch'import { envOr, hostOnly, plainError } from './users.hl'static NL = ""static ticketsRaw = envOr('GITORIA_TICKETS_URL', 'https://tickets.worldapi.org')static ticketsUrl = ticketsRaw.endsWith('/') ? ticketsRaw.slice(0, ticketsRaw.length - 1) : ticketsRawstatic ticketsToken = envOr('GITORIA_TICKETS_TOKEN', '')static maxRows = 200static ticketsProject = (slug) => { return slug + '.' + hostOnly }// one row for the page (a state's words are shown as tickets writes them)static viewOf = (t) => {return { number = t.number subject = t.subject state = t.state stateSlug = t.stateSlug created = t.created updated = t.updated url = ticketsUrl + t.href }}// the repo's tickets, newest update first (tickets' order): { tickets, count, truncated, project, url } or { error }static loadTickets = (slug) => {let project = ticketsProject(slug)let r = fetch(ticketsUrl + '/api/projects/' + project + '/tickets', { method = 'GET' headers = { 'user-agent' = 'gitoria.worldapi.org (tickets)' } timeoutMs = 10000 })if (r == null || r.status == null || r.status == 0) { return { error = 'tickets.worldapi.org did not answer' } }let base = { project = project url = ticketsUrl + '/projects/' + project }if (r.status == 404) { base.tickets = [] base.count = 0 base.truncated = false return base }let j = r.status == 200 ? r.json() : nullif (j == null || j.tickets == null) { return { error = 'tickets.worldapi.org answered ' + r.status } }let out = []for (t of j.tickets) { if (out.length < maxRows) { out.push(viewOf(t)) } }base.tickets = outbase.count = j.tickets.lengthbase.truncated = j.tickets.length > out.lengthreturn base}// open a ticket for a repo as `name` (a display name): { ticket } or { error, field }static openTicket = (slug, name, subject, summary) => {let s = subject == null ? '' : ('' + subject).trim()if (s == '') { return { error = 'the ticket needs a subject' field = 'subject' } }let bad = plainError(s, 200, 'the subject')if (bad != null) { return { error = bad field = 'subject' } }let body = summary == null ? '' : ('' + summary).trim()if (body.length > 20000) { return { error = 'the text is too long (at most 20000 characters)' field = 'summary' } }if (ticketsToken == '') { return { error = 'opening tickets is not set up on this server (GITORIA_TICKETS_TOKEN missing)' field = '' } }let text = (body == '' ? '' : body + NL + NL) + '— opened by ' + name + ' in gitoria'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 })if (r == null || r.status == null || r.status == 0) { return { error = 'tickets.worldapi.org did not answer' field = '' } }let j = r.json()if (r.status != 201 || j == null || j.ticket == null) {let why = j != null && j.error != null ? ': ' + j.error : ''return { error = 'tickets.worldapi.org refused the ticket (' + r.status + why + ')' field = '' }}return { ticket = viewOf(j.ticket) }}
Branches
- mainmain branch
Latest commits
- 4a2d7125initial commitmre