bee688e574
- app.md - auth.go - chat.go - chat.log - db.go - frontend/package.json - frontend/pnpm-lock.yaml - frontend/src/App.tsx - frontend/src/Root.tsx - frontend/src/api.ts - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
174 lines
4.4 KiB
TypeScript
174 lines
4.4 KiB
TypeScript
import type {
|
|
Board,
|
|
Card,
|
|
CardHistoryResponse,
|
|
Column,
|
|
Metrics,
|
|
MetricsFilter,
|
|
User,
|
|
} from "./types";
|
|
|
|
const BASE = "/api";
|
|
|
|
export class HTTPError extends Error {
|
|
constructor(public status: number, message: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
async function fetchJSON<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
credentials: "include",
|
|
...init,
|
|
headers: { "Content-Type": "application/json", ...(init?.headers || {}) },
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ Message: res.statusText }));
|
|
throw new HTTPError(res.status, err.Message || err.message || res.statusText);
|
|
}
|
|
if (res.status === 204) return undefined as T;
|
|
return res.json();
|
|
}
|
|
|
|
export function getBoard(): Promise<Board> {
|
|
return fetchJSON("/board");
|
|
}
|
|
|
|
export function createColumn(name: string): Promise<Column> {
|
|
return fetchJSON("/columns", { method: "POST", body: JSON.stringify({ name }) });
|
|
}
|
|
|
|
export interface UpdateColumnInput {
|
|
name?: string;
|
|
position?: number;
|
|
location?: "board" | "sidebar";
|
|
width?: number;
|
|
wip_limit?: number;
|
|
is_done?: boolean;
|
|
}
|
|
|
|
export function updateColumn(id: string, patch: UpdateColumnInput): Promise<void> {
|
|
return fetchJSON(`/columns/${id}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(patch),
|
|
});
|
|
}
|
|
|
|
export function deleteColumn(id: string): Promise<void> {
|
|
return fetchJSON(`/columns/${id}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function reorderColumns(ids: string[]): Promise<void> {
|
|
return fetchJSON("/columns/reorder", { method: "POST", body: JSON.stringify({ ids }) });
|
|
}
|
|
|
|
export interface CreateCardInput {
|
|
column_id: string;
|
|
requester?: string;
|
|
title: string;
|
|
description?: string;
|
|
assignee_id?: string | null;
|
|
}
|
|
|
|
export function createCard(input: CreateCardInput): Promise<Card> {
|
|
return fetchJSON("/cards", { method: "POST", body: JSON.stringify(input) });
|
|
}
|
|
|
|
export interface UpdateCardInput {
|
|
requester?: string;
|
|
title?: string;
|
|
description?: string;
|
|
color?: string;
|
|
locked?: boolean;
|
|
assignee_id?: string | null;
|
|
}
|
|
|
|
export function updateCard(id: string, patch: UpdateCardInput): Promise<void> {
|
|
return fetchJSON(`/cards/${id}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
}
|
|
|
|
export function deleteCard(id: string): Promise<void> {
|
|
return fetchJSON(`/cards/${id}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function listTrash(): Promise<Card[]> {
|
|
return fetchJSON("/trash");
|
|
}
|
|
|
|
export function restoreCard(id: string): Promise<void> {
|
|
return fetchJSON(`/cards/${id}/restore`, { method: "POST" });
|
|
}
|
|
|
|
export function purgeCard(id: string): Promise<void> {
|
|
return fetchJSON(`/cards/${id}/purge`, { method: "DELETE" });
|
|
}
|
|
|
|
export function moveCard(id: string, column_id: string, ordered_ids: string[]): Promise<void> {
|
|
return fetchJSON(`/cards/${id}/move`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ column_id, ordered_ids }),
|
|
});
|
|
}
|
|
|
|
export function cardHistory(id: string): Promise<CardHistoryResponse> {
|
|
return fetchJSON(`/cards/${id}/history`);
|
|
}
|
|
|
|
export interface ChatMessage {
|
|
role: "user" | "assistant";
|
|
content: string;
|
|
}
|
|
|
|
export interface ChatToolCall {
|
|
tool: string;
|
|
ok: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
export interface ChatResponse {
|
|
role: "assistant";
|
|
content: string;
|
|
board_changed: boolean;
|
|
tool_calls?: ChatToolCall[];
|
|
}
|
|
|
|
export function sendChat(messages: ChatMessage[]): Promise<ChatResponse> {
|
|
return fetchJSON("/chat", { method: "POST", body: JSON.stringify({ messages }) });
|
|
}
|
|
|
|
export function login(username: string, password: string): Promise<User> {
|
|
return fetchJSON("/auth/login", {
|
|
method: "POST",
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
}
|
|
|
|
export function register(username: string, password: string, display_name?: string): Promise<User> {
|
|
return fetchJSON("/auth/register", {
|
|
method: "POST",
|
|
body: JSON.stringify({ username, password, display_name }),
|
|
});
|
|
}
|
|
|
|
export function logout(): Promise<void> {
|
|
return fetchJSON("/auth/logout", { method: "POST" });
|
|
}
|
|
|
|
export function getMe(): Promise<User> {
|
|
return fetchJSON("/me");
|
|
}
|
|
|
|
export function listUsers(): Promise<User[]> {
|
|
return fetchJSON("/users");
|
|
}
|
|
|
|
export function getMetrics(f: MetricsFilter): Promise<Metrics> {
|
|
const qs = new URLSearchParams();
|
|
if (f.from) qs.set("from", f.from);
|
|
if (f.to) qs.set("to", f.to);
|
|
if (f.assignee_id) qs.set("assignee_id", f.assignee_id);
|
|
if (f.requester) qs.set("requester", f.requester);
|
|
const q = qs.toString();
|
|
return fetchJSON(`/metrics${q ? `?${q}` : ""}`);
|
|
}
|