Files
auroramusic/apps/web/src/lib/api.ts
T

108 lines
3.3 KiB
TypeScript

const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
export type Song = {
id: string;
user_id: string;
owner_display_name: string | null;
title: string;
prompt: string;
lyrics: string | null;
mode: "instrumental" | "vocals";
status: "pending" | "processing" | "completed" | "failed";
error_message: string | null;
duration_seconds: number | null;
cover_seed: number;
is_public: boolean;
like_count: number;
liked_by_me: boolean;
audio_url: string | null;
created_at: string;
};
export type User = {
id: string;
email: string;
display_name: string;
created_at: string;
};
class ApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
function getToken(): string | null {
if (typeof window === "undefined") return null;
return window.localStorage.getItem("aurora_token");
}
export function setToken(token: string | null) {
if (typeof window === "undefined") return;
if (token) window.localStorage.setItem("aurora_token", token);
else window.localStorage.removeItem("aurora_token");
}
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = getToken();
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(options.headers as Record<string, string> | undefined),
};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch(`${API_URL}${path}`, { ...options, headers });
if (!res.ok) {
let detail = res.statusText;
try {
const body = await res.json();
detail = body.detail ?? detail;
} catch {
// ignore
}
throw new ApiError(res.status, typeof detail === "string" ? detail : JSON.stringify(detail));
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
export function mediaUrl(audioUrl: string | null): string | null {
if (!audioUrl) return null;
return `${API_URL}${audioUrl}`;
}
export const api = {
signup: (email: string, password: string, display_name: string) =>
request<{ access_token: string }>("/auth/signup", {
method: "POST",
body: JSON.stringify({ email, password, display_name }),
}),
login: (email: string, password: string) =>
request<{ access_token: string }>("/auth/login", {
method: "POST",
body: JSON.stringify({ email, password }),
}),
me: () => request<User>("/auth/me"),
createSong: (payload: {
title: string;
prompt: string;
mode: "instrumental" | "vocals";
lyrics?: string | null;
is_public: boolean;
}) => request<Song>("/songs", { method: "POST", body: JSON.stringify(payload) }),
mySongs: () => request<{ items: Song[]; total: number }>("/songs/mine"),
feed: () => request<{ items: Song[]; total: number }>("/songs/feed"),
getSong: (id: string) => request<Song>(`/songs/${id}`),
updateSong: (id: string, payload: { title?: string; is_public?: boolean }) =>
request<Song>(`/songs/${id}`, { method: "PATCH", body: JSON.stringify(payload) }),
deleteSong: (id: string) => request<void>(`/songs/${id}`, { method: "DELETE" }),
likeSong: (id: string) => request<Song>(`/songs/${id}/like`, { method: "POST" }),
unlikeSong: (id: string) => request<Song>(`/songs/${id}/like`, { method: "DELETE" }),
};
export { ApiError };