feat: ajout de la gestion des sources de contenu avec création de composants, formulaires et API pour l'importation, l'exportation et la configuration des sources de scraping.

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-06-27 16:40:48 +02:00
parent 32b4e4fbb2
commit dac2f91998
15 changed files with 1364 additions and 15 deletions

View File

@@ -0,0 +1,55 @@
/**
* Types de scraping disponibles
*/
export const SCRAPING_TYPES = {
HTML: 'html',
JAVASCRIPT: 'javascript'
};
/**
* Orientations de lecture
*/
export const READING_ORIENTATIONS = {
VERTICAL: 'vertical',
HORIZONTAL: 'horizontal'
};
/**
* États des sources
*/
export const SOURCE_STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive',
ERROR: 'error'
};
/**
* Configuration par défaut pour une nouvelle source
*/
export const DEFAULT_SOURCE_CONFIG = {
baseUrl: '',
chapterUrlFormat: '',
scrapingType: SCRAPING_TYPES.HTML,
imageSelector: '',
nextPageSelector: '',
chapterSelector: '',
token: ''
};
/**
* Validateurs pour les champs
*/
export const VALIDATORS = {
URL_PATTERN: /^https?:\/\/.+/,
SELECTOR_PATTERN: /^[.#]?[\w\-\s.#\[\]=\"':()>+~,]+$/
};
/**
* Messages d'erreur
*/
export const ERROR_MESSAGES = {
INVALID_URL: 'L\'URL doit commencer par http:// ou https://',
INVALID_SELECTOR: 'Le sélecteur CSS n\'est pas valide',
REQUIRED_FIELD: 'Ce champ est obligatoire',
CHAPTER_URL_FORMAT_REQUIRED: 'Le format d\'URL des chapitres est obligatoire'
};

View File

@@ -0,0 +1,116 @@
/**
* Modèle représentant une source de contenu pour le scraping
*/
export class ContentSource {
constructor({
id = null,
baseUrl = '',
chapterUrlFormat = '',
scrapingType = 'HTML',
imageSelector = null,
nextPageSelector = null,
chapterSelector = null,
cleanBaseUrl = '',
token = null
} = {}) {
this.id = id;
this.baseUrl = baseUrl;
this.chapterUrlFormat = chapterUrlFormat;
this.scrapingType = scrapingType;
this.imageSelector = imageSelector;
this.nextPageSelector = nextPageSelector;
this.chapterSelector = chapterSelector;
this.cleanBaseUrl = cleanBaseUrl || this.extractCleanBaseUrl(baseUrl);
this.token = token;
}
/**
* Extrait une URL propre à partir de l'URL de base
*/
extractCleanBaseUrl(url) {
if (!url) return '';
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (error) {
return url;
}
}
/**
* Vérifie si la source est valide
*/
isValid() {
return !!(this.baseUrl && this.chapterUrlFormat);
}
/**
* Vérifie si la source est de type JavaScript
*/
isJavascriptSource() {
return this.scrapingType === 'Javascript';
}
/**
* Vérifie si la source est de type HTML
*/
isHtmlSource() {
return this.scrapingType === 'HTML';
}
/**
* Détermine l'orientation basée sur les sélecteurs
*/
getOrientation() {
return this.nextPageSelector ? 'vertical' : 'horizontal';
}
/**
* Génère une URL de chapitre basée sur le format
*/
generateChapterUrl(slug, chapterNumber) {
return this.chapterUrlFormat
.replace('{slug}', slug)
.replace('{chapterNumber}', chapterNumber);
}
/**
* Convertit l'objet en format API
*/
toApiFormat() {
return {
baseUrl: this.baseUrl,
chapterUrlFormat: this.chapterUrlFormat,
scrapingType: this.scrapingType,
imageSelector: this.imageSelector,
nextPageSelector: this.nextPageSelector,
chapterSelector: this.chapterSelector,
token: this.token
};
}
/**
* Crée une instance depuis les données API
*/
static fromApiData(data) {
return new ContentSource(data);
}
/**
* Clone l'instance
*/
clone() {
return new ContentSource({
id: this.id,
baseUrl: this.baseUrl,
chapterUrlFormat: this.chapterUrlFormat,
scrapingType: this.scrapingType,
imageSelector: this.imageSelector,
nextPageSelector: this.nextPageSelector,
chapterSelector: this.chapterSelector,
cleanBaseUrl: this.cleanBaseUrl,
token: this.token
});
}
}