feat: add identification page

This commit is contained in:
2025-12-18 06:46:27 +09:00
parent e3d3df9ecb
commit ea5a0004da
17 changed files with 696 additions and 79 deletions
+58 -14
View File
@@ -1,7 +1,24 @@
import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, ReplacementRule, PathMappingConfig, PathMappingMode, PathMappingRules, SyncStrategy, ScheduleSettings, BackupSettings } from '../types';
import { Playlist, ServerType, ApiResponse, PlexServerConnection, PlexConnectionSettings, PlexLibrary, ReplacementRule, PathMappingConfig, PathMappingMode, PathMappingRules, SyncStrategy, ScheduleSettings, BackupSettings, LoginCredentials, AuthResponse } from '../types';
const API_BASE = import.meta.env.VITE_API_BASE_URL || '';
const getAuthToken = (): string | null => {
try {
return localStorage.getItem('plexsync-token');
} catch {
return null;
}
};
const authFetch = (input: RequestInfo | URL, init: RequestInit = {}) => {
const token = getAuthToken();
const headers = new Headers(init.headers || {});
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return fetch(input, { ...init, headers });
};
const MODE_TO_STRATEGY: Record<string, SyncStrategy> = {
local_force: SyncStrategy.LOCAL_OVERWRITE,
remote_force: SyncStrategy.CLOUD_OVERWRITE,
@@ -20,6 +37,9 @@ const handleResponse = async <T>(response: Response): Promise<ApiResponse<T>> =>
try {
const data = await response.json();
if (!response.ok) {
if (response.status === 401) {
return { data: data as T, status: 'error', message: 'Unauthorized' };
}
return { data: data as T, status: 'error', message: (data as any)?.detail || response.statusText };
}
return { data, status: 'success' };
@@ -97,8 +117,32 @@ const pathMappingToApi = (config: PathMappingConfig) => {
};
export const apiService = {
async getAuthConfig(): Promise<ApiResponse<{ enabled: boolean }>> {
const response = await fetch(`${API_BASE}/api/auth/config`);
return handleResponse(response);
},
async login(creds: LoginCredentials): Promise<ApiResponse<AuthResponse>> {
const response = await fetch(`${API_BASE}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(creds),
});
return handleResponse(response);
},
async me(): Promise<ApiResponse<{ username: string }>> {
const response = await authFetch(`${API_BASE}/api/auth/me`);
return handleResponse(response);
},
async logout(): Promise<ApiResponse<{ status: string }>> {
const response = await authFetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
return handleResponse(response);
},
async getSettings(): Promise<ApiResponse<{ strategy: SyncStrategy; pathMapping: PathMappingConfig; connection: PlexConnectionSettings; localPath: string }>> {
const response = await fetch(`${API_BASE}/api/settings`);
const response = await authFetch(`${API_BASE}/api/settings`);
const result = await handleResponse<any>(response);
if (result.status === 'success') {
const mode = result.data.sync_mode as string;
@@ -118,7 +162,7 @@ export const apiService = {
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`, {
const response = await authFetch(`${API_BASE}/api/settings/sync-mode`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
@@ -128,7 +172,7 @@ export const apiService = {
async savePathMapping(config: PathMappingConfig): Promise<ApiResponse<null>> {
const payload = pathMappingToApi(config);
const response = await fetch(`${API_BASE}/api/settings/path-mapping`, {
const response = await authFetch(`${API_BASE}/api/settings/path-mapping`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
@@ -137,7 +181,7 @@ export const apiService = {
},
async updateLibrary(libraryName: string): Promise<ApiResponse<{ library_name: string }>> {
const response = await fetch(`${API_BASE}/api/settings/library`, {
const response = await authFetch(`${API_BASE}/api/settings/library`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ library_name: libraryName }),
@@ -146,12 +190,12 @@ export const apiService = {
},
async getScheduleSettings(): Promise<ApiResponse<ScheduleSettings & { nextRun?: string }>> {
const response = await fetch(`${API_BASE}/api/schedule`);
const response = await authFetch(`${API_BASE}/api/schedule`);
return handleResponse(response);
},
async saveScheduleSettings(settings: ScheduleSettings): Promise<ApiResponse<null>> {
const response = await fetch(`${API_BASE}/api/schedule`, {
const response = await authFetch(`${API_BASE}/api/schedule`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
@@ -164,7 +208,7 @@ export const apiService = {
if (serverType === ServerType.LOCAL && localPath) {
params.append('local_path', localPath);
}
const response = await fetch(`${API_BASE}/api/playlists?${params.toString()}`, { signal });
const response = await authFetch(`${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' };
@@ -173,7 +217,7 @@ export const apiService = {
},
async getServerStatus(signal?: AbortSignal): Promise<ApiResponse<PlexServerConnection>> {
const response = await fetch(`${API_BASE}/api/server`, { signal });
const response = await authFetch(`${API_BASE}/api/server`, { signal });
const result = await handleResponse<any>(response);
if (result.status === 'success') {
const info = result.data.serverInfo || {};
@@ -194,7 +238,7 @@ export const apiService = {
},
async connectToPlex(settings: PlexConnectionSettings, signal?: AbortSignal): Promise<ApiResponse<{ token: string; serverInfo: PlexServerConnection }>> {
const response = await fetch(`${API_BASE}/api/connect`, {
const response = await authFetch(`${API_BASE}/api/connect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -219,7 +263,7 @@ export const apiService = {
},
async syncPlaylists(strategy: SyncStrategy, _pathMapping: PathMappingConfig, localPath?: string): Promise<ApiResponse<null>> {
const response = await fetch(`${API_BASE}/api/sync`, {
const response = await authFetch(`${API_BASE}/api/sync`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -231,12 +275,12 @@ export const apiService = {
},
async getSyncStatus(): Promise<ApiResponse<{ is_syncing: boolean; last_sync_time: string | null; status: string; error: string | null }>> {
const response = await fetch(`${API_BASE}/api/sync/status`);
const response = await authFetch(`${API_BASE}/api/sync/status`);
return handleResponse(response);
},
async getBackupSettings(): Promise<ApiResponse<BackupSettings>> {
const response = await fetch(`${API_BASE}/api/backup/settings`);
const response = await authFetch(`${API_BASE}/api/backup/settings`);
const result = await handleResponse<any>(response);
if (result.status === 'success') {
return {
@@ -251,7 +295,7 @@ export const apiService = {
},
async saveBackupSettings(settings: BackupSettings): Promise<ApiResponse<null>> {
const response = await fetch(`${API_BASE}/api/backup/settings`, {
const response = await authFetch(`${API_BASE}/api/backup/settings`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({