Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sveltekit error handling #705

Merged
merged 16 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"sync": "svelte-kit sync",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"test": "npm run test:unit && npm run test:browser",
"test": "TZ=UTC vitest run",
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
"test:browser": "playwright test",
"test:unit": "TZ=UTC vitest run",
"test:watch": " TZ=UTC vitest watch",
Expand Down
92 changes: 32 additions & 60 deletions src/lib/api/addons.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Page } from "@/api/types/common";
import type { AddOnParams } from "@/api/types/addons";
import type { AddOnListItem, Event, Run, AddOnPayload } from "@/addons/types";
import type { APIResponse, ValidationError } from "./types";

import Ajv, { type DefinedError } from "ajv";
import addFormats from "ajv-formats";
import { APP_URL, BASE_API_URL, CSRF_HEADER_NAME } from "@/config/config.js";
import { isErrorCode } from "../utils/api";
import { getApiResponse, isErrorCode } from "../utils/api";

// todo i18n
export const CATEGORIES = [
Expand All @@ -28,31 +29,28 @@ export const eventValues = {
upload: 4,
};

/**
* List add-ons, with optional filters
*/
export async function getAddons(
params: AddOnParams = {},
fetch = globalThis.fetch,
): Promise<Page<AddOnListItem>> {
): Promise<APIResponse<Page<AddOnListItem>, unknown>> {
const endpoint = new URL("addons/", BASE_API_URL);
Object.entries(params).forEach(([key, value]) => {
endpoint.searchParams.set(key, String(value));
});
const resp = await fetch(endpoint, { credentials: "include" }).catch(
console.error,
);

if (!resp) {
throw new Error("API error");
}
const resp = await fetch(endpoint, { credentials: "include" });

if (isErrorCode(resp.status)) {
throw new Error(resp.statusText);
}
return resp.json();
return getApiResponse<Page<AddOnListItem>>(resp);
}

/**
* List pinned add-ons
*/
export async function getPinnedAddons(
fetch = globalThis.fetch,
): Promise<Page<AddOnListItem>> {
): Promise<APIResponse<Page<AddOnListItem>, unknown>> {
return getAddons({ active: true }, fetch);
}

Expand All @@ -62,9 +60,9 @@ export async function getAddon(
fetch = globalThis.fetch,
): Promise<AddOnListItem | null> {
const repository = [owner, repo].join("/");
const addons = await getAddons({ repository }, fetch);
const { data: addons, error } = await getAddons({ repository }, fetch);
// there should only be one result, if the addon exists
if (addons.results.length < 1) {
if (error || addons.results.length < 1) {
return null;
}
return addons.results[0];
Expand All @@ -73,22 +71,12 @@ export async function getAddon(
export async function getEvent(
id: number,
fetch = globalThis.fetch,
): Promise<Event> {
): Promise<APIResponse<Event, unknown>> {
const endpoint = new URL(`addon_events/${id}/?expand=addon`, BASE_API_URL);

const resp = await fetch(endpoint, { credentials: "include" }).catch(
console.error,
);

if (!resp) {
throw new Error("API error");
}

if (isErrorCode(resp.status)) {
throw new Error(resp.statusText);
}
const resp = await fetch(endpoint, { credentials: "include" });

return resp.json();
return getApiResponse<Event>(resp);
}

/**
Expand All @@ -102,25 +90,15 @@ export async function history(
per_page?: number;
} = {},
fetch = globalThis.fetch,
): Promise<Page<Run>> {
): Promise<APIResponse<Page<Run>, unknown>> {
const endpoint = new URL("addon_runs/?expand=addon", BASE_API_URL);
for (const [k, v] of Object.entries(params)) {
endpoint.searchParams.set(k, String(v));
}

const resp = await fetch(endpoint, { credentials: "include" }).catch(
console.error,
);
const resp = await fetch(endpoint, { credentials: "include" });

if (!resp) {
throw new Error("API error");
}

if (isErrorCode(resp.status)) {
throw new Error(resp.statusText);
}

return resp.json();
return getApiResponse<Page<Run>>(resp);
}

/**
Expand All @@ -129,40 +107,30 @@ export async function history(
export async function scheduled(
params: { cursor?: string; addon?: number; per_page?: number } = {},
fetch = globalThis.fetch,
): Promise<Page<Event>> {
): Promise<APIResponse<Page<Event>, unknown>> {
const endpoint = new URL("addon_events/?expand=addon", BASE_API_URL);
for (const [k, v] of Object.entries(params)) {
endpoint.searchParams.set(k, String(v));
}

const resp = await fetch(endpoint, { credentials: "include" }).catch(
console.error,
);

if (!resp) {
throw new Error("API error");
}

if (isErrorCode(resp.status)) {
throw new Error(resp.statusText);
}
const resp = await fetch(endpoint, { credentials: "include" });

return resp.json();
return getApiResponse<Page<Event>>(resp);
}

// dispatching

/**
* Create or schedule a single add-on run, returning the HTTP response
* Create or schedule a single add-on run
*/
export async function dispatch(
payload: AddOnPayload,
csrf_token: string,
fetch = globalThis.fetch,
): Promise<Response> {
): Promise<APIResponse<Event | Run, ValidationError>> {
const path = payload.event ? "addon_events/" : "addon_runs/";
const endpoint = new URL(path, BASE_API_URL);
return fetch(endpoint, {
const resp = await fetch(endpoint, {
credentials: "include",
method: "POST",
headers: {
Expand All @@ -172,6 +140,8 @@ export async function dispatch(
},
body: JSON.stringify(payload),
});

return getApiResponse<Run | Event, ValidationError>(resp);
}

/**
Expand All @@ -182,9 +152,9 @@ export async function update(
payload: AddOnPayload,
csrf_token: string,
fetch = globalThis.fetch,
) {
): Promise<APIResponse<Event, ValidationError>> {
const endpoint = new URL(`addon_events/${event_id}/`, BASE_API_URL);
return fetch(endpoint, {
const resp = await fetch(endpoint, {
credentials: "include",
method: "PUT",
headers: {
Expand All @@ -194,6 +164,8 @@ export async function update(
},
body: JSON.stringify(payload),
});

return getApiResponse<Event, ValidationError>(resp);
}

/**
Expand Down
51 changes: 22 additions & 29 deletions src/lib/api/collaborators.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
// manage users in a project
import type { ProjectAccess, ProjectUser } from "./types";
import type {
APIResponse,
ProjectAccess,
ProjectUser,
ValidationError,
} from "./types";

import { APP_URL, BASE_API_URL, CSRF_HEADER_NAME } from "@/config/config.js";
import { getAll, isErrorCode } from "$lib/utils/api";
import { getAll, getApiResponse } from "$lib/utils/api";

export async function list(project_id: number, fetch = globalThis.fetch) {
const endpoint = new URL(
Expand All @@ -13,12 +18,15 @@ export async function list(project_id: number, fetch = globalThis.fetch) {
return getAll<ProjectUser>(endpoint, undefined, fetch);
}

/**
* Add a collaborator to a project
*/
export async function add(
project_id: number,
user: { email: string; access: ProjectAccess },
csrf_token: string,
fetch = globalThis.fetch,
) {
): Promise<APIResponse<ProjectUser, ValidationError>> {
const endpoint = new URL(`projects/${project_id}/users/`, BASE_API_URL);

const resp = await fetch(endpoint, {
Expand All @@ -32,26 +40,19 @@ export async function add(
method: "POST",
}).catch(console.error);

if (!resp) {
throw new Error("API unavailable");
}

if (isErrorCode(resp.status)) {
const data = await resp.json();
console.error(data);
throw new Error(resp.statusText);
}

return resp.json();
return getApiResponse<ProjectUser, ValidationError>(resp);
}

/**
* Update a user's permissions within a project
*/
export async function update(
project_id: number,
user_id: number,
access: ProjectAccess,
csrf_token: string,
fetch = globalThis.fetch,
) {
): Promise<APIResponse<ProjectUser, ValidationError>> {
const endpoint = new URL(
`projects/${project_id}/users/${user_id}/`,
BASE_API_URL,
Expand All @@ -68,37 +69,29 @@ export async function update(
method: "PATCH",
}).catch(console.error);

if (!resp) {
throw new Error("API unavailable");
}

if (isErrorCode(resp.status)) {
const data = await resp.json();
console.error(data);
throw new Error(resp.statusText);
}

return resp.json();
return getApiResponse<ProjectUser, ValidationError>(resp);
}

export async function remove(
project_id: number,
user_id: number,
csrf_token: string,
fetch = globalThis.fetch,
) {
): Promise<APIResponse<null, any>> {
const endpoint = new URL(
`projects/${project_id}/users/${user_id}/`,
BASE_API_URL,
);

return fetch(endpoint, {
const resp = await fetch(endpoint, {
credentials: "include",
headers: {
"Content-type": "application/json",
[CSRF_HEADER_NAME]: csrf_token,
Referer: APP_URL,
},
method: "DELETE",
});
}).catch(console.error);

return getApiResponse<null, any>(resp);
}
Loading