Files
PlexPlaylistSync/frontend/services/api.ts
T
2025-11-29 00:25:45 +09:00

159 lines
6.5 KiB
TypeScript

import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, RegexReplacement, SyncStrategy } from '../types';
const API_BASE = import.meta.env.VITE_API_BASE_URL || '';
const MODE_TO_STRATEGY: Record<string, SyncStrategy> = {
local_force: SyncStrategy.LOCAL_OVERWRITE,
remote_force: SyncStrategy.CLOUD_OVERWRITE,
merge_local_primary: SyncStrategy.MERGE_LOCAL,
merge_remote_primary: SyncStrategy.MERGE_CLOUD,
};
const STRATEGY_TO_MODE: Record<SyncStrategy, string> = {
[SyncStrategy.LOCAL_OVERWRITE]: 'local_force',
[SyncStrategy.CLOUD_OVERWRITE]: 'remote_force',
[SyncStrategy.MERGE_LOCAL]: 'merge_local_primary',
[SyncStrategy.MERGE_CLOUD]: 'merge_remote_primary',
};
const handleResponse = async <T>(response: Response): Promise<ApiResponse<T>> => {
try {
const data = await response.json();
if (!response.ok) {
return { data: data as T, status: 'error', message: (data as any)?.detail || response.statusText };
}
return { data, status: 'success' };
} catch (error: any) {
return { data: {} as T, status: 'error', message: error?.message || 'Unexpected error' };
}
};
const mapPlaylist = (item: any): Playlist => ({
id: item.id || `${item.title}-${item.trackCount}`,
title: item.title ?? item.name ?? 'Unknown',
trackCount: item.trackCount ?? item.track_count ?? 0,
lastUpdated: item.lastUpdated || item.last_updated || new Date().toISOString(),
});
const mapLibrary = (item: any): PlexLibrary => ({
id: item.id ?? item.title,
title: item.title ?? item.id,
type: item.type ?? 'artist',
});
const mapRegexRules = (rules: any[]): RegexReplacement[] =>
(rules || []).map((rule, index) => ({
id: rule.id || `${rule.pattern || 'rule'}-${index}`,
pattern: rule.pattern || '',
replacement: rule.replacement || '',
}));
export const apiService = {
async getSettings(): Promise<ApiResponse<{ strategy: SyncStrategy; regex: RegexReplacement[]; connection: PlexConnectionSettings; localPath: string }>> {
const response = await fetch(`${API_BASE}/api/settings`);
const result = await handleResponse<any>(response);
if (result.status === 'success') {
const mode = result.data.sync_mode as string;
const strategy = MODE_TO_STRATEGY[mode] || SyncStrategy.LOCAL_OVERWRITE;
const regex = mapRegexRules(result.data.path_rules || []);
const connection: PlexConnectionSettings = {
protocol: (result.data.scheme as 'http' | 'https') || 'https',
address: result.data.server_url || '',
port: result.data.port || '32400',
token: result.data.token || '',
libraryName: result.data.library_name || '',
};
return { status: 'success', data: { strategy, regex, connection, localPath: result.data.local_path || '' } };
}
return result as ApiResponse<any> as ApiResponse<{ strategy: SyncStrategy; regex: RegexReplacement[]; connection: PlexConnectionSettings; localPath: string }>;
},
async updateSyncStrategy(strategy: SyncStrategy): Promise<ApiResponse<{ sync_mode: string }>> {
const payload = { mode: STRATEGY_TO_MODE[strategy] };
const response = await fetch(`${API_BASE}/api/settings/sync-mode`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return handleResponse(response);
},
async saveRegexRules(replacements: RegexReplacement[]): Promise<ApiResponse<{ rules: RegexReplacement[] }>> {
const payload = { rules: replacements.map(({ pattern, replacement }) => ({ pattern, replacement })) };
const response = await fetch(`${API_BASE}/api/settings/regex-rules`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return handleResponse(response);
},
async updateLibrary(libraryName: string): Promise<ApiResponse<{ library_name: string }>> {
const response = await fetch(`${API_BASE}/api/settings/library`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ library_name: libraryName }),
});
return handleResponse(response);
},
async getPlaylists(serverType: ServerType, signal?: AbortSignal, localPath?: string): Promise<ApiResponse<Playlist[]>> {
const params = new URLSearchParams({ server: serverType.toLowerCase() });
if (serverType === ServerType.LOCAL && localPath) {
params.append('local_path', localPath);
}
const response = await fetch(`${API_BASE}/api/playlists?${params.toString()}`, { signal });
const result = await handleResponse<any>(response);
if (result.status === 'success' && (result.data as any)?.playlists) {
return { data: (result.data.playlists as any[]).map(mapPlaylist), status: 'success' };
}
return result as ApiResponse<Playlist[]>;
},
async getServerStatus(signal?: AbortSignal): Promise<ApiResponse<PlexServerConnection>> {
const response = await fetch(`${API_BASE}/api/server`, { signal });
const result = await handleResponse<any>(response);
if (result.status === 'success') {
const info = result.data.serverInfo || {};
const libraries: PlexLibrary[] = (result.data.libraries || []).map(mapLibrary);
return {
status: 'success',
data: {
isConnected: !!info.isConnected,
name: info.name,
ip: info.ip,
port: info.port ? Number(info.port) : undefined,
libraryName: info.libraryName,
libraries,
},
};
}
return result as ApiResponse<PlexServerConnection>;
},
async connectToPlex(settings: PlexConnectionSettings, signal?: AbortSignal): Promise<ApiResponse<{ token: string; serverInfo: PlexServerConnection }>> {
const response = await fetch(`${API_BASE}/api/connect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
protocol: settings.protocol,
address: settings.address,
port: settings.port,
token: settings.token,
username: settings.username,
password: settings.password,
library_name: settings.libraryName,
timeout: settings.timeout,
}),
signal,
});
const result = await handleResponse<any>(response);
if (result.status === 'success') {
const info = result.data.serverInfo;
info.libraries = (info.libraries || []).map(mapLibrary);
return { status: 'success', data: { token: result.data.token, serverInfo: info } };
}
return result as ApiResponse<{ token: string; serverInfo: PlexServerConnection }>;
},
};