first comit
This commit is contained in:
commit
e3ef711383
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.env
|
||||
5
.env
Normal file
5
.env
Normal file
@ -0,0 +1,5 @@
|
||||
DB_NAME=manga_database
|
||||
DB_USER=root
|
||||
DB_PASSWORD=secret
|
||||
DB_HOST=localhost
|
||||
PORT=8080
|
||||
35
.gitlab-ci.yml
Normal file
35
.gitlab-ci.yml
Normal file
@ -0,0 +1,35 @@
|
||||
stages:
|
||||
- deploy
|
||||
|
||||
deploy_to_portainer:
|
||||
stage: deploy
|
||||
image:
|
||||
name: curlimages/curl:latest
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- echo "Installation de jq"
|
||||
- curl -L -o /usr/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
|
||||
- chmod +x /usr/bin/jq
|
||||
- echo "Début du déploiement vers Portainer"
|
||||
- echo "Téléchargement du fichier docker-compose.yml depuis le dépôt"
|
||||
- 'curl -s -H "PRIVATE-TOKEN: $CI_JOB_TOKEN" -o docker-compose.yml "$CI_PROJECT_URL/raw/$CI_COMMIT_REF_NAME/docker-compose.yml"'
|
||||
- echo "Mise à jour ou création de la stack dans Portainer"
|
||||
- |
|
||||
STACK_ID=$(curl -s -H "Authorization: Bearer $PORTAINER_TOKEN" "$PORTAINER_URL/stacks?filters=%7B%22Name%22%3A%5B%22$STACK_NAME%22%5D%7D" | jq -r '.[0].Id')
|
||||
if [ "$STACK_ID" = "null" ] || [ -z "$STACK_ID" ]; then
|
||||
echo "La stack n'existe pas, création en cours"
|
||||
STACK_CREATE=$(curl -s -X POST "$PORTAINER_URL/stacks?type=1&method=string&endpointId=$ENDPOINT_ID" \
|
||||
-H "Authorization: Bearer $PORTAINER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"Name\": \"$STACK_NAME\", \"StackFileContent\": \"$(cat docker-compose.yml | sed 's/"/\\"/g' | sed 's/$/\\n/' | tr -d '\n')\", \"Prune\": false}")
|
||||
echo "Réponse de création : $STACK_CREATE"
|
||||
else
|
||||
echo "La stack existe, mise à jour en cours"
|
||||
STACK_UPDATE=$(curl -s -X PUT "$PORTAINER_URL/stacks/$STACK_ID?endpointId=$ENDPOINT_ID" \
|
||||
-H "Authorization: Bearer $PORTAINER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"StackFileContent\": \"$(cat docker-compose.yml | sed 's/"/\\"/g' | sed 's/$/\\n/' | tr -d '\n')\", \"Prune\": false, \"PullImage\": true}")
|
||||
echo "Réponse de mise à jour : $STACK_UPDATE"
|
||||
fi
|
||||
only:
|
||||
- main
|
||||
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
8
.idea/dev manag.iml
Normal file
8
.idea/dev manag.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/dev manag.iml" filepath="$PROJECT_DIR$/.idea/dev manag.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/php.xml
Normal file
19
.idea/php.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
# Utiliser une image Node.js officielle comme image de base
|
||||
FROM node:18
|
||||
|
||||
# Créer un répertoire de travail dans le conteneur
|
||||
WORKDIR /app
|
||||
|
||||
# Copier les fichiers package.json et package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Installer les dépendances
|
||||
RUN npm install
|
||||
|
||||
# Copier le reste du code de l'application
|
||||
COPY . .
|
||||
|
||||
# Exposer le port sur lequel votre application s'exécute (assurez-vous que c'est le bon)
|
||||
EXPOSE 8080
|
||||
|
||||
# Démarrer l'application
|
||||
CMD [ "node", "app.js" ]
|
||||
20
app.js
Normal file
20
app.js
Normal file
@ -0,0 +1,20 @@
|
||||
const express = require('express');
|
||||
const sequelize = require('./config');
|
||||
const Manga = require('./models/manga');
|
||||
const Chapter = require('./models/chapter');
|
||||
const mangaRoutes = require('./routes/mangaRoutes');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
app.use(express.json());
|
||||
app.use('/', mangaRoutes);
|
||||
|
||||
sequelize.sync().then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Serveur en écoute sur le port ${PORT}`);
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error('Erreur lors de la synchronisation avec la base de données:', err);
|
||||
});
|
||||
16
config.js
Normal file
16
config.js
Normal file
@ -0,0 +1,16 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'manga_database',
|
||||
process.env.DB_USER || 'root',
|
||||
process.env.DB_PASSWORD || 'secret',
|
||||
{
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
dialect: 'mysql', // ou 'postgres', selon votre choix
|
||||
logging: false,
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = sequelize;
|
||||
177
connectors.json
Normal file
177
connectors.json
Normal file
@ -0,0 +1,177 @@
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"baseUrl": "https://www.lelmanga.com",
|
||||
"url": "https://www.lelmanga.com/manga",
|
||||
"name": "LelManga P4",
|
||||
"titleSelector": "div.tt",
|
||||
"imageMangaSelector": ".limit img",
|
||||
"attributManga": "src",
|
||||
"categorieElement": ".info-desc",
|
||||
"categorieAtribute": "a",
|
||||
"categories": ".mgen",
|
||||
"rate": ".rating-prc .num",
|
||||
"description": ".entry-content-single p",
|
||||
"paramSup": "?page=",
|
||||
"chapterPage": ".bsx a",
|
||||
"chapterSelector": "li[data-num]",
|
||||
"chapterNumSelector": "span.chapternum",
|
||||
"chapterTitleSelector": "a",
|
||||
"chapterDateSelector": "span.chapterdate",
|
||||
"imageSelector": "#readerarea noscript",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status":"Functional",
|
||||
"localisation": "fr"
|
||||
},
|
||||
|
||||
{
|
||||
"id": "2",
|
||||
"baseUrl": "https://scan-trad.com",
|
||||
"url": "https://scan-trad.com/manga",
|
||||
"name": "scan-trad.com P16",
|
||||
"titleSelector": ".series-name",
|
||||
"imageMangaSelector": ".series img",
|
||||
"attributManga": "data-src",
|
||||
"categorieElement": ".card-series-detail .col-6.col-md-12.mb-4",
|
||||
"categorieAtribute": "a",
|
||||
"categories": ".badge.bg-light.text-dark",
|
||||
"rate": ".rate-value span",
|
||||
"description": ".col-12 p",
|
||||
"paramSup": "?page=",
|
||||
"chapterPage": ".link-series",
|
||||
"chapterSelector": ".col-chapter",
|
||||
"chapterNumSelector": ".mb-0",
|
||||
"chapterTitleSelector": "a",
|
||||
"chapterDateSelector": "div.text-muted",
|
||||
"imageSelector": ".book-page",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status":"Functional",
|
||||
"localisation": "fr"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"baseUrl": "https://astral-manga.fr",
|
||||
"url": "https://astral-manga.fr/manga/",
|
||||
"name": "astral-manga P14",
|
||||
"titleSelector": ".manga .h5 a",
|
||||
"imageMangaSelector": ".manga img",
|
||||
"attributManga": "data-src",
|
||||
"categorieElement": ".genres-content",
|
||||
"categorieAtribute": "a",
|
||||
"categories": ".genres-content",
|
||||
"rate": ".allow_vote .total_votes",
|
||||
"description": ".manga-excerpt p",
|
||||
"paramSup": "page/",
|
||||
"chapterPage": ".manga .h5 a",
|
||||
"chapterSelector": ".listing-chapters_wrap ul li",
|
||||
"chapterNumSelector": ".wp-manga-chapter a",
|
||||
"chapterTitleSelector": "a",
|
||||
"chapterDateSelector": "span.chapter-release-date",
|
||||
"imageSelector": ".reading-content",
|
||||
"imageAttribute": "img.wp-manga-chapter-img",
|
||||
"navigatePageImage": true,
|
||||
"status":"Functional",
|
||||
"localisation": "fr"
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"baseUrl": "https://bato.to",
|
||||
"url": "https://bato.to/browse?genres=|gore,bloody,violence,ecchi,adult,mature,smut,hentai&langs=en&sort=update.za",
|
||||
"name": "bato.to P1184",
|
||||
"titleSelector": ".item-title",
|
||||
"imageMangaSelector": ".item-cover img",
|
||||
"attributManga": "src",
|
||||
"categorieElement": ".attr-item span span",
|
||||
"categorieAtribute": "span",
|
||||
"categories": ".attr-item span span",
|
||||
"rate": ".attr-main .attr-item span",
|
||||
"description": ".limit-html",
|
||||
"paramSup": "&page=",
|
||||
"chapterPage": ".item-cover",
|
||||
"chapterSelector": ".item",
|
||||
"chapterNumSelector": ".item a b",
|
||||
"chapterTitleSelector": ".chapt",
|
||||
"chapterDateSelector": ".item a b",
|
||||
"imageSelector": "#viewer",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status": "Functional",
|
||||
"localisation": "us"
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"baseUrl": "https://mangahub.io",
|
||||
"url": "https://mangahub.io/search/",
|
||||
"name": "mangahub.io [limit for moment preview max 6 img] P1946",
|
||||
"titleSelector": ".media .media-body h4 a",
|
||||
"imageMangaSelector": ".media .media-left img",
|
||||
"attributManga": "src",
|
||||
"categorieElement": "._3Czbn",
|
||||
"categorieAtribute": "a",
|
||||
"categories": "._3Czbn",
|
||||
"rate": "._3SlhO",
|
||||
"description": "h1 small",
|
||||
"paramSup": "page/",
|
||||
"chapterPage": ".media .media-left a",
|
||||
"chapterSelector": "ul li.list-group-item",
|
||||
"chapterNumSelector": "ul li.list-group-item ._2IG5P",
|
||||
"chapterTitleSelector": "ul li.list-group-item ._3pfyN",
|
||||
"chapterDateSelector": "ul li.list-group-item small",
|
||||
"imageSelector": "._2aWyJ",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status": "Functional",
|
||||
"localisation": "us"
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"baseUrl": "https://bato.to",
|
||||
"url": "https://bato.to/browse?genres=|gore,bloody,violence,ecchi,adult,mature,smut,hentai&langs=fr&sort=title.az",
|
||||
"name": "bato.to P36",
|
||||
"titleSelector": ".item-title",
|
||||
"imageMangaSelector": ".item-cover img",
|
||||
"attributManga": "src",
|
||||
"categorieElement": ".attr-item span span",
|
||||
"categorieAtribute": "span",
|
||||
"categories": ".attr-item span span",
|
||||
"rate": ".attr-main .attr-item span",
|
||||
"description": ".limit-html",
|
||||
"paramSup": "&page=",
|
||||
"chapterPage": ".item-cover",
|
||||
"chapterSelector": ".item",
|
||||
"chapterNumSelector": ".item a b",
|
||||
"chapterTitleSelector": ".chapt",
|
||||
"chapterDateSelector": ".item a b",
|
||||
"imageSelector": "#viewer",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status": "Functional",
|
||||
"localisation": "fr"
|
||||
},{
|
||||
"id": "8",
|
||||
"baseUrl": "https://toonclash.com",
|
||||
"url": "https://toonclash.com/manga/",
|
||||
"name": "toonclash.com P256",
|
||||
"titleSelector": ".post-title .h5",
|
||||
"imageMangaSelector": ".manga .c-image-hover img",
|
||||
"attributManga": "data-src",
|
||||
"categorieElement": ".genres-content",
|
||||
"categorieAtribute": "a",
|
||||
"categories": ".genres-content",
|
||||
"rate": "#averagerate",
|
||||
"description": ".description-summary .summary__content",
|
||||
"paramSup": "page/",
|
||||
"chapterPage": ".post-title .h5 a",
|
||||
"chapterSelector": ".listing-chapters_wrap ul li",
|
||||
"chapterNumSelector": ".listing-chapters_wrap ul li a",
|
||||
"chapterTitleSelector": ".listing-chapters_wrap ul li a",
|
||||
"chapterDateSelector": ".listing-chapters_wrap ul li a",
|
||||
"imageSelector": "div.reading-content",
|
||||
"imageAttribute": "img",
|
||||
"navigatePageImage": false,
|
||||
"status": "Functional",
|
||||
"localisation": "us"
|
||||
}
|
||||
]
|
||||
153
controllers/mangaController.js
Normal file
153
controllers/mangaController.js
Normal file
@ -0,0 +1,153 @@
|
||||
const axios = require('axios');
|
||||
const cheerio = require('cheerio');
|
||||
const Manga = require('../models/manga');
|
||||
const Chapter = require('../models/chapter');
|
||||
const connectors = require('../connectors.json');
|
||||
|
||||
class MangaController {
|
||||
constructor() {}
|
||||
|
||||
async fetchAndStoreMangas(connectorId, localisation) {
|
||||
const connectorsToFetch = connectors.filter((config) => {
|
||||
const matchesConnectorId = !connectorId || config.id === connectorId;
|
||||
const matchesLocalisation = !localisation || config.localisation === localisation;
|
||||
return matchesConnectorId && matchesLocalisation;
|
||||
});
|
||||
|
||||
for (const config of connectorsToFetch) {
|
||||
await this.fetchMangas(config);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMangas(config) {
|
||||
let page = 1;
|
||||
let shouldContinue = true;
|
||||
|
||||
while (shouldContinue) {
|
||||
const pageUrl = config.paramSup !== "null" ? `${config.url}${config.paramSup}${page}` : config.url;
|
||||
shouldContinue = config.paramSup !== "null";
|
||||
|
||||
try {
|
||||
const response = await axios.get(pageUrl);
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
const titleElements = $(config.titleSelector);
|
||||
const imageElements = $(config.imageMangaSelector);
|
||||
const linkElements = $(config.chapterPage);
|
||||
|
||||
if (!titleElements.length || !imageElements.length || !linkElements.length) break;
|
||||
|
||||
const mangasOnPage = [];
|
||||
|
||||
for (let i = 0; i < titleElements.length; i++) {
|
||||
const title = $(titleElements[i]).text().trim();
|
||||
const imageUrl = $(imageElements[i]).attr(config.attributManga) || '';
|
||||
let linkChapter = $(linkElements[i]).attr('href') || '';
|
||||
if (linkChapter.startsWith('/')) linkChapter = config.baseUrl + linkChapter;
|
||||
|
||||
if (!title) continue;
|
||||
|
||||
try {
|
||||
const mangaPageResponse = await axios.get(linkChapter);
|
||||
const $$ = cheerio.load(mangaPageResponse.data);
|
||||
|
||||
const description = $$(config.description).text().trim() || 'Not available';
|
||||
const categories = [];
|
||||
|
||||
const categorieElement = $$(config.categorieElement);
|
||||
const categoriesSelector = config.categorieElement === config.categories
|
||||
? $$(config.categorieElement)
|
||||
: categorieElement.find(config.categories);
|
||||
|
||||
categoriesSelector.each((_, elem) => {
|
||||
categories.push($$(elem).text().trim());
|
||||
});
|
||||
|
||||
const rate = $$(config.rate).text().trim() || '0';
|
||||
|
||||
const chapters = await this.fetchChapters(linkChapter, config);
|
||||
|
||||
// Enregistrer le manga dans la base de données
|
||||
const [manga, created] = await Manga.findOrCreate({
|
||||
where: { title },
|
||||
defaults: {
|
||||
connectorId: config.id,
|
||||
connectorName: config.name,
|
||||
imageUrl,
|
||||
linkChapter,
|
||||
description,
|
||||
categories,
|
||||
rate,
|
||||
},
|
||||
});
|
||||
|
||||
if (!created) {
|
||||
// Mettre à jour le manga existant
|
||||
await manga.update({
|
||||
connectorId: config.id,
|
||||
connectorName: config.name,
|
||||
imageUrl,
|
||||
linkChapter,
|
||||
description,
|
||||
categories,
|
||||
rate,
|
||||
});
|
||||
}
|
||||
|
||||
// Enregistrer les chapitres
|
||||
for (const chapterData of chapters) {
|
||||
await Chapter.findOrCreate({
|
||||
where: { chapterLink: chapterData.chapterLink },
|
||||
defaults: {
|
||||
...chapterData,
|
||||
mangaId: manga.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Erreur lors du traitement du manga ${title}: ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
page++;
|
||||
} catch (e) {
|
||||
console.error(`Erreur lors de la récupération de la page ${pageUrl}: ${e.message}`);
|
||||
shouldContinue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchChapters(linkChapter, config) {
|
||||
try {
|
||||
const response = await axios.get(linkChapter);
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
const chapterElements = $(config.chapterSelector);
|
||||
|
||||
const chapters = [];
|
||||
|
||||
chapterElements.each((_, elem) => {
|
||||
const chapterNum = $(elem).find(config.chapterNumSelector).text().trim() || 'N/A';
|
||||
let chapterLink = $(elem).find(config.chapterTitleSelector).attr('href') || '';
|
||||
const chapterTitle = $(elem).find(config.chapterTitleSelector).text().replace(/^\s+|\s+$/g, '') || 'N/A';
|
||||
const chapterDate = $(elem).find(config.chapterDateSelector).text().trim() || '';
|
||||
|
||||
if (chapterLink.startsWith('/')) chapterLink = config.baseUrl + chapterLink;
|
||||
console.log(chapterTitle)
|
||||
chapters.push({
|
||||
chapterNum,
|
||||
chapterTitle,
|
||||
chapterLink,
|
||||
chapterDate,
|
||||
});
|
||||
});
|
||||
|
||||
return chapters;
|
||||
} catch (e) {
|
||||
console.error(`Erreur lors de la récupération des chapitres: ${e.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MangaController();
|
||||
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
@ -0,0 +1,41 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- '8080:8080'
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_USER=root
|
||||
- DB_PASSWORD=secret
|
||||
- DB_NAME=manga_database
|
||||
- PORT=8080
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=secret
|
||||
- MYSQL_DATABASE=manga_database
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
ports:
|
||||
- '3306:3306'
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
restart: always
|
||||
ports:
|
||||
- '8081:80'
|
||||
environment:
|
||||
- PMA_HOST=db
|
||||
- PMA_USER=root
|
||||
- PMA_PASSWORD=secret
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
23
models/chapter.js
Normal file
23
models/chapter.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config');
|
||||
|
||||
const Chapter = sequelize.define('Chapter', {
|
||||
chapterNum: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
chapterTitle: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
chapterLink: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
chapterDate: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = Chapter;
|
||||
0
models/connectorConfig.js
Normal file
0
models/connectorConfig.js
Normal file
47
models/manga.js
Normal file
47
models/manga.js
Normal file
@ -0,0 +1,47 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config');
|
||||
const Chapter = require('./chapter');
|
||||
|
||||
const Manga = sequelize.define('Manga', {
|
||||
connectorId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
connectorName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
imageUrl: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
linkChapter: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
isFavorite: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
categories: {
|
||||
type: DataTypes.JSON, // Stocke la liste des catégories
|
||||
allowNull: false,
|
||||
},
|
||||
rate: {
|
||||
type: DataTypes.TEXT,
|
||||
defaultValue: '0',
|
||||
},
|
||||
});
|
||||
|
||||
Manga.hasMany(Chapter, { as: 'chapters', foreignKey: 'mangaId' });
|
||||
Chapter.belongsTo(Manga, { foreignKey: 'mangaId' });
|
||||
|
||||
module.exports = Manga;
|
||||
1359
package-lock.json
generated
Normal file
1359
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "manga_server",
|
||||
"version": "1.0.0",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"cheerio": "^1.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.0",
|
||||
"mysql2": "^3.11.3",
|
||||
"sequelize": "^6.37.3"
|
||||
}
|
||||
}
|
||||
60
routes/mangaRoutes.js
Normal file
60
routes/mangaRoutes.js
Normal file
@ -0,0 +1,60 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Manga = require('../models/manga');
|
||||
const Chapter = require('../models/chapter');
|
||||
const mangaController = require('../controllers/mangaController');
|
||||
const connectors = require('../connectors.json');
|
||||
|
||||
router.get('/fetch', async (req, res) => {
|
||||
const connectorId = req.query.connectorId;
|
||||
const localisation = req.query.localisation;
|
||||
|
||||
await mangaController.fetchAndStoreMangas(connectorId, localisation);
|
||||
res.send('Données récupérées avec succès');
|
||||
});
|
||||
|
||||
router.get('/mangas', async (req, res) => {
|
||||
const connectorId = req.query.connectorId;
|
||||
const localisation = req.query.localisation;
|
||||
|
||||
let whereClause = {};
|
||||
|
||||
if (connectorId) {
|
||||
whereClause.connectorId = connectorId;
|
||||
}
|
||||
|
||||
if (localisation) {
|
||||
const connectorsForLoc = connectors.filter((config) => config.localisation === localisation);
|
||||
const connectorNames = connectorsForLoc.map((c) => c.name);
|
||||
|
||||
if (connectorNames.length > 0) {
|
||||
whereClause.connectorName = connectorNames;
|
||||
} else {
|
||||
return res.json([]);
|
||||
}
|
||||
}
|
||||
|
||||
const mangas = await Manga.findAll({
|
||||
where: whereClause,
|
||||
include: [{ model: Chapter, as: 'chapters' }],
|
||||
});
|
||||
|
||||
res.json(mangas);
|
||||
});
|
||||
|
||||
router.get('/mangas/:title', async (req, res) => {
|
||||
const title = req.params.title;
|
||||
|
||||
const manga = await Manga.findOne({
|
||||
where: { title },
|
||||
include: [{ model: Chapter, as: 'chapters' }],
|
||||
});
|
||||
|
||||
if (!manga) {
|
||||
return res.status(404).send('Manga non trouvé');
|
||||
}
|
||||
|
||||
res.json(manga);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Reference in New Issue
Block a user