chore: update package dependencies and enhance AppShell component with new navigation items and tooltip support. Refactor API client for improved error handling and response parsing. Expand type definitions for modules, entries, and communities, and enhance UI components with better structure and accessibility features.
CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 20s
CI / bird2 (push) Successful in 16s
CI / docker-images (deploy/docker/bird2/Dockerfile, , evobgp-bird2) (push) Successful in 40s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m6s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m1s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 59s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 1m23s
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m27s
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m12s
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m25s
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m24s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m31s
CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 20s
CI / bird2 (push) Successful in 16s
CI / docker-images (deploy/docker/bird2/Dockerfile, , evobgp-bird2) (push) Successful in 40s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m6s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m1s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 59s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 1m23s
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m27s
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m12s
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m25s
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m24s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m31s
This commit is contained in:
+56
-16
@@ -3,44 +3,84 @@ import { browser } from '$app/environment';
|
||||
export const TOKEN_STORAGE_KEY = 'evobgp_api_token';
|
||||
|
||||
export type Problem = {
|
||||
type?: string;
|
||||
title?: string;
|
||||
status?: number;
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
function mergeHeaders(init?: RequestInit): Headers {
|
||||
function getToken(): string | null {
|
||||
if (!browser) return null;
|
||||
return localStorage.getItem(TOKEN_STORAGE_KEY);
|
||||
}
|
||||
|
||||
function mergeHeaders(init?: RequestInit, extraHeaders?: Record<string, string>): Headers {
|
||||
const h = new Headers(init?.headers);
|
||||
if (!h.has('Accept')) {
|
||||
h.set('Accept', 'application/json');
|
||||
}
|
||||
if (browser) {
|
||||
const t = localStorage.getItem(TOKEN_STORAGE_KEY);
|
||||
if (t && !h.has('Authorization')) {
|
||||
h.set('Authorization', `Bearer ${t}`);
|
||||
if (!h.has('Accept')) h.set('Accept', 'application/json');
|
||||
const t = getToken();
|
||||
if (t && !h.has('Authorization')) h.set('Authorization', `Bearer ${t}`);
|
||||
if (extraHeaders) {
|
||||
for (const [k, v] of Object.entries(extraHeaders)) {
|
||||
if (!h.has(k)) h.set(k, v);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
export async function apiFetch(path: string, init?: RequestInit): Promise<Response> {
|
||||
if (!browser) {
|
||||
throw new Error('API is only available in the browser');
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
message: string,
|
||||
public readonly problem?: Problem
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiFetch(path: string, init?: RequestInit): Promise<Response> {
|
||||
if (!browser) throw new Error('API is only available in the browser');
|
||||
return fetch(path, { ...init, headers: mergeHeaders(init) });
|
||||
}
|
||||
|
||||
/** GET / DELETE без тела */
|
||||
export async function apiJSON<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await apiFetch(path, init);
|
||||
return parseResponse<T>(res);
|
||||
}
|
||||
|
||||
/** POST / PATCH / PUT с JSON-телом и автоматическим Idempotency-Key */
|
||||
export async function apiMutate<T = void>(
|
||||
path: string,
|
||||
method: 'POST' | 'PATCH' | 'PUT' | 'DELETE',
|
||||
body?: unknown,
|
||||
opts?: { idempotent?: boolean }
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {};
|
||||
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
||||
if (opts?.idempotent !== false) {
|
||||
headers['Idempotency-Key'] = crypto.randomUUID();
|
||||
}
|
||||
const res = await fetch(path, {
|
||||
method,
|
||||
headers: mergeHeaders({ headers }, headers),
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined
|
||||
});
|
||||
return parseResponse<T>(res);
|
||||
}
|
||||
|
||||
async function parseResponse<T>(res: Response): Promise<T> {
|
||||
if (res.status === 204 || res.status === 205) return undefined as T;
|
||||
const text = await res.text();
|
||||
if (!res.ok) {
|
||||
let detail = text;
|
||||
let problem: Problem | undefined;
|
||||
let detail = `HTTP ${res.status}`;
|
||||
try {
|
||||
const j = JSON.parse(text) as Problem;
|
||||
if (j?.detail) detail = j.detail;
|
||||
problem = JSON.parse(text) as Problem;
|
||||
detail = problem.detail ?? problem.title ?? detail;
|
||||
} catch {
|
||||
/* plain text */
|
||||
if (text) detail = text;
|
||||
}
|
||||
throw new Error(detail || `HTTP ${res.status}`);
|
||||
throw new ApiError(res.status, detail, problem);
|
||||
}
|
||||
if (!text) return undefined as T;
|
||||
return JSON.parse(text) as T;
|
||||
|
||||
Reference in New Issue
Block a user