Integrate React frontend with backend API

This commit is contained in:
Koha9
2025-11-28 03:00:02 +09:00
parent 4c6af7115e
commit 8d358a1de2
14 changed files with 2608 additions and 150 deletions
+111 -98
View File
@@ -1,98 +1,90 @@
import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary } from '../types';
import { MOCK_LOCAL_PLAYLISTS, MOCK_CLOUD_PLAYLISTS } from './mockData';
import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, RegexReplacement, UiConfig, SyncSettings } from '../types';
const SIMULATE_DELAY_MS = 800;
const API_PREFIX = '/api';
// Mock available libraries on a server
const MOCK_LIBRARIES: PlexLibrary[] = [
{ id: 'lib1', title: 'Music (Flac)', type: 'artist' },
{ id: 'lib2', title: 'MP3 Collection', type: 'artist' },
{ id: 'lib3', title: 'Soundtracks', type: 'artist' },
{ id: 'lib4', title: 'Audiobooks', type: 'artist' }
];
// Helper to simulate network request or call actual API
const fetchPlaylists = async (type: ServerType): Promise<Playlist[]> => {
// In a real Docker environment with FastAPI, you would do:
// const response = await fetch(`/api/playlists/${type.toLowerCase()}`);
// const data = await response.json();
// return data;
// Mocking for UI demonstration
return new Promise((resolve) => {
setTimeout(() => {
if (type === ServerType.LOCAL) {
resolve([...MOCK_LOCAL_PLAYLISTS]);
} else {
resolve([...MOCK_CLOUD_PLAYLISTS]);
}
}, SIMULATE_DELAY_MS);
});
const parseJson = async <T>(response: Response): Promise<ApiResponse<T>> => {
try {
const data = await response.json();
return data as ApiResponse<T>;
} catch (error) {
console.error('Failed to parse API response', error);
return { data: {} as T, status: 'error', message: 'Invalid response from server' };
}
};
const fetchServerStatus = async (): Promise<PlexServerConnection> => {
// Mocking server status
return new Promise((resolve) => {
setTimeout(() => {
// 90% chance of success for demo
const isSuccess = Math.random() > 0.1;
if (isSuccess) {
resolve({
isConnected: true,
name: 'Home Media Server',
ip: '192.168.1.105',
port: 32400,
libraryName: 'Music (Flac)'
});
} else {
resolve({
isConnected: false
});
}
}, SIMULATE_DELAY_MS);
});
};
const authenticatePlex = async (settings: PlexConnectionSettings): Promise<{ token: string, serverInfo: PlexServerConnection }> => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Simulate validation
if (!settings.address) {
reject(new Error("Server address is required"));
return;
}
// If user provided username/password, mock a token generation
let token = settings.token;
if (!token && settings.username && settings.password) {
token = "MOCK_TOKEN_XYZ_999";
} else if (!token) {
reject(new Error("Token or Username/Password required"));
return;
}
// Success response with libraries
resolve({
token: token,
serverInfo: {
isConnected: true,
name: 'My Plex Server',
ip: settings.address,
port: parseInt(settings.port) || 32400,
libraryName: MOCK_LIBRARIES[0].title, // Default to first library
libraries: MOCK_LIBRARIES
}
});
}, 1500);
});
}
const mapServerType = (type: ServerType) => type === ServerType.LOCAL ? 'local' : 'cloud';
export const apiService = {
getUiConfig: async (): Promise<ApiResponse<UiConfig>> => {
try {
const response = await fetch(`${API_PREFIX}/ui-config`);
const result = await parseJson<UiConfig>(response);
if (!response.ok || result.status !== 'success') {
return { data: { statusCheckIntervalSeconds: 60 }, status: 'error', message: result.message || '无法获取前端配置' };
}
return result;
} catch (error) {
return { data: { statusCheckIntervalSeconds: 60 }, status: 'error', message: '无法获取前端配置' };
}
},
getSettings: async (): Promise<ApiResponse<SyncSettings>> => {
try {
const response = await fetch(`${API_PREFIX}/settings`);
const result = await parseJson<SyncSettings>(response);
if (!response.ok || result.status !== 'success') {
return { data: { syncStrategy: 'LOCAL_OVERWRITE', regexRules: [] }, status: 'error', message: result.message || '无法获取设置' };
}
return result;
} catch (error) {
return { data: { syncStrategy: 'LOCAL_OVERWRITE', regexRules: [] }, status: 'error', message: '无法获取设置' };
}
},
saveStrategy: async (strategy: string): Promise<ApiResponse<SyncSettings>> => {
try {
const response = await fetch(`${API_PREFIX}/settings/strategy`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ strategy })
});
return await parseJson<SyncSettings>(response);
} catch (error) {
return { data: { syncStrategy: strategy, regexRules: [] }, status: 'error', message: '无法保存同步策略' };
}
},
getRegexRules: async (): Promise<ApiResponse<RegexReplacement[]>> => {
try {
const response = await fetch(`${API_PREFIX}/regex-rules`);
return await parseJson<RegexReplacement[]>(response);
} catch (error) {
return { data: [], status: 'error', message: '无法获取正则规则' };
}
},
saveRegexRules: async (rules: RegexReplacement[]): Promise<ApiResponse<RegexReplacement[]>> => {
try {
const response = await fetch(`${API_PREFIX}/regex-rules`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(rules)
});
return await parseJson<RegexReplacement[]>(response);
} catch (error) {
return { data: [], status: 'error', message: '无法保存正则规则' };
}
},
getPlaylists: async (serverType: ServerType): Promise<ApiResponse<Playlist[]>> => {
try {
const data = await fetchPlaylists(serverType);
return { data, status: 'success' };
const response = await fetch(`${API_PREFIX}/playlists/${mapServerType(serverType)}`);
const result = await parseJson<Playlist[]>(response);
if (!response.ok || result.status !== 'success') {
return { data: [], status: 'error', message: result.message || 'Failed to fetch playlists' };
}
return result;
} catch (error) {
console.error(`Error fetching ${serverType} playlists:`, error);
return { data: [], status: 'error', message: 'Failed to fetch playlists' };
@@ -101,27 +93,48 @@ export const apiService = {
getServerStatus: async (): Promise<ApiResponse<PlexServerConnection>> => {
try {
const data = await fetchServerStatus();
return { data, status: 'success' };
const response = await fetch(`${API_PREFIX}/server/status`);
const result = await parseJson<PlexServerConnection>(response);
if (!response.ok || result.status !== 'success') {
return { data: { isConnected: false }, status: 'error', message: result.message || 'Failed to connect to server' };
}
return result;
} catch (error) {
return {
data: { isConnected: false },
status: 'error',
message: 'Failed to connect to server'
return {
data: { isConnected: false },
status: 'error',
message: 'Failed to connect to server'
};
}
},
connectToPlex: async (settings: PlexConnectionSettings): Promise<ApiResponse<{ token: string, serverInfo: PlexServerConnection }>> => {
try {
const data = await authenticatePlex(settings);
return { data, status: 'success', message: 'Connected successfully' };
const response = await fetch(`${API_PREFIX}/server/connect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings)
});
return await parseJson<{ token: string, serverInfo: PlexServerConnection }>(response);
} catch (error: any) {
return {
data: { token: '', serverInfo: { isConnected: false } },
status: 'error',
message: error.message || 'Connection failed'
return {
data: { token: '', serverInfo: { isConnected: false } },
status: 'error',
message: error?.message || 'Connection failed'
};
}
},
selectLibrary: async (library: string): Promise<ApiResponse<{ libraryName: string }>> => {
try {
const response = await fetch(`${API_PREFIX}/server/library`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ library })
});
return await parseJson<{ libraryName: string }>(response);
} catch (error) {
return { data: { libraryName: library }, status: 'error', message: '无法切换媒体库' };
}
}
};