Skip to content

Commit

Permalink
Merge pull request #705 from MuckRock/sveltekit-error-handling
Browse files Browse the repository at this point in the history
sveltekit error handling
  • Loading branch information
allanlasser authored Sep 25, 2024
2 parents 506dffb + f6993cf commit 12067bc
Show file tree
Hide file tree
Showing 47 changed files with 775 additions and 810 deletions.
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",
"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

0 comments on commit 12067bc

Please sign in to comment.