Initial commit: AuroraMusic monorepo
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user