Patchpass V1
Deploy / Build (push) Successful in 28s
Deploy / Test & Lint (push) Failing after 29s
Deploy / Build and Push Docker Image (push) Has been skipped

This commit is contained in:
Space-Banane
2026-07-18 20:14:44 +02:00
commit 7e05dd918c
101 changed files with 15183 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
import type {
Agent,
AdminUser,
AuditLog,
ChangeRequest,
GlobalSettings,
Notification,
RequestState,
User,
} from "./types";
export class ApiError extends Error {
status: number;
data?: unknown;
constructor(message: string, status: number, data?: unknown) {
super(message);
this.status = status;
this.data = data;
}
}
async function api<T = unknown>(path: string, opts: RequestInit = {}): Promise<T> {
const res = await fetch(path, {
credentials: "include",
headers:
opts.body && !(opts.headers && "Content-Type" in opts.headers)
? { "Content-Type": "application/json", ...(opts.headers || {}) }
: opts.headers,
...opts,
});
let body: any = null;
try {
body = await res.json();
} catch {
/* no body */
}
if (!res.ok || body?.status === "FAILED") {
throw new ApiError(body?.message || `Request failed (${res.status})`, res.status, body?.data);
}
return (body?.data ?? body) as T;
}
// ── Auth ──────────────────────────────────────────────────────────────────────
export const auth = {
me: () => api<User>("/api/auth/me"),
login: (username: string, password: string, totp?: string) =>
api<User>("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password, totp }) }),
register: (username: string, display_name: string, password: string) =>
api<User>("/api/auth/register", {
method: "POST",
body: JSON.stringify({ username, display_name, password }),
}),
logout: () => api("/api/auth/logout", { method: "POST" }),
setup2fa: () => api<{ secret: string; otpauth_url: string }>("/api/auth/2fa/setup", { method: "POST" }),
enable2fa: (totp: string) => api("/api/auth/2fa/enable", { method: "POST", body: JSON.stringify({ totp }) }),
disable2fa: (password: string) =>
api("/api/auth/2fa/disable", { method: "POST", body: JSON.stringify({ password }) }),
};
// ── Account ────────────────────────────────────────────────────────────────────
export const account = {
updateProfile: (display_name: string) =>
api("/api/account/profile", { method: "PATCH", body: JSON.stringify({ display_name }) }),
changePassword: (current_password: string, new_password: string) =>
api("/api/account/password", {
method: "POST",
body: JSON.stringify({ current_password, new_password }),
}),
updateSettings: (settings: { auto_delete_enabled?: boolean; auto_delete_days?: number }) =>
api<{ auto_delete_enabled: boolean; auto_delete_days: number }>("/api/account/settings", {
method: "PATCH",
body: JSON.stringify(settings),
}),
deleteAccount: (password: string) =>
api("/api/account", { method: "DELETE", body: JSON.stringify({ password, confirm: "DELETE" }) }),
exportUrl: "/api/account/export",
};
// ── Agents ─────────────────────────────────────────────────────────────────────
export const agents = {
list: () => api<Agent[]>("/api/agents"),
create: (input: Partial<Agent>) => api<Agent>("/api/agents", { method: "POST", body: JSON.stringify(input) }),
update: (id: number, input: Partial<Agent>) =>
api<Agent>(`/api/agents/${id}`, { method: "PATCH", body: JSON.stringify(input) }),
regenerateKey: (id: number) => api<Agent>(`/api/agents/${id}/regenerate-key`, { method: "POST" }),
setDisabled: (id: number, disabled: boolean) =>
api<Agent>(`/api/agents/${id}/disabled`, { method: "POST", body: JSON.stringify({ disabled }) }),
remove: (id: number) => api(`/api/agents/${id}`, { method: "DELETE" }),
};
// ── Change requests (human) ──────────────────────────────────────────────────────
export const requests = {
list: (params: { page?: number; page_size?: number; state?: string; agent_id?: number } = {}) => {
const q = new URLSearchParams();
if (params.page) q.set("page", String(params.page));
if (params.page_size) q.set("page_size", String(params.page_size));
if (params.state) q.set("state", params.state);
if (params.agent_id) q.set("agent_id", String(params.agent_id));
return api<{ page: number; page_size: number; total: number; requests: ChangeRequest[] }>(
`/api/change-requests?${q.toString()}`,
);
},
summary: () => api<{ counts: Record<RequestState, number> }>("/api/change-requests/summary"),
get: (id: string) => api<ChangeRequest>(`/api/change-requests/${id}`),
decide: (id: string, decision: "APPROVE" | "REJECT" | "REQUEST_CHANGES", comment?: string) =>
api<ChangeRequest>(`/api/change-requests/${id}/decision`, {
method: "POST",
body: JSON.stringify({ decision, comment }),
}),
};
// ── Notifications ────────────────────────────────────────────────────────────────
export const notifications = {
list: (params: { page?: number; page_size?: number; unread?: boolean } = {}) => {
const q = new URLSearchParams();
if (params.page) q.set("page", String(params.page));
if (params.page_size) q.set("page_size", String(params.page_size));
if (params.unread) q.set("unread", "true");
return api<{ total: number; unread_count: number; notifications: Notification[] }>(
`/api/notifications?${q.toString()}`,
);
},
markRead: (id: number) => api(`/api/notifications/${id}/read`, { method: "POST" }),
markAllRead: () => api("/api/notifications/read-all", { method: "POST" }),
clear: () => api("/api/notifications", { method: "DELETE" }),
};
// ── Global ───────────────────────────────────────────────────────────────────────
export const global = {
get: () => api<GlobalSettings>("/api/global"),
};
// ── Admin ─────────────────────────────────────────────────────────────────────────
export const admin = {
users: () => api<AdminUser[]>("/api/admin/users"),
updateUser: (id: number, input: { role?: "ADMIN" | "USER"; disabled?: boolean }) =>
api(`/api/admin/users/${id}`, { method: "PATCH", body: JSON.stringify(input) }),
deleteUser: (id: number) => api(`/api/admin/users/${id}`, { method: "DELETE" }),
settings: () => api<GlobalSettings>("/api/admin/settings"),
updateSettings: (input: { registration_enabled?: boolean; requests_enabled?: boolean }) =>
api<GlobalSettings>("/api/admin/settings", { method: "PATCH", body: JSON.stringify(input) }),
agents: () => api<(Agent & { owner: { id: number; username: string } })[]>("/api/admin/agents"),
setAgentDisabled: (id: number, disabled: boolean) =>
api(`/api/admin/agents/${id}/disabled`, { method: "POST", body: JSON.stringify({ disabled }) }),
deleteAgent: (id: number) => api(`/api/admin/agents/${id}`, { method: "DELETE" }),
auditLogs: (page = 1) =>
api<{ total: number; page: number; page_size: number; logs: AuditLog[] }>(
`/api/admin/audit-logs?page=${page}`,
),
};