File size: 2,827 Bytes
6f91ce5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import type { LinkKind } from '../shared/ids.ts'
import type { PaperStatus } from '../shared/paperCard.ts'

// The board service's REST API (T08) from the browser: JSON in and out, the member ID as the
// bearer (decision D5). Routes and shapes are in the root README.

export class ApiError extends Error {
	status: number
	code: string
	constructor(status: number, code: string, message: string) {
		super(message)
		this.status = status
		this.code = code
	}
}

export interface Member {
	memberId: string
	displayName: string
	lastSeenSeq: number
	joinedAt: string
}

/** `GET /boards/:id`, the fields the browser reads. */
export interface Board {
	id: string
	title: string
	question: string
	context: string
	/** A member id, or "demo:<board>" on a seeded demo board. */
	createdBy?: string | null
	seedCount: number
	paperCount: number
	/** The newest event's seq: what `seen` marks as read. */
	lastSeq: number
	agentActive: boolean
	/** For the caller: events since they last looked ("Catch me up"). */
	newCount?: number
}

export interface VoteResult {
	status: PaperStatus
	keepBy: string[]
	discardBy: string[]
}

async function call<T>(method: string, path: string, memberId: string, body?: unknown): Promise<T> {
	const res = await fetch(`/api/v1${path}`, {
		method,
		headers: { authorization: `Bearer ${memberId}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
		body: body === undefined ? undefined : JSON.stringify(body),
	})
	if (res.status === 204) return undefined as T
	const data = await res.json().catch(() => null)
	if (!res.ok) throw new ApiError(res.status, data?.error?.code ?? 'error', data?.error?.message ?? `The board service answered ${res.status}`)
	return data as T
}

/** One board's routes, as one member. */
export function boardApi(boardId: string, memberId: string) {
	const board = `/boards/${encodeURIComponent(boardId)}`
	return {
		get: () => call<Board>('GET', board, memberId),
		/** Everything up to `seq` counts as seen: Home's "Catch me up" count starts over. */
		seen: (seq: number) => call<void>('POST', `${board}/seen`, memberId, { seq }),
		/** Join the board; with a name, also set the name the others see. */
		join: (displayName?: string) => call<Member>('PUT', `${board}/members/me`, memberId, { displayName }),
		/** Keep, Discard, or (null) take the vote back; the card updates for everyone through sync. */
		vote: (paperId: string, decision: 'keep' | 'discard' | null) =>
			call<VoteResult>('PUT', `${board}/papers/${encodeURIComponent(paperId)}/vote`, memberId, { decision }),
		/** A link a person deleted: get_board lists it in removedLinks and link_papers never draws it again (T14). */
		removeLink: (from: string, to: string, kind: LinkKind) =>
			call<void>('PUT', `${board}/removed-links`, memberId, { from, to, kind }),
	}
}