Updated npm on new server
This commit is contained in:
parent
cffab8977e
commit
7d0852b9f8
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const dbConfig = {
|
const dbConfig = {
|
||||||
mysqldb: {
|
mysqldb: {
|
||||||
host: '192.168.0.170',
|
host: '192.168.0.210',
|
||||||
port: 3306,
|
port: 3306,
|
||||||
database: 'sunnymh_mysql',
|
database: 'sunnymh_mysql',
|
||||||
user: '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 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 app = express();
|
||||||
const port = 4000;
|
app.use(express.json());
|
||||||
|
|
||||||
app.use(express.json()); // Middleware to parse JSON
|
|
||||||
|
|
||||||
// Initialize MySQL database connection
|
|
||||||
const mySQLDB = new MySQLDB();
|
|
||||||
|
|
||||||
async function initializeDatabases() {
|
async function initializeDatabases() {
|
||||||
try {
|
try {
|
||||||
await mySQLDB.createConnection();
|
await databaseService.createConnection();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize databases:', error);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get('/Pic', async (req, res) => {
|
// Routes
|
||||||
|
app.get('/manga/:id', async (req, res) => {
|
||||||
const mangaId = req.params.id;
|
const mangaId = req.params.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sqlQuery = `SELECT * FROM manga_table WHERE manga_id = ${mySQLDB.connection.escape(mangaId)}`;
|
const sqlQuery = 'SELECT * FROM mangas WHERE mangaId = ?';
|
||||||
const mySQLResults = await mySQLDB.query(sqlQuery);
|
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) {
|
} 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) => {
|
app.get('/getMangaId/:mangaName', async (req, res) => {
|
||||||
const { mangaId, mangaName } = req.body;
|
const mangaName = req.params.mangaName;
|
||||||
console.log(mangaId, 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
|
app.get('/getGenres/:genre', async (req, res) => {
|
||||||
initializeDatabases().then(() => {
|
const genre = req.params.genre;
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`API server listening at http://localhost:${port}`);
|
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",
|
"version": "1.0.0",
|
||||||
"main": "db_api.jsx",
|
"description": "Backend API for SunnyMH manga application",
|
||||||
|
"main": "src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node db_api.jsx",
|
"start": "node src/app.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"dev": "nodemon src/app.js",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.21.2",
|
"cors": "^2.8.5",
|
||||||
"mongodb": "^6.15.0",
|
"dotenv": "^16.5.0",
|
||||||
"mysql2": "^3.14.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