177 lines
5.1 KiB
TypeScript
177 lines
5.1 KiB
TypeScript
import axios, { type AxiosError } from 'axios';
|
|
import { auth } from '$lib/stores/auth';
|
|
import { browser } from '$app/environment';
|
|
import { get } from 'svelte/store';
|
|
|
|
const API_BASE_URL_LOCAL = 'http://localhost:8000';
|
|
const API_BASE_URL_PROD = 'https://ohmj-api.c.nadal-fr.com';
|
|
|
|
const API_BASE_URL = import.meta.env.DEV ? API_BASE_URL_LOCAL : API_BASE_URL_PROD;
|
|
|
|
const api = axios.create({
|
|
baseURL: API_BASE_URL,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
export { api };
|
|
|
|
api.interceptors.request.use((config) => {
|
|
const authState = get(auth);
|
|
if (authState.token) {
|
|
config.headers.Authorization = `Bearer ${authState.token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
(error: AxiosError) => {
|
|
// Only logout on actual 401 from the server, not network/CORS errors
|
|
if (error.response?.status === 401 && !error.message?.includes('Network Error')) {
|
|
auth.logout();
|
|
if (browser) {
|
|
window.location.href = '/';
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export interface Score {
|
|
id: string;
|
|
name: string;
|
|
compositor: string;
|
|
ressource?: string | null;
|
|
instruments?: Instrument[];
|
|
}
|
|
|
|
export interface Piece {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
export interface Instrument {
|
|
id: string;
|
|
title: string;
|
|
piece: string;
|
|
parts: Part[];
|
|
}
|
|
|
|
export interface Part {
|
|
id: string;
|
|
files: PdfFile[];
|
|
}
|
|
|
|
export interface PdfFile {
|
|
name: string;
|
|
filename: string;
|
|
path: string;
|
|
part: string | null;
|
|
key: string | null;
|
|
clef: string | null;
|
|
variant: string | null;
|
|
}
|
|
|
|
export const apiService = {
|
|
async login(username: string, password: string): Promise<{ token: string; user: { username: string; role: string } }> {
|
|
const response = await api.post('/login', { username, password });
|
|
return response.data;
|
|
},
|
|
|
|
async getScores(): Promise<Score[]> {
|
|
const response = await api.get('/scores');
|
|
return response.data.scores;
|
|
},
|
|
|
|
async getScore(id: string): Promise<Score> {
|
|
const response = await api.get(`/scores/${id}`);
|
|
return response.data.score;
|
|
},
|
|
|
|
async getInstruments(scoreId: string, pieceId?: number): Promise<Instrument[]> {
|
|
const url = pieceId
|
|
? `/scores/${scoreId}/instruments?piece=${pieceId}`
|
|
: `/scores/${scoreId}/instruments`;
|
|
const response = await api.get(url);
|
|
return response.data.instruments;
|
|
},
|
|
|
|
async getPieces(scoreId: string): Promise<Piece[]> {
|
|
const response = await api.get(`/pieces/${scoreId}`);
|
|
return response.data.pieces;
|
|
},
|
|
|
|
async downloadPdf(path: string): Promise<Blob> {
|
|
const response = await api.get(`/download/${path}`, {
|
|
responseType: 'blob'
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
getDownloadUrl(path: string): string {
|
|
// Pass token in URL for direct browser access (PDF viewer, iframe, etc.)
|
|
// Safe over HTTPS
|
|
const authState = get(auth);
|
|
const token = authState.token ? encodeURIComponent(authState.token) : '';
|
|
return `${API_BASE_URL}/download/${path}?token=${token}`;
|
|
},
|
|
|
|
// New method to download with proper auth header
|
|
async downloadFileWithAuth(path: string): Promise<Blob> {
|
|
const response = await api.get(`/download/${path}`, {
|
|
responseType: 'blob'
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
async createScore(name: string, compositor: string, pieces: { number: number; name: string }[] = []): Promise<{ success: boolean; score?: any; error?: string }> {
|
|
const response = await api.post('/admin/scores', { name, compositor, pieces });
|
|
return response.data;
|
|
},
|
|
|
|
async updateScore(id: string, name: string, compositor: string, ressource: string = ''): Promise<{ success: boolean; error?: string }> {
|
|
const response = await api.put(`/admin/scores/${id}`, { name, compositor, ressource });
|
|
return response.data;
|
|
},
|
|
|
|
async deleteScore(id: string): Promise<{ success: boolean; error?: string }> {
|
|
const response = await api.delete(`/admin/scores/${id}`);
|
|
return response.data;
|
|
},
|
|
|
|
async uploadPdf(scoreId: string, file: File, piece: string, instrument: string, version: string, key?: string, clef?: string, variant?: string, part?: string, watermark?: boolean, watermarkPosition?: 'left' | 'right'): Promise<{ success: boolean; path?: string; error?: string }> {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('piece', piece);
|
|
formData.append('instrument', instrument);
|
|
formData.append('version', version);
|
|
if (key) formData.append('key', key);
|
|
if (clef) formData.append('clef', clef);
|
|
if (variant) formData.append('variant', variant);
|
|
if (part) formData.append('part', part);
|
|
if (watermark) formData.append('watermark', 'true');
|
|
if (watermarkPosition) formData.append('watermarkPosition', watermarkPosition);
|
|
|
|
const response = await api.post(`/admin/scores/${scoreId}/upload`, formData, {
|
|
headers: {
|
|
'Content-Type': undefined
|
|
}
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
async getScoreFiles(scoreId: string): Promise<{ success: boolean; files: any[]; error?: string }> {
|
|
const response = await api.get(`/admin/scores/${scoreId}/files`);
|
|
return response.data;
|
|
},
|
|
|
|
async deleteScoreFile(scoreId: string, filePath: string): Promise<{ success: boolean; error?: string }> {
|
|
const response = await api.delete(`/admin/scores/${scoreId}/files`, {
|
|
params: { path: filePath }
|
|
});
|
|
return response.data;
|
|
}
|
|
};
|