import express from "express"; import { protectVendor } from "../middleware/vendorAuthMiddleware.js"; import Store from "../models/Store.model.js"; import TelegramUser from "../models/TelegramUser.model.js"; import { sendBulkTelegramMessages } from "../utils/telegramUtils.js"; import multer from "multer"; import sharp from "sharp"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const router = express.Router(); // Get the current directory and set up a relative path for uploads const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const uploadsBaseDir = path.join(process.cwd(), 'uploads'); const broadcastsDir = path.join(uploadsBaseDir, 'broadcasts'); console.log('Upload directory:', { path: broadcastsDir, exists: fs.existsSync(broadcastsDir), absolutePath: path.resolve(broadcastsDir) }); // Create both the base uploads directory and the broadcasts subdirectory if (!fs.existsSync(uploadsBaseDir)) { fs.mkdirSync(uploadsBaseDir, { recursive: true }); } if (!fs.existsSync(broadcastsDir)) { fs.mkdirSync(broadcastsDir, { recursive: true }); } const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit for Telegram fileFilter: (req, file, cb) => { if (file.mimetype.startsWith("image/")) { cb(null, true); } else { cb(new Error("Only image files are allowed!"), false); } }, }); const handleMulterError = (err, req, res, next) => { if (err instanceof multer.MulterError) { if (err.code === "LIMIT_FILE_SIZE") { return res.status(400).json({ error: "File size too large. Maximum allowed size is 10MB." }); } return res.status(400).json({ error: "Upload error occurred.", details: err.message }); } next(err); }; /** * Get Storefront Details * @route GET /api/storefront * @access Private (Vendors only) */ router.get("/", protectVendor, async (req, res) => { try { const storeId = req.user.storeId; const store = await Store.findById(storeId); if (!store) { return res.status(404).json({ error: "Storefront not found" }); } return res.json(store); } catch (error) { console.error("Error fetching storefront:", error); return res.status(500).json({ error: "Failed to retrieve storefront" }); } }); /** * Update Storefront Settings * @route PUT /api/storefront * @access Private (Vendors only) */ router.put("/", protectVendor, async (req, res) => { try { const storeId = req.user.storeId; const { pgpKey, welcomeMessage, telegramToken, shipsFrom, shipsTo, wallets } = req.body; const updatedStore = await Store.findByIdAndUpdate( storeId, { ...(pgpKey !== undefined && { pgpKey }), ...(welcomeMessage !== undefined && { welcomeMessage }), ...(telegramToken !== undefined && { telegramToken }), ...(shipsFrom !== undefined && { shipsFrom }), ...(shipsTo !== undefined && { shipsTo }), ...(wallets !== undefined && { wallets }) }, { new: true } ); if (!updatedStore) { return res.status(404).json({ error: "Storefront not found" }); } return res.json({ message: "Storefront updated successfully", store: updatedStore, }); } catch (error) { console.error("Error updating storefront:", error); return res.status(500).json({ error: "Failed to update storefront" }); } }); /** * Broadcast a Message * @route POST /api/storefront/broadcast * @access Private (Vendors only) */ router.post("/broadcast", protectVendor, upload.single("file"), handleMulterError, async (req, res) => { let photoPath = null; try { const storeId = req.user.storeId; const { message } = req.body; // Handle image upload if present if (req.file) { const outputFileName = `${Date.now()}-${req.file.originalname.split('.').slice(0, -1).join('.')}.jpg`; const outputFilePath = path.join(broadcastsDir, outputFileName); // Ensure upload directory exists if (!fs.existsSync(broadcastsDir)) { fs.mkdirSync(broadcastsDir, { recursive: true }); } console.log('Processing upload:', { fileName: outputFileName, filePath: outputFilePath, absolutePath: path.resolve(outputFilePath), uploadDirExists: fs.existsSync(broadcastsDir), uploadDirContents: fs.readdirSync(broadcastsDir) }); try { // Process and save the image await sharp(req.file.buffer) .resize(1280, 1280, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 80 }) .toFile(outputFilePath); // Verify file was saved and get stats await new Promise((resolve, reject) => { fs.stat(outputFilePath, (err, stats) => { if (err) { console.error('Error verifying file:', err); reject(err); } else { console.log('File processed and verified:', { path: outputFilePath, size: stats.size, exists: fs.existsSync(outputFilePath), stats: stats }); resolve(stats); } }); }); // Additional verification if (!fs.existsSync(outputFilePath)) { throw new Error('File was not saved successfully'); } const stats = fs.statSync(outputFilePath); if (stats.size > 10 * 1024 * 1024) { fs.unlinkSync(outputFilePath); return res.status(400).json({ error: "File size too large after processing." }); } // Set the photo path for sending photoPath = outputFilePath; } catch (err) { console.error("Error processing image:", { error: err, outputFilePath, exists: fs.existsSync(outputFilePath) }); // Cleanup if file exists if (outputFilePath && fs.existsSync(outputFilePath)) { fs.unlinkSync(outputFilePath); } return res.status(500).json({ error: "Failed to process image file." }); } } else if (!message || !message.trim()) { return res.status(400).json({ error: "Either message or image must be provided" }); } const users = await TelegramUser.find({ "stores.store": storeId }); if (!users.length) { return res.status(404).json({ error: "No users found to broadcast to." }); } const store = await Store.findById(storeId); if (!store || !store.telegramToken) { return res.status(400).json({ error: "Store has no Telegram bot token configured." }); } // Flatten out each store entry to gather all chat IDs belonging to this store const chatIds = users.flatMap((user) => user.stores .filter((s) => s.store.toString() === storeId.toString()) .map((s) => s.chatId) ); console.log("Broadcasting to chat IDs:", chatIds); try { if (photoPath) { console.log('Sending photo broadcast:', { photoPath, exists: fs.existsSync(photoPath), stats: fs.existsSync(photoPath) ? fs.statSync(photoPath) : null }); await sendBulkTelegramMessages(store.telegramToken, chatIds, message, photoPath); } else { await sendBulkTelegramMessages(store.telegramToken, chatIds, message); } return res.json({ message: "Broadcast sent successfully", totalUsers: chatIds.length, }); } catch (error) { console.error("Error sending broadcast:", { error: error.message, photoPath, messageLength: message?.length, fileExists: photoPath ? fs.existsSync(photoPath) : null }); throw error; } } catch (error) { console.error("Error broadcasting message:", error); return res.status(500).json({ error: "Failed to broadcast message", details: error.message, }); } finally { // Clean up the file after everything is done if (photoPath && fs.existsSync(photoPath)) { try { fs.unlinkSync(photoPath); } catch (err) { console.error("Error cleaning up file:", err); } } } }); export default router;