import { invoke } from '@tauri-apps/api/core'; export interface NdLibrary { id: number; name: string; } export interface NdUser { id: string; userName: string; name: string; email: string; isAdmin: boolean; libraryIds: number[]; lastLoginAt?: string | null; lastAccessAt?: string | null; createdAt?: string; updatedAt?: string; } export interface NdLoginResult { token: string; userId: string; isAdmin: boolean; } export async function ndLogin( serverUrl: string, username: string, password: string, ): Promise { return invoke('navidrome_login', { serverUrl, username, password }); } function extractLibraryIds(o: Record): number[] { const libs = o.libraries; if (!Array.isArray(libs)) return []; const ids: number[] = []; for (const l of libs) { const id = (l as Record)?.id; if (typeof id === 'number') ids.push(id); else if (typeof id === 'string' && /^\d+$/.test(id)) ids.push(Number(id)); } return ids; } export async function ndListUsers(serverUrl: string, token: string): Promise { const raw = await invoke('nd_list_users', { serverUrl, token }); if (!Array.isArray(raw)) return []; return raw.map(u => { const o = u as Record; return { id: String(o.id ?? ''), userName: String(o.userName ?? ''), name: String(o.name ?? ''), email: String(o.email ?? ''), isAdmin: !!o.isAdmin, libraryIds: extractLibraryIds(o), lastLoginAt: (o.lastLoginAt as string | null | undefined) ?? null, lastAccessAt: (o.lastAccessAt as string | null | undefined) ?? null, createdAt: o.createdAt as string | undefined, updatedAt: o.updatedAt as string | undefined, }; }); } export async function ndListLibraries(serverUrl: string, token: string): Promise { const raw = await invoke('nd_list_libraries', { serverUrl, token }); if (!Array.isArray(raw)) return []; return raw.map(l => { const o = l as Record; const id = typeof o.id === 'number' ? o.id : typeof o.id === 'string' && /^\d+$/.test(o.id) ? Number(o.id) : 0; return { id, name: String(o.name ?? '') }; }).filter(l => l.id > 0); } export async function ndSetUserLibraries( serverUrl: string, token: string, id: string, libraryIds: number[], ): Promise { await invoke('nd_set_user_libraries', { serverUrl, token, id, libraryIds }); } export async function ndCreateUser( serverUrl: string, token: string, data: { userName: string; name: string; email: string; password: string; isAdmin: boolean }, ): Promise<{ id: string }> { const raw = await invoke('nd_create_user', { serverUrl, token, ...data }); const o = (raw as Record | null) ?? {}; return { id: String(o.id ?? '') }; } export async function ndUpdateUser( serverUrl: string, token: string, id: string, data: { userName: string; name: string; email: string; password: string; isAdmin: boolean }, ): Promise { await invoke('nd_update_user', { serverUrl, token, id, ...data }); } export async function ndDeleteUser(serverUrl: string, token: string, id: string): Promise { await invoke('nd_delete_user', { serverUrl, token, id }); } /** * Fetch the absolute filesystem path of a song from Navidrome's native API. * Subsonic `getSong.view` only returns a relative path (or none on Navidrome); * the native `/api/song/:id` endpoint returns the absolute path the server * stores the file at, the same way Feishin and the Navidrome web client get it. * * Returns `null` when the server doesn't expose the field (non-admin on some * Navidrome configurations) or when the call fails — the Song Info modal then * falls back to whatever the Subsonic response had. */ export async function ndGetSongPath( serverUrl: string, username: string, password: string, id: string, ): Promise { try { const raw = await invoke('nd_get_song_path', { serverUrl, username, password, id }); return raw && raw.length > 0 ? raw : null; } catch { return null; } }