Updated npm on new server
This commit is contained in:
parent
cffab8977e
commit
7d0852b9f8
@ -2,7 +2,7 @@
|
||||
|
||||
const dbConfig = {
|
||||
mysqldb: {
|
||||
host: '192.168.0.170',
|
||||
host: '192.168.0.210',
|
||||
port: 3306,
|
||||
database: 'sunnymh_mysql',
|
||||
user: 'sunnymh_mysql',
|
||||
|
||||
13
db.js
13
db.js
@ -1,13 +0,0 @@
|
||||
const config = require('./config');
|
||||
|
||||
class DB {
|
||||
constructor() {
|
||||
this.config = null;
|
||||
}
|
||||
|
||||
loadConfig() {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DB;
|
||||
201
db_api.jsx
201
db_api.jsx
@ -1,45 +1,202 @@
|
||||
const express = require('express');
|
||||
const MySQLDB = require('./mysqldb');
|
||||
const databaseService = require('./src/services/database.service');
|
||||
|
||||
// Configuration
|
||||
const PORT = process.env.PORT || 4000;
|
||||
|
||||
// Initialize Express app
|
||||
const app = express();
|
||||
const port = 4000;
|
||||
|
||||
app.use(express.json()); // Middleware to parse JSON
|
||||
|
||||
// Initialize MySQL database connection
|
||||
const mySQLDB = new MySQLDB();
|
||||
app.use(express.json());
|
||||
|
||||
async function initializeDatabases() {
|
||||
try {
|
||||
await mySQLDB.createConnection();
|
||||
await databaseService.createConnection();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize databases:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/Pic', async (req, res) => {
|
||||
// Routes
|
||||
app.get('/manga/:id', async (req, res) => {
|
||||
const mangaId = req.params.id;
|
||||
|
||||
try {
|
||||
const sqlQuery = `SELECT * FROM manga_table WHERE manga_id = ${mySQLDB.connection.escape(mangaId)}`;
|
||||
const mySQLResults = await mySQLDB.query(sqlQuery);
|
||||
const sqlQuery = 'SELECT * FROM mangas WHERE mangaId = ?';
|
||||
const mySQLResults = await databaseService.query(sqlQuery, [mangaId]);
|
||||
|
||||
res.json({ mySQLResults });
|
||||
if (!mySQLResults || mySQLResults.length === 0) {
|
||||
return res.status(404).json({ error: 'Manga not found' });
|
||||
}
|
||||
|
||||
res.json({ data: mySQLResults[0] });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.error('Error fetching manga:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/insert', async (req, res) => {
|
||||
const { mangaId, mangaName } = req.body;
|
||||
console.log(mangaId, mangaName);
|
||||
app.get('/getMangaId/:mangaName', async (req, res) => {
|
||||
const mangaName = req.params.mangaName;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT mangaId FROM mangas WHERE mangaName = ?';
|
||||
const mySQLResults = await databaseService.query(sqlQuery, [mangaName]);
|
||||
|
||||
if (!mySQLResults || mySQLResults.length === 0) {
|
||||
return res.status(404).json({ error: 'Manga not found' });
|
||||
}
|
||||
|
||||
res.json({ data: mySQLResults[0] });
|
||||
} catch (error) {
|
||||
console.error('Error getting manga ID:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Start server after initializing databases
|
||||
initializeDatabases().then(() => {
|
||||
app.listen(port, () => {
|
||||
console.log(`API server listening at http://localhost:${port}`);
|
||||
app.get('/getGenres/:genre', async (req, res) => {
|
||||
const genre = req.params.genre;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT genreId FROM genres WHERE genreName = ?';
|
||||
const mySQLResults = await databaseService.query(sqlQuery, [genre]);
|
||||
|
||||
if (!mySQLResults || mySQLResults.length === 0) {
|
||||
return res.status(404).json({ error: 'Genre not found' });
|
||||
}
|
||||
|
||||
res.json({ data: mySQLResults[0] });
|
||||
} catch (error) {
|
||||
console.error('Error getting genre:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/insertGenre', async (req, res) => {
|
||||
const genre = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'INSERT INTO genres (genreName) VALUES (?)';
|
||||
const mySQLResults = await databaseService.query(sqlQuery, [genre.genreName]);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Genre inserted successfully',
|
||||
genreId: mySQLResults.insertId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting genre:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/insertMangaGenres', async (req, res) => {
|
||||
const genres = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
INSERT INTO manga_genres (
|
||||
mangaId,
|
||||
genreId
|
||||
) VALUES (?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
genres.mangaId,
|
||||
genres.genreId
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manga genres inserted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting manga genres:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/insertMangaChapter', async (req, res) => {
|
||||
const chapterInfo = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
INSERT INTO chapters (
|
||||
mangaId,
|
||||
chapterOrder,
|
||||
chapterName
|
||||
) VALUES (?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
chapterInfo.mangaId,
|
||||
chapterInfo.chapterOrder,
|
||||
chapterInfo.chapterName
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Chapter inserted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting chapter:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/insertMangaInfo', async (req, res) => {
|
||||
const mangaInfo = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
INSERT INTO mangas (
|
||||
mangaId,
|
||||
mangaName,
|
||||
mangaAuthor,
|
||||
mangaNickname,
|
||||
mangaStatus
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
mangaName = VALUES(mangaName),
|
||||
mangaAuthor = VALUES(mangaAuthor),
|
||||
mangaNickname = VALUES(mangaNickname),
|
||||
mangaStatus = VALUES(mangaStatus)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
mangaInfo.mangaId,
|
||||
mangaInfo.name,
|
||||
mangaInfo.author,
|
||||
JSON.stringify(mangaInfo.nickNames),
|
||||
mangaInfo.status
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manga details inserted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting manga details:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Unhandled error:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
initializeDatabases()
|
||||
.then(() => {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API server listening at http://localhost:${PORT}`);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
49
mysqldb.js
49
mysqldb.js
@ -1,49 +0,0 @@
|
||||
const DB = require("./db");
|
||||
const mysql = require('mysql2');
|
||||
|
||||
class MySQLDB extends DB {
|
||||
constructor() {
|
||||
super();
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
async createConnection() {
|
||||
this.loadConfig();
|
||||
const { mysqldb } = this.config;
|
||||
this.connection = mysql.createConnection({
|
||||
host: mysqldb.host,
|
||||
port: mysqldb.port,
|
||||
user: mysqldb.user,
|
||||
password: mysqldb.password,
|
||||
database: mysqldb.database,
|
||||
connectTimeout: 0 // disable timeout
|
||||
});
|
||||
await this.connection.connect();
|
||||
}
|
||||
|
||||
async closeConnection() {
|
||||
if (this.connection) {
|
||||
await this.connection.end();
|
||||
this.connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
async query(query) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Database not connected');
|
||||
}
|
||||
const results = await new Promise((resolve, reject) => {
|
||||
this.connection.query(query, (err, rows) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MySQLDB;
|
||||
|
||||
4018
package-lock.json
generated
4018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@ -1,17 +1,23 @@
|
||||
{
|
||||
"name": "sunnymh-db",
|
||||
"name": "sunnymh-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "db_api.jsx",
|
||||
"description": "Backend API for SunnyMH manga application",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node db_api.jsx",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "node src/app.js",
|
||||
"dev": "nodemon src/app.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.2",
|
||||
"mongodb": "^6.15.0",
|
||||
"mysql2": "^3.14.0"
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
}
|
||||
|
||||
43
src/app.js
Normal file
43
src/app.js
Normal file
@ -0,0 +1,43 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const config = require('./config');
|
||||
const { errorHandler, notFoundHandler } = require('./middleware/error.middleware');
|
||||
const mangaRoutes = require('./routes/manga.routes');
|
||||
const genreRoutes = require('./routes/genre.routes');
|
||||
const pictureRoutes = require('./routes/picture.routes');
|
||||
const databaseService = require('./services/database.service');
|
||||
|
||||
// Initialize Express app
|
||||
const app = express();
|
||||
|
||||
// Enable CORS for all routes
|
||||
app.use(cors());
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Routes
|
||||
app.use('/manga', mangaRoutes);
|
||||
app.use('/genre', genreRoutes);
|
||||
app.use('/pic', pictureRoutes);
|
||||
|
||||
// Error handling
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
// Start server
|
||||
async function startServer() {
|
||||
try {
|
||||
// Initialize database connection
|
||||
await databaseService.createConnection();
|
||||
console.log('Database connection established');
|
||||
|
||||
app.listen(config.port, () => {
|
||||
console.log(`API server listening at http://localhost:${config.port}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
startServer();
|
||||
14
src/config/index.js
Normal file
14
src/config/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
require('dotenv').config();
|
||||
|
||||
module.exports = {
|
||||
port: process.env.PORT || 4000,
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
mysqldb: {
|
||||
host: process.env.DB_HOST || '192.168.0.210',
|
||||
port: parseInt(process.env.DB_PORT || '3306', 10),
|
||||
user: process.env.DB_USER || 'sunnymh_mysql',
|
||||
password: process.env.DB_PASSWORD || 'hengserver',
|
||||
database: process.env.DB_NAME || 'sunnymh_mysql'
|
||||
},
|
||||
picturePath: process.env.PICTURE_PATH || 'Y:'
|
||||
};
|
||||
24
src/middleware/error.middleware.js
Normal file
24
src/middleware/error.middleware.js
Normal file
@ -0,0 +1,24 @@
|
||||
// Async handler wrapper to eliminate try-catch blocks
|
||||
const asyncHandler = (fn) => (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
|
||||
// Error handling middleware
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
console.error('Unhandled error:', err);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||
});
|
||||
};
|
||||
|
||||
// Not found middleware
|
||||
const notFoundHandler = (req, res) => {
|
||||
res.status(404).json({ error: 'Route not found' });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
asyncHandler,
|
||||
errorHandler,
|
||||
notFoundHandler
|
||||
};
|
||||
52
src/routes/genre.routes.js
Normal file
52
src/routes/genre.routes.js
Normal file
@ -0,0 +1,52 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const databaseService = require('../services/database.service');
|
||||
|
||||
// **** For frontend ****
|
||||
|
||||
|
||||
// **** For scraper ****
|
||||
router.get('/getGenreId/:genreName', async (req, res) => {
|
||||
const genre = req.params.genreName;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT genreId FROM genres WHERE genreName = ?';
|
||||
const results = await databaseService.query(sqlQuery, [genre]);
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
message: 'Genre not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Genre found',
|
||||
genreId: results[0].genreId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting genre:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Insert new genre
|
||||
router.post('/insertGenre', async (req, res) => {
|
||||
const genre = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'INSERT INTO genres (genreName) VALUES (?)';
|
||||
const results = await databaseService.query(sqlQuery, [genre.genreName]);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Genre inserted successfully',
|
||||
genreId: results.insertId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting genre:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
181
src/routes/manga.routes.js
Normal file
181
src/routes/manga.routes.js
Normal file
@ -0,0 +1,181 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const databaseService = require('../services/database.service');
|
||||
|
||||
// **** For frontend ****
|
||||
router.get('/getMangaInfo/:mangaId', async (req, res) => {
|
||||
const mangaId = req.params.mangaId;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
SELECT m.*, GROUP_CONCAT(g.genreName) as genreNames
|
||||
FROM mangas m
|
||||
LEFT JOIN manga_genres mg ON m.mangaId = mg.mangaId
|
||||
LEFT JOIN genres g ON mg.genreId = g.genreId
|
||||
WHERE m.mangaId = ?
|
||||
GROUP BY m.mangaId
|
||||
`;
|
||||
const results = await databaseService.query(sqlQuery, [mangaId]);
|
||||
|
||||
if (!results[0]) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Manga not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Convert genreNames string to array
|
||||
const mangaInfo = {
|
||||
...results[0],
|
||||
genres: results[0].genreNames ? results[0].genreNames.split(',') : []
|
||||
};
|
||||
|
||||
// Remove the raw genreNames field
|
||||
delete mangaInfo.genreNames;
|
||||
|
||||
return res.json(mangaInfo);
|
||||
} catch (error) {
|
||||
console.error('Error getting manga info:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.get('/getMangaChapters/:mangaId', async (req, res) => {
|
||||
const mangaId = req.params.mangaId;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT * FROM chapters WHERE mangaId = ?';
|
||||
const results = await databaseService.query(sqlQuery, [mangaId]);
|
||||
return res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error getting manga chapters:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// **** For scraper ****
|
||||
router.get('/getMangaId/:mangaName', async (req, res) => {
|
||||
const mangaName = req.params.mangaName;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT mangaId FROM mangas WHERE mangaName = ?';
|
||||
const results = await databaseService.query(sqlQuery, [mangaName]);
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
return res.json({
|
||||
success: false,
|
||||
message: 'Manga not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manga found',
|
||||
mangaId: results[0].mangaId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting manga ID:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Insert manga info
|
||||
router.post('/insertMangaInfo', async (req, res) => {
|
||||
const mangaInfo = req.body;
|
||||
|
||||
try {
|
||||
const mangaNickname = Array.isArray(mangaInfo.mangaNickname)
|
||||
? mangaInfo.mangaNickname.join(',')
|
||||
: mangaInfo.mangaNickname;
|
||||
|
||||
const sqlQuery = `
|
||||
INSERT INTO mangas (
|
||||
mangaId,
|
||||
mangaName,
|
||||
mangaAuthor,
|
||||
mangaNickname,
|
||||
mangaStatus
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
mangaInfo.mangaId,
|
||||
mangaInfo.mangaName,
|
||||
mangaInfo.mangaAuthor,
|
||||
mangaNickname,
|
||||
mangaInfo.mangaStatus
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manga details inserted successfully',
|
||||
mangaId: mangaInfo.mangaId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting manga details:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Insert manga chapter
|
||||
router.post('/insertMangaChapter', async (req, res) => {
|
||||
const chapterInfo = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
INSERT INTO chapters (
|
||||
mangaId,
|
||||
chapterOrder,
|
||||
chapterName
|
||||
) VALUES (?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
chapterInfo.mangaId,
|
||||
chapterInfo.chapterOrder,
|
||||
chapterInfo.chapterName
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Chapter inserted successfully',
|
||||
chapterId: chapterInfo.chapterId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting chapter:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Insert manga genres
|
||||
router.post('/insertMangaGenres', async (req, res) => {
|
||||
const genres = req.body;
|
||||
|
||||
try {
|
||||
const sqlQuery = `
|
||||
INSERT INTO manga_genres (
|
||||
mangaId,
|
||||
genreId
|
||||
) VALUES (?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
genres.mangaId,
|
||||
genres.genreId
|
||||
];
|
||||
|
||||
await databaseService.query(sqlQuery, params);
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Manga genres inserted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error inserting manga genres:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
151
src/routes/picture.routes.js
Normal file
151
src/routes/picture.routes.js
Normal file
@ -0,0 +1,151 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const config = require('../config');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const databaseService = require('../services/database.service');
|
||||
|
||||
|
||||
// **** For frontend ****
|
||||
router.get('/:mangaId', async (req, res) => {
|
||||
const mangaId = req.params.mangaId;
|
||||
|
||||
try {
|
||||
const sqlQuery = 'SELECT mangaName FROM mangas WHERE mangaId = ?';
|
||||
const results = await databaseService.query(sqlQuery, [mangaId]);
|
||||
|
||||
if (!results[0]) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Manga not found'
|
||||
});
|
||||
}
|
||||
|
||||
const mangaName = results[0].mangaName;
|
||||
const picturePath = `${config.picturePath}\\${mangaName}/`;
|
||||
const coverPicture = fs.readFileSync(path.join(picturePath, 'cover.jpg'));
|
||||
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
return res.send(coverPicture);
|
||||
} catch (error) {
|
||||
console.error('Error getting picture:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:mangaId/:chapterOrder', async (req, res) => {
|
||||
const mangaId = req.params.mangaId;
|
||||
const chapterOrder = req.params.chapterOrder;
|
||||
|
||||
try {
|
||||
// Get manga name and chapter name from database
|
||||
const sqlQuery = `
|
||||
SELECT m.mangaName, c.chapterName
|
||||
FROM mangas m
|
||||
JOIN chapters c ON m.mangaId = c.mangaId
|
||||
WHERE m.mangaId = ? AND c.chapterOrder = ?
|
||||
`;
|
||||
const results = await databaseService.query(sqlQuery, [mangaId, chapterOrder]);
|
||||
|
||||
if (!results[0]) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Manga or chapter not found'
|
||||
});
|
||||
}
|
||||
|
||||
const mangaName = results[0].mangaName;
|
||||
const chapterName = results[0].chapterName;
|
||||
const chapterPath = path.join(config.picturePath, mangaName, chapterName);
|
||||
|
||||
// Check if directory exists
|
||||
if (!fs.existsSync(chapterPath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Chapter directory not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Read all files in the directory
|
||||
const files = fs.readdirSync(chapterPath);
|
||||
|
||||
// Filter for image files and sort them
|
||||
const imageFiles = files
|
||||
.filter(file => /\.(jpg|jpeg|png|webp)$/i.test(file))
|
||||
.sort((a, b) => {
|
||||
// Extract numbers from filenames for proper sorting
|
||||
const numA = parseInt(a.match(/\d+/)[0]);
|
||||
const numB = parseInt(b.match(/\d+/)[0]);
|
||||
return numA - numB;
|
||||
});
|
||||
|
||||
// Return the image paths
|
||||
const imagePaths = imageFiles.map(file => (file));
|
||||
return res.json(imagePaths);
|
||||
} catch (error) {
|
||||
console.error('Error getting chapter pictures:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:mangaId/:chapterOrder/:picName', async (req, res) => {
|
||||
const mangaId = req.params.mangaId;
|
||||
const chapterOrder = req.params.chapterOrder;
|
||||
const picName = req.params.picName;
|
||||
|
||||
try {
|
||||
// Get manga name and chapter name from database
|
||||
const sqlQuery = `
|
||||
SELECT m.mangaName, c.chapterName
|
||||
FROM mangas m
|
||||
JOIN chapters c ON m.mangaId = c.mangaId
|
||||
WHERE m.mangaId = ? AND c.chapterOrder = ?
|
||||
`;
|
||||
const results = await databaseService.query(sqlQuery, [mangaId, chapterOrder]);
|
||||
|
||||
if (!results[0]) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Manga or chapter not found'
|
||||
});
|
||||
}
|
||||
|
||||
const mangaName = results[0].mangaName;
|
||||
const chapterName = results[0].chapterName;
|
||||
const chapterPath = path.join(config.picturePath, mangaName, chapterName);
|
||||
|
||||
// Check if directory exists
|
||||
if (!fs.existsSync(chapterPath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Chapter directory not found'
|
||||
});
|
||||
}
|
||||
|
||||
const picPath = path.join(chapterPath, picName);
|
||||
const pic = fs.readFileSync(picPath);
|
||||
|
||||
// Set appropriate content type based on file extension
|
||||
const ext = path.extname(picName).toLowerCase();
|
||||
const contentType = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.webp': 'image/webp'
|
||||
}[ext] || 'application/octet-stream';
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
return res.send(pic);
|
||||
} catch (error) {
|
||||
console.error('Error getting picture:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// **** For scraper ****
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
117
src/services/database.service.js
Normal file
117
src/services/database.service.js
Normal file
@ -0,0 +1,117 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const config = require('../config');
|
||||
|
||||
class DatabaseService {
|
||||
constructor() {
|
||||
this.pool = null;
|
||||
this.config = config;
|
||||
this.lastRefreshTime = null;
|
||||
this.REFRESH_INTERVAL = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||
}
|
||||
|
||||
async createConnection() {
|
||||
try {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Check if we need to refresh the connection
|
||||
if (!this.pool || !this.lastRefreshTime ||
|
||||
(currentTime - this.lastRefreshTime) > this.REFRESH_INTERVAL) {
|
||||
|
||||
// Close existing pool if it exists
|
||||
if (this.pool) {
|
||||
await this.closeConnection();
|
||||
}
|
||||
|
||||
const { mysqldb } = this.config;
|
||||
|
||||
if (!this.isValidConfig(mysqldb)) {
|
||||
throw new Error('Invalid database configuration');
|
||||
}
|
||||
|
||||
this.pool = mysql.createPool({
|
||||
host: mysqldb.host,
|
||||
port: mysqldb.port,
|
||||
user: mysqldb.user,
|
||||
password: mysqldb.password,
|
||||
database: mysqldb.database,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 0
|
||||
});
|
||||
|
||||
this.lastRefreshTime = currentTime;
|
||||
console.log('Database connection pool refreshed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create MySQL connection pool:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
isValidConfig(config) {
|
||||
return config
|
||||
&& typeof config.host === 'string'
|
||||
&& typeof config.port === 'number'
|
||||
&& typeof config.user === 'string'
|
||||
&& typeof config.password === 'string'
|
||||
&& typeof config.database === 'string';
|
||||
}
|
||||
|
||||
async closeConnection() {
|
||||
if (this.pool) {
|
||||
try {
|
||||
await this.pool.end();
|
||||
this.pool = null;
|
||||
this.lastRefreshTime = null;
|
||||
} catch (error) {
|
||||
console.error('Error closing MySQL connection pool:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async query(query, params = []) {
|
||||
if (!this.pool) {
|
||||
await this.createConnection();
|
||||
}
|
||||
|
||||
try {
|
||||
const [results] = await this.pool.query(query, params);
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('Database query error:', {
|
||||
query,
|
||||
params,
|
||||
error: error.message
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async transaction(callback) {
|
||||
if (!this.pool) {
|
||||
await this.createConnection();
|
||||
}
|
||||
|
||||
const connection = await this.pool.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
try {
|
||||
const result = await callback(connection);
|
||||
await connection.commit();
|
||||
return result;
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a singleton instance
|
||||
const databaseService = new DatabaseService();
|
||||
|
||||
module.exports = databaseService;
|
||||
18
src/services/genre.service.js
Normal file
18
src/services/genre.service.js
Normal file
@ -0,0 +1,18 @@
|
||||
class GenreService {
|
||||
|
||||
async getGenreByName(db, genreName) {
|
||||
const sqlQuery = 'SELECT genreId FROM genres WHERE genreName = ?';
|
||||
const results = await db.query(sqlQuery, [genreName]);
|
||||
return results[0];
|
||||
}
|
||||
|
||||
async insertGenre(db, genre) {
|
||||
const sqlQuery = 'INSERT INTO genres (genreName) VALUES (?)';
|
||||
const results = await db.query(sqlQuery, [genre.genreName]);
|
||||
return {
|
||||
genreId: results.insertId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new GenreService();
|
||||
72
src/services/manga.service.js
Normal file
72
src/services/manga.service.js
Normal file
@ -0,0 +1,72 @@
|
||||
class MangaService {
|
||||
|
||||
async getMangaById(db, mangaId) {
|
||||
const sqlQuery = 'SELECT * FROM mangas WHERE mangaId = ?';
|
||||
const results = await db.query(sqlQuery, [mangaId]);
|
||||
return results[0];
|
||||
}
|
||||
|
||||
async getMangaIdByName(db, mangaName) {
|
||||
const sqlQuery = 'SELECT mangaId FROM mangas WHERE mangaName = ?';
|
||||
const results = await db.query(sqlQuery, [mangaName]);
|
||||
return results[0];
|
||||
}
|
||||
|
||||
async insertMangaInfo(db, mangaInfo) {
|
||||
const sqlQuery = `
|
||||
INSERT INTO mangas (
|
||||
mangaId,
|
||||
mangaName,
|
||||
mangaAuthor,
|
||||
mangaNickname,
|
||||
mangaStatus
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
mangaInfo.mangaId,
|
||||
mangaInfo.name,
|
||||
mangaInfo.author,
|
||||
JSON.stringify(mangaInfo.nickNames),
|
||||
mangaInfo.status
|
||||
];
|
||||
|
||||
return await db.query(sqlQuery, params);
|
||||
}
|
||||
|
||||
async insertMangaChapter(db, chapterInfo) {
|
||||
const sqlQuery = `
|
||||
INSERT INTO chapters (
|
||||
mangaId,
|
||||
chapterOrder,
|
||||
chapterName
|
||||
) VALUES (?, ?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
chapterInfo.mangaId,
|
||||
chapterInfo.chapterOrder,
|
||||
chapterInfo.chapterName
|
||||
];
|
||||
|
||||
return await db.query(sqlQuery, params);
|
||||
}
|
||||
|
||||
async insertMangaGenres(db, genres) {
|
||||
const sqlQuery = `
|
||||
INSERT INTO manga_genres (
|
||||
mangaId,
|
||||
genreId
|
||||
) VALUES (?, ?)
|
||||
`;
|
||||
|
||||
const params = [
|
||||
genres.mangaId,
|
||||
genres.genreId
|
||||
];
|
||||
|
||||
return await db.query(sqlQuery, params);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MangaService();
|
||||
Loading…
x
Reference in New Issue
Block a user