feat: analyse import + all tests fixed
This commit is contained in:
parent
fbe9619224
commit
3170a7c60e
200
assets/vue/app/domain/import/domain/entities/FileImport.js
Normal file
200
assets/vue/app/domain/import/domain/entities/FileImport.js
Normal 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;
|
||||
}
|
||||
}
|
||||
239
assets/vue/app/domain/import/domain/entities/ImportFile.js
Normal file
239
assets/vue/app/domain/import/domain/entities/ImportFile.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user