212 lines
8.7 KiB
JavaScript
212 lines
8.7 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.coverRoutes = void 0;
|
|
const express_1 = require("express");
|
|
const multer_1 = __importDefault(require("multer"));
|
|
const path_1 = __importDefault(require("path"));
|
|
const fs_1 = __importDefault(require("fs"));
|
|
const sharp_1 = __importDefault(require("sharp"));
|
|
const axios_1 = __importDefault(require("axios"));
|
|
exports.coverRoutes = (0, express_1.Router)();
|
|
// Configure multer for file uploads
|
|
const storage = multer_1.default.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
const uploadDir = path_1.default.join(process.cwd(), 'uploads', 'covers');
|
|
if (!fs_1.default.existsSync(uploadDir)) {
|
|
fs_1.default.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
cb(null, uploadDir);
|
|
},
|
|
filename: (req, file, cb) => {
|
|
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1E9)}`;
|
|
cb(null, `cover-${uniqueSuffix}${path_1.default.extname(file.originalname)}`);
|
|
}
|
|
});
|
|
const upload = (0, multer_1.default)({
|
|
storage,
|
|
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
|
|
fileFilter: (req, file, cb) => {
|
|
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
|
|
if (allowedTypes.includes(file.mimetype)) {
|
|
cb(null, true);
|
|
}
|
|
else {
|
|
cb(new Error('Invalid file type. Only JPEG, PNG, and WEBP are allowed.'));
|
|
}
|
|
}
|
|
});
|
|
// Upload a cover image
|
|
exports.coverRoutes.post('/upload', upload.single('image'), (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: 'No file uploaded' });
|
|
}
|
|
res.json({
|
|
id: path_1.default.basename(req.file.filename, path_1.default.extname(req.file.filename)),
|
|
filename: req.file.filename,
|
|
path: req.file.path,
|
|
url: `/uploads/covers/${req.file.filename}`,
|
|
size: req.file.size
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Upload error:', error);
|
|
res.status(500).json({
|
|
error: 'Failed to upload image',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
});
|
|
// Get all covers
|
|
exports.coverRoutes.get('/', (req, res) => {
|
|
try {
|
|
const coversDir = path_1.default.join(process.cwd(), 'uploads', 'covers');
|
|
if (!fs_1.default.existsSync(coversDir)) {
|
|
return res.json({ covers: [] });
|
|
}
|
|
const files = fs_1.default.readdirSync(coversDir)
|
|
.filter(file => /\.(jpg|jpeg|png|webp)$/i.test(file))
|
|
.map(file => ({
|
|
id: path_1.default.basename(file, path_1.default.extname(file)),
|
|
filename: file,
|
|
url: `/uploads/covers/${file}`,
|
|
createdAt: fs_1.default.statSync(path_1.default.join(coversDir, file)).mtime
|
|
}))
|
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
res.json({ covers: files });
|
|
}
|
|
catch (error) {
|
|
console.error('List covers error:', error);
|
|
res.status(500).json({
|
|
error: 'Failed to list covers',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
});
|
|
// Delete a cover
|
|
exports.coverRoutes.delete('/:id', (req, res) => {
|
|
try {
|
|
const coverPath = path_1.default.join(process.cwd(), 'uploads', 'covers', `${req.params.id}*`);
|
|
const coversDir = path_1.default.join(process.cwd(), 'uploads', 'covers');
|
|
if (!fs_1.default.existsSync(coversDir)) {
|
|
return res.status(404).json({ error: 'Cover not found' });
|
|
}
|
|
const files = fs_1.default.readdirSync(coversDir)
|
|
.filter(f => f.startsWith(`${req.params.id}`));
|
|
if (files.length === 0) {
|
|
return res.status(404).json({ error: 'Cover not found' });
|
|
}
|
|
files.forEach(file => {
|
|
fs_1.default.unlinkSync(path_1.default.join(coversDir, file));
|
|
});
|
|
res.json({ message: 'Cover deleted successfully' });
|
|
}
|
|
catch (error) {
|
|
console.error('Delete error:', error);
|
|
res.status(500).json({
|
|
error: 'Failed to delete cover',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
});
|
|
// Process cover image (resize, filter, etc.)
|
|
exports.coverRoutes.post('/process', upload.single('image'), async (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: 'No file uploaded' });
|
|
}
|
|
const { width, height, filter, brightness, contrast } = req.body;
|
|
let pipeline = (0, sharp_1.default)(req.file.path);
|
|
// Resize if dimensions provided
|
|
if (width && height) {
|
|
pipeline = pipeline.resize(parseInt(width), parseInt(height), {
|
|
fit: 'cover'
|
|
});
|
|
}
|
|
// Apply filters
|
|
if (filter === 'grayscale') {
|
|
pipeline = pipeline.grayscale();
|
|
}
|
|
else if (filter === 'sepia') {
|
|
pipeline = pipeline.modulate({ saturation: 0, brightness: 1.1 });
|
|
}
|
|
// Adjust brightness and saturation (sharp doesn't have contrast)
|
|
if (brightness) {
|
|
pipeline = pipeline.modulate({ brightness: parseFloat(brightness) });
|
|
}
|
|
if (contrast) {
|
|
pipeline = pipeline.modulate({ saturation: parseFloat(contrast) });
|
|
}
|
|
const processedBuffer = await pipeline.toBuffer();
|
|
const outputPath = path_1.default.join(process.cwd(), 'uploads', 'covers', `processed-${req.file.filename}`);
|
|
await pipeline.toFile(outputPath);
|
|
res.json({
|
|
id: `processed-${path_1.default.basename(req.file.filename, path_1.default.extname(req.file.filename))}`,
|
|
filename: `processed-${req.file.filename}`,
|
|
path: outputPath,
|
|
url: `/uploads/covers/processed-${req.file.filename}`
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Process error:', error);
|
|
res.status(500).json({
|
|
error: 'Failed to process image',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
});
|
|
// Generate cover image using AI (Pollinations.ai - Free, no API key required)
|
|
exports.coverRoutes.post('/generate', async (req, res) => {
|
|
try {
|
|
const { prompt, genre, language } = req.body;
|
|
const targetLang = language && language.startsWith('es') ? 'es' : 'en';
|
|
if (!prompt) {
|
|
return res.status(400).json({ error: 'prompt is required' });
|
|
}
|
|
let enhancedPrompt = '';
|
|
if (targetLang === 'es') {
|
|
enhancedPrompt = genre
|
|
? `Portada de libro para una novela de ${genre}: ${prompt}, diseño profesional de portada de libro, alta calidad, ilustración detallada, 8k, obra maestra`
|
|
: `Portada de libro: ${prompt}, diseño profesional de portada de libro, alta calidad, ilustración detallada, 8k, obra maestra`;
|
|
}
|
|
else {
|
|
enhancedPrompt = genre
|
|
? `Book cover for a ${genre} novel: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`
|
|
: `Book cover: ${prompt}, professional book cover design, high quality, detailed illustration, 8k, masterpiece`;
|
|
}
|
|
// Use Pollinations.ai free text-to-image API (no API key required)
|
|
const imageUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(enhancedPrompt)}?width=1024&height=1024&nologo=true&seed=${Date.now()}`;
|
|
// Fetch the generated image
|
|
const imageResponse = await axios_1.default.get(imageUrl, {
|
|
responseType: 'arraybuffer',
|
|
timeout: 30000
|
|
});
|
|
const buffer = Buffer.from(imageResponse.data);
|
|
const uploadsDir = path_1.default.join(process.cwd(), 'uploads', 'covers');
|
|
if (!fs_1.default.existsSync(uploadsDir)) {
|
|
fs_1.default.mkdirSync(uploadsDir, { recursive: true });
|
|
}
|
|
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1E9)}`;
|
|
const filename = `generated-${uniqueSuffix}.png`;
|
|
const filePath = path_1.default.join(uploadsDir, filename);
|
|
fs_1.default.writeFileSync(filePath, buffer);
|
|
res.json({
|
|
id: `generated-${uniqueSuffix}`,
|
|
filename,
|
|
path: filePath,
|
|
url: `/uploads/covers/${filename}`,
|
|
generated: true
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Image generation error:', error);
|
|
res.status(500).json({
|
|
error: 'Failed to generate image',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
});
|
|
//# sourceMappingURL=covers.js.map
|