/
t3
/
web-ui
Обзор
Документация
Войти
/
t3
/
web-ui
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/api/client.ts
276 строк
10 KB
Ivan Shibkikh
fix login issue
23 июл 2026, 19:09
23 июл 2026, 19:09
75543f6
Код
Авторство
О чём код?
import type { LoginRequest, LoginResponse, MeResponse, BucketResponse, CreateBucketRequest, ObjectResponse, PresignResponse, CreateUserRequest, CreateUserResponse, UserResponse, UpdateUserRequest, InspectUserResponse, CreateAccessKeyRequest, CreateAccessKeyResponse, AccessKeyResponse, UpdateAccessKeyRequest, RotateAccessKeyRequest, CreateRoleRequest, RoleResponse, UpdateRoleRequest, UserRolesResponse, AssignRolesRequest, SetPermissionsRequest, UserPermissionsResponse, RolePermissionsResponse, AuditFilters, ListAuditResponse, } from "./types"; export class ApiError extends Error { status: number; code: string; detail?: string; iamFields?: { field: string; issue: string }[]; requestId?: string; constructor( status: number, code: string, detail?: string, iamFields?: { field: string; issue: string }[], requestId?: string, ) { super(detail || code); this.name = "ApiError"; this.status = status; this.code = code; this.detail = detail; this.iamFields = iamFields; this.requestId = requestId; } } const TOKEN_KEY = "t3_jwt_token"; export function getStoredToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setStoredToken(token: string): void { localStorage.setItem(TOKEN_KEY, token); } export function clearStoredToken(): void { localStorage.removeItem(TOKEN_KEY); } async function request<T>( method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>, ): Promise<{ data: T; headers: Headers }> { const token = getStoredToken(); const headers: Record<string, string> = { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...extraHeaders, }; const res = await fetch(path, { method, headers, body: body !== undefined ? JSON.stringify(body) : undefined, }); if (res.status === 401) { if (path === "/api/v1/auth/login") { throw new ApiError(401, "unauthorized", "Неверный access_key или access_secret"); } clearStoredToken(); throw new ApiError(401, "unauthorized", "Сессия истекла. Пожалуйста, войдите заново."); } if (res.status === 204) { return { data: undefined as unknown as T, headers: res.headers }; } const isJson = res.headers.get("content-type")?.includes("json"); const raw = isJson ? await res.json() : await res.text(); if (!res.ok) { if (isJson && typeof raw === "object" && raw !== null) { // Gateway error shape: { error, message, request_id } if ("error" in raw && typeof raw.error === "string") { throw new ApiError(res.status, raw.error, raw.message, undefined, raw.request_id); } // IAM error shape: { error: { code, message, fields, request_id } } if (raw.error && typeof raw.error === "object" && raw.error.code) { throw new ApiError( res.status, raw.error.code, raw.error.message, raw.error.fields, raw.error.request_id, ); } // Fallback generic error throw new ApiError(res.status, "error", JSON.stringify(raw)); } throw new ApiError(res.status, "error", typeof raw === "string" ? raw : undefined); } return { data: raw as T, headers: res.headers }; } // ===== Auth ===== export const authApi = { login: (body: LoginRequest) => request<LoginResponse>("POST", "/api/v1/auth/login", body), me: () => request<MeResponse>("GET", "/api/v1/auth/me"), }; // ===== Users ===== export const usersApi = { list: () => request<UserResponse[]>("GET", "/api/v1/users"), create: (body: CreateUserRequest) => request<CreateUserResponse>("POST", "/api/v1/users", body), get: (id: string) => request<InspectUserResponse>("GET", `/api/v1/users/${id}`), update: (id: string, body: UpdateUserRequest, version?: number) => request<void>( "PATCH", `/api/v1/users/${id}`, body, version !== undefined ? { "If-Match": String(version) } : undefined, ), delete: (id: string) => request<void>("DELETE", `/api/v1/users/${id}`), // Access Keys listKeys: (userId: string) => request<AccessKeyResponse[]>("GET", `/api/v1/users/${userId}/access-keys`), createKey: (userId: string, body: CreateAccessKeyRequest) => request<CreateAccessKeyResponse>("POST", `/api/v1/users/${userId}/access-keys`, body), rotateKey: (userId: string, body: RotateAccessKeyRequest) => request<CreateAccessKeyResponse>( "POST", `/api/v1/users/${userId}/access-keys:rotate`, body, ), updateKey: (userId: string, keyId: string, body: UpdateAccessKeyRequest) => request<void>("PATCH", `/api/v1/users/${userId}/access-keys/${keyId}`, body), deleteKey: (userId: string, keyId: string) => request<void>("DELETE", `/api/v1/users/${userId}/access-keys/${keyId}`), // User Roles getUserRoles: (userId: string) => request<UserRolesResponse>("GET", `/api/v1/users/${userId}/roles`), replaceUserRoles: (userId: string, body: AssignRolesRequest, version?: number) => request<void>( "PUT", `/api/v1/users/${userId}/roles`, body, version !== undefined ? { "If-Match": String(version) } : undefined, ), addUserRoles: (userId: string, body: AssignRolesRequest) => request<void>("POST", `/api/v1/users/${userId}/roles`, body), removeUserRoles: (userId: string, body: AssignRolesRequest) => request<void>("DELETE", `/api/v1/users/${userId}/roles`, body), // User Permissions getUserPermissions: (userId: string) => request<UserPermissionsResponse>("GET", `/api/v1/users/${userId}/permissions`), replaceUserPermissions: (userId: string, body: SetPermissionsRequest, version?: number) => request<void>( "PUT", `/api/v1/users/${userId}/permissions`, body, version !== undefined ? { "If-Match": String(version) } : undefined, ), addUserPermissions: (userId: string, body: SetPermissionsRequest) => request<void>("POST", `/api/v1/users/${userId}/permissions`, body), removeUserPermissions: (userId: string, body: SetPermissionsRequest) => request<void>("DELETE", `/api/v1/users/${userId}/permissions`, body), }; // ===== Roles ===== export const rolesApi = { list: () => request<RoleResponse[]>("GET", "/api/v1/roles"), create: (body: CreateRoleRequest) => request<RoleResponse>("POST", "/api/v1/roles", body), get: (id: string) => request<RolePermissionsResponse>("GET", `/api/v1/roles/${id}`), update: (id: string, body: UpdateRoleRequest, version?: number) => request<RoleResponse>( "PATCH", `/api/v1/roles/${id}`, body, version !== undefined ? { "If-Match": String(version) } : undefined, ), delete: (id: string) => request<void>("DELETE", `/api/v1/roles/${id}`), restore: (id: string) => request<RoleResponse>("POST", `/api/v1/roles/${id}/restore`), // Role Permissions getPermissions: (id: string) => request<RolePermissionsResponse>("GET", `/api/v1/roles/${id}`), replacePermissions: (id: string, body: SetPermissionsRequest, version?: number) => request<void>( "PUT", `/api/v1/roles/${id}/permissions`, body, version !== undefined ? { "If-Match": String(version) } : undefined, ), addPermissions: (id: string, body: SetPermissionsRequest) => request<void>("POST", `/api/v1/roles/${id}/permissions`, body), removePermissions: (id: string, body: SetPermissionsRequest) => request<void>("DELETE", `/api/v1/roles/${id}/permissions`, body), }; // ===== Audit ===== export const auditApi = { list: (filters: AuditFilters = {}) => { const params = new URLSearchParams(); if (filters.limit) params.set("limit", String(filters.limit)); if (filters.offset) params.set("offset", String(filters.offset)); if (filters.action) params.set("action", filters.action); if (filters.resource_type) params.set("resource_type", filters.resource_type); if (filters.resource_id) params.set("resource_id", filters.resource_id); if (filters.request_id) params.set("request_id", filters.request_id); if (filters.from) params.set("from", filters.from); if (filters.to) params.set("to", filters.to); const qs = params.toString(); return request<ListAuditResponse>("GET", `/api/v1/audit${qs ? `?${qs}` : ""}`); }, }; // ===== Buckets ===== export const bucketsApi = { list: () => request<BucketResponse[]>("GET", "/api/v1/buckets"), create: (body: CreateBucketRequest) => request<BucketResponse>("POST", "/api/v1/buckets", body), delete: (bucket: string) => request<void>("DELETE", `/api/v1/buckets/${encodeURIComponent(bucket)}`), }; // ===== Objects ===== export const objectsApi = { list: (bucket: string, prefix?: string) => { const qs = prefix ? `?prefix=${encodeURIComponent(prefix)}` : ""; return request<ObjectResponse[]>( "GET", `/api/v1/buckets/${encodeURIComponent(bucket)}/objects${qs}`, ); }, presignDownload: (bucket: string, key: string) => request<PresignResponse>( "POST", `/api/v1/buckets/${encodeURIComponent(bucket)}/objects/${encodeURIComponent(key)}/presign-download`, ), presignUpload: (bucket: string, key: string) => request<PresignResponse>( "POST", `/api/v1/buckets/${encodeURIComponent(bucket)}/objects/${encodeURIComponent(key)}/presign-upload`, ), delete: (bucket: string, key: string) => request<void>( "DELETE", `/api/v1/buckets/${encodeURIComponent(bucket)}/objects/${encodeURIComponent(key)}`, ), };