108 lines
3.3 KiB
TypeScript
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 };
|