feat: analyse import + all tests fixed

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-10-15 16:14:15 +02:00
parent fbe9619224
commit 3170a7c60e
74 changed files with 4318 additions and 183 deletions

View File

@@ -0,0 +1,200 @@
/**
* Entité représentant un fichier en cours d'import avec ses correspondances possibles
*/
export class FileImport {
constructor({
file, // File object from browser
filename, // Original filename
analysis = null, // Result from /api/manga-matches endpoint
selectedManga = null, // Selected manga match
selectedChapterNumber = null, // Selected chapter number (extracted from filename)
selectedVolumeNumber = null, // Selected volume number (extracted from filename)
status = 'pending', // 'pending', 'analyzed', 'importing', 'imported', 'error'
errorMessage = null,
importedAt = null
}) {
this.file = file;
this.filename = filename;
this.analysis = analysis;
this.selectedManga = selectedManga;
this.selectedChapterNumber = selectedChapterNumber;
this.selectedVolumeNumber = selectedVolumeNumber;
this.status = status;
this.errorMessage = errorMessage;
this.importedAt = importedAt;
this.id = this._generateId();
}
static create(file) {
return new FileImport({
file,
filename: file.name
});
}
_generateId() {
return `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Status helpers
isPending() {
return this.status === 'pending';
}
isAnalyzed() {
return this.status === 'analyzed';
}
isImporting() {
return this.status === 'importing';
}
isImported() {
return this.status === 'imported';
}
hasError() {
return this.status === 'error';
}
// Analysis helpers
hasMatches() {
return this.analysis && this.analysis.matches && this.analysis.matches.length > 0;
}
getMatches() {
return this.analysis?.matches || [];
}
getBestMatch() {
const matches = this.getMatches();
// Sort by matchScore (highest first) and return the best one
return matches.length > 0 ? matches.sort((a, b) => b.matchScore - a.matchScore)[0] : null;
}
// Analysis extracted data
getExtractedChapterNumber() {
return this.analysis?.chapterNumber || null;
}
getExtractedVolumeNumber() {
return this.analysis?.volumeNumber || null;
}
// Selection helpers
isReadyForImport() {
// Ready if a manga is selected and at least chapter or volume number is set
return this.selectedManga && (this.selectedChapterNumber !== null || this.selectedVolumeNumber !== null);
}
getImportType() {
if (this.selectedChapterNumber !== null) return 'chapter';
if (this.selectedVolumeNumber !== null) return 'volume';
return null;
}
// File helpers
getFormattedSize() {
if (!this.file || !this.file.size) return 'Unknown';
const bytes = this.file.size;
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
getFileExtension() {
const extension = this.filename.split('.').pop().toLowerCase();
return extension;
}
isValidFormat() {
const validExtensions = ['cbz', 'cbr'];
return validExtensions.includes(this.getFileExtension());
}
// Update methods
setAnalysis(analysis) {
this.analysis = analysis;
this.status = 'analyzed';
// Auto-set extracted chapter/volume numbers from analysis
if (analysis.chapterNumber !== null && analysis.chapterNumber !== undefined) {
this.selectedChapterNumber = analysis.chapterNumber;
}
if (analysis.volumeNumber !== null && analysis.volumeNumber !== undefined) {
this.selectedVolumeNumber = analysis.volumeNumber;
}
// Auto-select best match if available
const bestMatch = this.getBestMatch();
if (bestMatch) {
this.selectedManga = bestMatch;
}
}
setSelectedManga(manga) {
this.selectedManga = manga;
// Keep the chapter/volume numbers from analysis
}
setSelectedChapterNumber(chapterNumber) {
this.selectedChapterNumber = chapterNumber;
// If setting chapter, clear volume
if (chapterNumber !== null) {
this.selectedVolumeNumber = null;
}
}
setSelectedVolumeNumber(volumeNumber) {
this.selectedVolumeNumber = volumeNumber;
// If setting volume, clear chapter
if (volumeNumber !== null) {
this.selectedChapterNumber = null;
}
}
setImporting() {
this.status = 'importing';
this.errorMessage = null;
}
setImported() {
this.status = 'imported';
this.importedAt = new Date().toISOString();
this.errorMessage = null;
}
setError(message) {
this.status = 'error';
this.errorMessage = message;
}
// Export selection for API
getImportData() {
if (!this.isReadyForImport()) {
throw new Error('File is not ready for import');
}
const data = {
mangaId: this.selectedManga.id
};
if (this.selectedChapterNumber !== null) {
data.chapterNumber = this.selectedChapterNumber;
}
if (this.selectedVolumeNumber !== null) {
data.volumeNumber = this.selectedVolumeNumber;
}
return data;
}
}

View File

@@ -0,0 +1,239 @@
export class ImportFile {
constructor({
id,
originalName,
fileSize,
extension,
status = 'pending',
createdAt,
metadata = null,
mangaMatches = [],
selectedMangaSlug = null,
selectedVolume = null,
selectedChapter = null,
errorMessage = null,
processedAt = null,
// New properties for simplified workflow
file = null, // Browser File object
analysis = null, // Analysis result from API
selectedManga = null, // Selected manga object
selectedChapterId = null // Selected chapter ID
}) {
this.id = id;
this.originalName = originalName;
this.fileSize = fileSize;
this.extension = extension;
this.status = status;
this.createdAt = createdAt;
this.metadata = metadata;
this.mangaMatches = mangaMatches;
this.selectedMangaSlug = selectedMangaSlug;
this.selectedVolume = selectedVolume;
this.selectedChapter = selectedChapter;
this.errorMessage = errorMessage;
this.processedAt = processedAt;
// New properties
this.file = file;
this.analysis = analysis;
this.selectedManga = selectedManga;
this.selectedChapterId = selectedChapterId;
this.mangaMatches = mangaMatches; // Store found manga matches
}
static create(data) {
return new ImportFile({
...data,
createdAt: data.createdAt || new Date().toISOString()
});
}
// Create from browser File object
static createFromFile(file) {
return new ImportFile({
id: `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
originalName: file.name,
fileSize: file.size,
extension: file.name.split('.').pop().toLowerCase(),
file: file,
createdAt: new Date().toISOString()
});
}
isProcessed() {
return this.status === 'processed';
}
hasError() {
return this.status === 'error';
}
isPending() {
return this.status === 'pending';
}
needsConversion() {
return this.extension === 'cbr';
}
isReadyForImport() {
return this.isProcessed() && this.selectedMangaSlug && (this.selectedVolume || this.selectedChapter);
}
getFormattedSize() {
const bytes = parseInt(this.fileSize);
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
getContentType() {
if (this.metadata?.chapter) {
return `Chapter ${this.metadata.chapter}`;
}
if (this.metadata?.volume) {
return `Volume ${this.metadata.volume}`;
}
return 'Unknown';
}
// === NEW METHODS FOR SIMPLIFIED WORKFLOW ===
// Status helpers for new workflow
isAnalyzed() {
return this.status === 'analyzed';
}
isImporting() {
return this.status === 'importing';
}
isImported() {
return this.status === 'imported';
}
// Analysis helpers
hasAnalysis() {
return this.analysis && this.analysis.possibleTitles && this.analysis.possibleTitles.length > 0;
}
getPossibleTitles() {
return this.analysis?.possibleTitles || [];
}
getAnalyzedChapter() {
return this.analysis?.chapterNumber || null;
}
getAnalyzedVolume() {
return this.analysis?.volumeNumber || null;
}
// For backward compatibility with existing code
hasMatches() {
return this.mangaMatches && this.mangaMatches.length > 0;
}
getMatches() {
return this.mangaMatches || [];
}
getBestMatch() {
const matches = this.getMatches();
return matches.length > 0 ? matches[0] : null;
}
// Selection helpers
isReadyForNewImport() {
return this.selectedManga && (this.selectedChapterId || this.selectedVolume !== null);
}
getImportType() {
if (this.selectedChapterId) return 'chapter';
if (this.selectedVolume !== null) return 'volume';
return null;
}
// File validation
isValidFormat() {
const validExtensions = ['cbz', 'cbr'];
return validExtensions.includes(this.extension);
}
// Update methods for new workflow
setAnalysis(analysis) {
this.analysis = analysis;
this.status = 'analyzed';
}
setMangaMatches(matches) {
this.mangaMatches = matches;
// Auto-select best match if available
const bestMatch = this.getBestMatch();
if (bestMatch) {
this.selectedManga = bestMatch;
}
}
setSelectedManga(manga) {
this.selectedManga = manga;
// Reset chapter/volume selection when manga changes
this.selectedChapterId = null;
this.selectedVolume = null;
}
setSelectedChapterById(chapterId) {
this.selectedChapterId = chapterId;
this.selectedVolume = null; // Can't have both
}
setSelectedVolumeNumber(volumeNumber) {
this.selectedVolume = volumeNumber;
this.selectedChapterId = null; // Can't have both
}
setImporting() {
this.status = 'importing';
this.errorMessage = null;
}
setImported() {
this.status = 'imported';
this.processedAt = new Date().toISOString();
this.errorMessage = null;
}
setError(message) {
this.status = 'error';
this.errorMessage = message;
}
// Export selection for API
getImportData() {
if (!this.isReadyForNewImport()) {
throw new Error('File is not ready for import');
}
const data = {
mangaId: this.selectedManga.id
};
if (this.selectedChapterId) {
data.chapterId = this.selectedChapterId;
}
if (this.selectedVolume !== null) {
data.volumeNumber = this.selectedVolume;
}
return data;
}
}