feat(api): refactor project API functions to use $fetch for improved error handling
Some checks failed
ci / ci (22) (pull_request) Failing after 6m4s

This commit is contained in:
Nicolas HOARAU 2026-06-22 15:46:46 +04:00
parent 79e0efc71e
commit a4f909289c
4 changed files with 55 additions and 90 deletions

View file

@ -1,35 +1,8 @@
import type { Project, ProjectInsertDTO, ProjectUpdateDTO } from "@workspace/schema";
interface ApiResponse<T> {
data: T | null;
error: string | null;
}
function getBaseUrl(): string {
const config = useRuntimeConfig();
return config.public.apiBaseUrl as string;
}
async function request<T>(path: string, options?: RequestInit): Promise<ApiResponse<T>> {
try {
const res = await fetch(`${getBaseUrl()}${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) {
const body = await res.json().catch(() => null);
return { data: null, error: body?.message ?? `HTTP ${res.status}` };
}
const data = (await res.json()) as T;
return { data, error: null };
} catch (err) {
return { data: null, error: err instanceof Error ? err.message : "Unknown error" };
}
}
export function useProjectApi() {
const config = useRuntimeConfig();
const apiBase = config.public.apiBaseUrl as string;
const NOT_FOUND = -1;
const projects = ref<Project[]>([]);
@ -39,67 +12,65 @@ export function useProjectApi() {
async function fetchProjects(): Promise<void> {
loading.value = true;
error.value = null;
const { data, error: err } = await request<Project[]>("/projects");
if (data) projects.value = data;
else error.value = err;
try {
projects.value = await $fetch<Project[]>(`${apiBase}/projects`);
} catch (err) {
error.value = err instanceof Error ? err.message : "Unknown error";
} finally {
loading.value = false;
}
}
async function createProject(body: Partial<ProjectInsertDTO>): Promise<Project | null> {
loading.value = true;
error.value = null;
const { data, error: err } = await request<Project>("/projects", {
try {
const created = await $fetch<Project>(`${apiBase}/projects`, {
method: "POST",
body: JSON.stringify(body),
body,
});
if (data) {
projects.value.push(data);
} else {
error.value = err;
}
projects.value.push(created);
return created;
} catch (err) {
error.value = err instanceof Error ? err.message : "Unknown error";
return null;
} finally {
loading.value = false;
return data;
}
}
async function updateProject(id: string, body: Partial<ProjectUpdateDTO>): Promise<Project | null> {
loading.value = true;
error.value = null;
const { data, error: err } = await request<Project>(`/projects/${id}`, {
try {
const updated = await $fetch<Project>(`${apiBase}/projects/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
body,
});
if (data) {
const idx = projects.value.findIndex(p => p.id === id);
if (idx !== NOT_FOUND) projects.value[idx] = data;
} else {
error.value = err;
}
const idx = projects.value.findIndex((p) => p.id === id);
if (idx !== NOT_FOUND) projects.value[idx] = updated;
return updated;
} catch (err) {
error.value = err instanceof Error ? err.message : "Unknown error";
return null;
} finally {
loading.value = false;
return data;
}
}
async function deleteProject(id: string): Promise<boolean> {
loading.value = true;
error.value = null;
const { error: err } = await request(`/projects/${id}`, { method: "DELETE" });
if (err) {
error.value = err;
} else {
projects.value = projects.value.filter(p => p.id !== id);
}
try {
await $fetch(`${apiBase}/projects/${id}`, { method: "DELETE" });
projects.value = projects.value.filter((p) => p.id !== id);
return true;
} catch (err) {
error.value = err instanceof Error ? err.message : "Unknown error";
return false;
} finally {
loading.value = false;
return !err;
}
}
return { projects, loading, error, fetchProjects, createProject, updateProject, deleteProject };

View file

@ -16,17 +16,14 @@ const project = ref<Project | null>(null);
const loading = ref(true);
const notFound = ref(false);
const config = useRuntimeConfig();
const apiBase = config.public.apiBaseUrl as string;
onMounted(async () => {
const res = await fetch(`${apiBase}/projects/${route.params.id}`);
if (res.ok) {
project.value = await res.json();
} else {
try {
project.value = await $fetch<Project>(`${useRuntimeConfig().public.apiBaseUrl as string}/projects/${route.params.id}`);
} catch {
notFound.value = true;
}
} finally {
loading.value = false;
}
});
async function handleSubmit(body: { name: string; description?: string; status: string }) {

View file

@ -1,14 +1,17 @@
// @ts-check
import withNuxt from "./.nuxt/eslint.config.mjs"
import { baseRules, } from "@workspace/tooling/eslint/base"
import withNuxt from "./.nuxt/eslint.config.mjs";
import { baseRules, } from "@workspace/tooling/eslint/base";
export default withNuxt({
rules: {
...baseRules,
"@stylistic/member-delimiter-style": "off",
"@stylistic/arrow-parens": "off",
"vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/html-closing-bracket-newline": "off",
"vue/no-multiple-template-root": "off",
"max-lines-per-function": "off",
"max-statements": "off",
},
},)
},);

View file

@ -21,10 +21,4 @@ export {
type ProjectUpdateDTO,
} from "./drizzle/projects";
export { projectsRelations, tasksRelations } from "./drizzle/relations";
export {
tasks,
taskSelect,
taskInsert,
taskUpdate,
type Task,
} from "./drizzle/tasks";
export { tasks, taskSelect, taskInsert, taskUpdate, type Task } from "./drizzle/tasks";