Patchpass V1
This commit is contained in:
@@ -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}`,
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user