265 lines
8.1 KiB
JavaScript
265 lines
8.1 KiB
JavaScript
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; |