From 2deb3b2429c6236e14a501993e909bab3cc0b720 Mon Sep 17 00:00:00 2001 From: NotII <46204250+NotII@users.noreply.github.com> Date: Fri, 14 Mar 2025 17:06:55 +0000 Subject: [PATCH] WOOHOO --- backend/config/db.js | 19 - backend/controllers/chat.controller.js | 521 ---------------- backend/controllers/cryptoController.js | 69 --- backend/controllers/promotion.controller.js | 271 --------- backend/controllers/stock.controller.js | 151 ----- backend/controllers/vendor.controller.js | 39 -- backend/index.js | 112 ---- backend/middleware/apiAuthMiddleware.js | 10 - backend/middleware/authMiddleware.js | 32 - backend/middleware/staffAuthMiddleware.js | 49 -- backend/middleware/telegramAuthMiddleware.js | 70 --- backend/middleware/vendorAuthMiddleware.js | 32 - backend/models/BlockedUser.model.js | 14 - backend/models/Buyer.model.js | 10 - backend/models/Chat.model.js | 68 --- backend/models/Escrow.model.js | 43 -- backend/models/Invitation.model.js | 40 -- backend/models/Order.model.js | 100 ---- backend/models/Product.model.js | 69 --- backend/models/Promotion.model.js | 126 ---- backend/models/PromotionUse.model.js | 54 -- backend/models/Staff.model.js | 11 - backend/models/Store.model.js | 132 ---- backend/models/TelegramUser.model.js | 22 - backend/models/Vendor.model.js | 13 - backend/models/Wallet.model.js | 56 -- backend/routes/auth.routes.js | 158 ----- backend/routes/blockedUsers.routes.js | 75 --- backend/routes/categories.routes.js | 154 ----- backend/routes/chat.routes.js | 41 -- backend/routes/crypto.routes.js | 9 - backend/routes/invites.routes.js | 21 - backend/routes/orders.routes.js | 598 ------------------- backend/routes/products.routes.js | 456 -------------- backend/routes/promotion.routes.js | 24 - backend/routes/shipping.routes.js | 151 ----- backend/routes/staffAuth.routes.js | 84 --- backend/routes/stock.routes.js | 13 - backend/routes/storefront.routes.js | 265 -------- backend/scripts/disableAllStockTracking.js | 17 - backend/test.js | 8 - backend/utils/createAdmin.js | 44 -- backend/utils/createFakeOrder.js | 56 -- backend/utils/createInvitation.js | 67 --- backend/utils/createKeys.js | 7 - backend/utils/disableStockTracking.js | 55 -- backend/utils/encryptPgp.js | 54 -- backend/utils/litecoin/index.js | 96 --- backend/utils/logger.js | 77 --- backend/utils/telegramUtils.js | 171 ------ backend/utils/walletUtils.js | 0 server.js | 125 ---- traefik.yml | 8 - 53 files changed, 4967 deletions(-) delete mode 100644 backend/config/db.js delete mode 100644 backend/controllers/chat.controller.js delete mode 100644 backend/controllers/cryptoController.js delete mode 100644 backend/controllers/promotion.controller.js delete mode 100644 backend/controllers/stock.controller.js delete mode 100644 backend/controllers/vendor.controller.js delete mode 100644 backend/index.js delete mode 100644 backend/middleware/apiAuthMiddleware.js delete mode 100644 backend/middleware/authMiddleware.js delete mode 100644 backend/middleware/staffAuthMiddleware.js delete mode 100644 backend/middleware/telegramAuthMiddleware.js delete mode 100644 backend/middleware/vendorAuthMiddleware.js delete mode 100644 backend/models/BlockedUser.model.js delete mode 100644 backend/models/Buyer.model.js delete mode 100644 backend/models/Chat.model.js delete mode 100644 backend/models/Escrow.model.js delete mode 100644 backend/models/Invitation.model.js delete mode 100644 backend/models/Order.model.js delete mode 100644 backend/models/Product.model.js delete mode 100644 backend/models/Promotion.model.js delete mode 100644 backend/models/PromotionUse.model.js delete mode 100644 backend/models/Staff.model.js delete mode 100644 backend/models/Store.model.js delete mode 100644 backend/models/TelegramUser.model.js delete mode 100644 backend/models/Vendor.model.js delete mode 100644 backend/models/Wallet.model.js delete mode 100644 backend/routes/auth.routes.js delete mode 100644 backend/routes/blockedUsers.routes.js delete mode 100644 backend/routes/categories.routes.js delete mode 100644 backend/routes/chat.routes.js delete mode 100644 backend/routes/crypto.routes.js delete mode 100644 backend/routes/invites.routes.js delete mode 100644 backend/routes/orders.routes.js delete mode 100644 backend/routes/products.routes.js delete mode 100644 backend/routes/promotion.routes.js delete mode 100644 backend/routes/shipping.routes.js delete mode 100644 backend/routes/staffAuth.routes.js delete mode 100644 backend/routes/stock.routes.js delete mode 100644 backend/routes/storefront.routes.js delete mode 100644 backend/scripts/disableAllStockTracking.js delete mode 100644 backend/test.js delete mode 100644 backend/utils/createAdmin.js delete mode 100644 backend/utils/createFakeOrder.js delete mode 100644 backend/utils/createInvitation.js delete mode 100644 backend/utils/createKeys.js delete mode 100644 backend/utils/disableStockTracking.js delete mode 100644 backend/utils/encryptPgp.js delete mode 100644 backend/utils/litecoin/index.js delete mode 100644 backend/utils/logger.js delete mode 100644 backend/utils/telegramUtils.js delete mode 100644 backend/utils/walletUtils.js delete mode 100644 server.js delete mode 100644 traefik.yml diff --git a/backend/config/db.js b/backend/config/db.js deleted file mode 100644 index 74e80f6..0000000 --- a/backend/config/db.js +++ /dev/null @@ -1,19 +0,0 @@ -import mongoose from "mongoose"; -import dotenv from "dotenv"; - -dotenv.config(); - -const connectDB = async () => { - try { - await mongoose.connect(process.env.MONGO_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, - }); - console.log("✅ MongoDB Connected"); - } catch (error) { - console.error("❌ MongoDB Connection Error:", error); - process.exit(1); // Exit on failure - } -}; - -export default connectDB; diff --git a/backend/controllers/chat.controller.js b/backend/controllers/chat.controller.js deleted file mode 100644 index e2c19ed..0000000 --- a/backend/controllers/chat.controller.js +++ /dev/null @@ -1,521 +0,0 @@ -import Chat from "../models/Chat.model.js"; -import Store from "../models/Store.model.js"; -import Vendor from "../models/Vendor.model.js"; -import { encryptWithPGP } from "../utils/encryptPgp.js"; -import logger from "../utils/logger.js"; -import { sendTelegramMessage } from "../utils/telegramUtils.js"; -import axios from "axios"; - -// Get all chats for a vendor -export const getVendorChats = async (req, res) => { - try { - const { vendorId } = req.params; - - // Check if vendor exists and requester has access - if (req.user._id.toString() !== vendorId) { - return res.status(403).json({ error: "Not authorized to access these chats" }); - } - - const chats = await Chat.find({ vendorId }) - .sort({ lastUpdated: -1 }) - .select("-messages") // Don't include messages in the list view - .lean(); - - return res.status(200).json(chats); - } catch (error) { - logger.error("Error getting vendor chats", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error getting chats" }); - } -}; - -// Get all messages in a specific chat -export const getChatMessages = async (req, res) => { - try { - const { chatId } = req.params; - const { markAsRead = "true" } = req.query; // Default to true for backward compatibility - const shouldMarkAsRead = markAsRead === "true"; - - const chat = await Chat.findById(chatId).lean(); - - if (!chat) { - return res.status(404).json({ error: "Chat not found" }); - } - - // Check if user has access to this chat - if (req.user && chat.vendorId.toString() !== req.user._id.toString()) { - return res.status(403).json({ error: "Not authorized to access this chat" }); - } - - // Only mark messages as read if markAsRead parameter is true - if (shouldMarkAsRead) { - // Mark all vendor messages as read if the request is from the buyer - if (req.telegramUser && chat.buyerId === req.telegramUser.telegramId) { - await Chat.updateMany( - { _id: chatId, "messages.sender": "vendor", "messages.read": false }, - { $set: { "messages.$[elem].read": true } }, - { arrayFilters: [{ "elem.sender": "vendor", "elem.read": false }] } - ); - } - - // Mark all buyer messages as read if the request is from the vendor - if (req.user && chat.vendorId.toString() === req.user._id.toString()) { - await Chat.updateMany( - { _id: chatId, "messages.sender": "buyer", "messages.read": false }, - { $set: { "messages.$[elem].read": true } }, - { arrayFilters: [{ "elem.sender": "buyer", "elem.read": false }] } - ); - } - } - - return res.status(200).json(chat); - } catch (error) { - logger.error("Error getting chat messages", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error getting chat messages" }); - } -}; - -// Explicitly mark messages as read -export const markMessagesAsRead = async (req, res) => { - try { - const { chatId } = req.params; - - const chat = await Chat.findById(chatId); - - if (!chat) { - return res.status(404).json({ error: "Chat not found" }); - } - - // Check if user has access to this chat - if (req.user && chat.vendorId.toString() !== req.user._id.toString()) { - return res.status(403).json({ error: "Not authorized to access this chat" }); - } - - // Mark all buyer messages as read if the request is from the vendor - if (req.user && chat.vendorId.toString() === req.user._id.toString()) { - await Chat.updateMany( - { _id: chatId, "messages.sender": "buyer", "messages.read": false }, - { $set: { "messages.$[elem].read": true } }, - { arrayFilters: [{ "elem.sender": "buyer", "elem.read": false }] } - ); - - logger.info("Marked all buyer messages as read", { chatId }); - } - - return res.status(200).json({ success: true }); - } catch (error) { - logger.error("Error marking messages as read", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error marking messages as read" }); - } -}; - -// Send a message from vendor to buyer -export const sendVendorMessage = async (req, res) => { - try { - const { chatId } = req.params; - const { content, attachments } = req.body; - - if (!content && (!attachments || attachments.length === 0)) { - return res.status(400).json({ error: "Message content or attachments required" }); - } - - const chat = await Chat.findById(chatId); - - if (!chat) { - return res.status(404).json({ error: "Chat not found" }); - } - - // Check vendor authorization - if (chat.vendorId.toString() !== req.user._id.toString()) { - return res.status(403).json({ error: "Not authorized to send messages in this chat" }); - } - - // Create the new message - const newMessage = { - sender: "vendor", - buyerId: chat.buyerId, - vendorId: chat.vendorId, - content: content || "", - attachments: attachments || [], - read: false, - createdAt: new Date() - }; - - // Add message to chat - chat.messages.push(newMessage); - chat.lastUpdated = new Date(); - await chat.save(); - - // Send notification to Telegram client if configured - try { - const store = await Store.findById(chat.storeId); - if (!store) { - logger.warn("Store not found for chat notification", { chatId, storeId: chat.storeId }); - } else { - // Get store name for the notification - const storeName = store.storeName || "Vendor"; - - // Check if store has telegram token - if (store.telegramToken) { - logger.info("Sending Telegram notification", { buyerId: chat.buyerId, storeId: store._id }); - - // Format the message with emoji and preview - const notificationMessage = `📬 New message from ${storeName}: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`; - - // Use the sendTelegramMessage utility instead of axios - const success = await sendTelegramMessage( - store.telegramToken, - chat.buyerId, - notificationMessage - ); - - if (success) { - logger.info("Telegram notification sent successfully", { chatId, buyerId: chat.buyerId }); - } else { - logger.error("Failed to send Telegram notification", { - chatId, - buyerId: chat.buyerId - }); - } - } else { - logger.warn("Store missing telegramToken", { storeId: store._id }); - } - } - } catch (notifyError) { - // Log but don't fail if notification fails - logger.error("Notification system error", { - error: notifyError.message, - stack: notifyError.stack, - chatId - }); - } - - return res.status(201).json(newMessage); - } catch (error) { - logger.error("Error sending vendor message", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error sending message" }); - } -}; - -// Process an incoming message from the Telegram client -export const processTelegramMessage = async (req, res) => { - try { - const { buyerId, storeId, content, attachments } = req.body; - - if (!buyerId || !storeId) { - return res.status(400).json({ error: "Buyer ID and Store ID are required" }); - } - - if (!content && (!attachments || attachments.length === 0)) { - return res.status(400).json({ error: "Message content or attachments required" }); - } - - // Find the store - const store = await Store.findById(storeId); - - if (!store) { - logger.error("Store not found for Telegram message", { storeId }); - return res.status(404).json({ error: "Store not found" }); - } - - // Check if vendorId field exists in store - if (!store.vendorId) { - logger.error("Store missing vendorId field", { storeId, store }); - return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); - } - - // Find or create a chat - let chat = await Chat.findOne({ buyerId, storeId }); - - if (!chat) { - // Create a new chat - chat = new Chat({ - buyerId, - vendorId: store.vendorId, - storeId, - messages: [], - }); - } - - // Create the new message - const newMessage = { - sender: "buyer", - buyerId, - vendorId: store.vendorId, - content: content || "", - attachments: attachments || [], - read: false, - createdAt: new Date() - }; - - // Add message to chat - chat.messages.push(newMessage); - chat.lastUpdated = new Date(); - await chat.save(); - - return res.status(201).json({ - success: true, - chatId: chat._id, - message: newMessage - }); - } catch (error) { - logger.error("Error processing Telegram message", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error processing message" }); - } -}; - -// Get unread message counts for a vendor -export const getVendorUnreadCounts = async (req, res) => { - try { - const { vendorId } = req.params; - - // Check if vendor is authorized - if (req.user._id.toString() !== vendorId) { - return res.status(403).json({ error: "Not authorized" }); - } - - // Find all chats for this vendor - const chats = await Chat.find({ vendorId }); - - // Calculate unread counts - const result = { - totalUnread: 0, - chatCounts: {} - }; - - chats.forEach(chat => { - const unreadCount = chat.messages.filter( - msg => msg.sender === "buyer" && !msg.read - ).length; - - if (unreadCount > 0) { - result.totalUnread += unreadCount; - result.chatCounts[chat._id] = unreadCount; - } - }); - - return res.status(200).json(result); - } catch (error) { - logger.error("Error getting unread counts", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error getting unread counts" }); - } -}; - -// Create a new chat directly (for customer service purposes) -export const createChat = async (req, res) => { - try { - const { buyerId, storeId, initialMessage } = req.body; - - if (!buyerId || !storeId) { - return res.status(400).json({ error: "Buyer ID and Store ID are required" }); - } - - // Find the store and vendor - const store = await Store.findById(storeId); - console.log("Store data:", JSON.stringify(store, null, 2)); - console.log("Authenticated user:", JSON.stringify(req.user, null, 2)); - - if (!store) { - return res.status(404).json({ error: "Store not found" }); - } - - // Check if vendorId field exists in store - if (!store.vendorId) { - logger.error("Store missing vendorId field", { storeId, store }); - return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); - } - - // Check if requester is authorized for this store - if (req.user._id.toString() !== store.vendorId.toString()) { - return res.status(403).json({ error: "Not authorized to create chats for this store" }); - } - - // Check if chat already exists - const existingChat = await Chat.findOne({ buyerId, storeId }); - if (existingChat) { - return res.status(409).json({ - error: "Chat already exists", - chatId: existingChat._id - }); - } - - // Create a new chat - const chat = new Chat({ - buyerId, - vendorId: store.vendorId, - storeId, - messages: [], - }); - - // Add initial message if provided - if (initialMessage) { - chat.messages.push({ - sender: "vendor", - buyerId, - vendorId: store.vendorId, - content: initialMessage, - read: false, - createdAt: new Date() - }); - } - - chat.lastUpdated = new Date(); - await chat.save(); - - // Send notification to Telegram if configured - try { - if (initialMessage) { - // Get store name for the notification - const storeName = store.storeName || "Vendor"; - - // Check if store has telegram token - if (store.telegramToken) { - logger.info("Sending Telegram notification for new chat", { buyerId, storeId: store._id }); - - // Format the message with emoji and preview - const notificationMessage = `📬 New chat created by ${storeName}: ${initialMessage.substring(0, 50)}${initialMessage.length > 50 ? '...' : ''}`; - - // Use the sendTelegramMessage utility instead of axios - const success = await sendTelegramMessage( - store.telegramToken, - buyerId, - notificationMessage - ); - - if (success) { - logger.info("Telegram notification for new chat sent successfully", { chatId: chat._id, buyerId }); - } else { - logger.error("Failed to send Telegram notification for new chat", { - chatId: chat._id, - buyerId - }); - } - } else { - logger.warn("Store missing telegramToken for new chat notification", { storeId: store._id }); - } - } - } catch (notifyError) { - logger.error("Notification system error for new chat", { - error: notifyError.message, - stack: notifyError.stack, - chatId: chat._id, - buyerId, - storeId - }); - } - - return res.status(201).json({ - success: true, - chatId: chat._id - }); - } catch (error) { - logger.error("Error creating chat", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error creating chat" }); - } -}; - -// Create a chat from Telegram client -export const createTelegramChat = async (req, res) => { - try { - const { buyerId, storeId, initialMessage } = req.body; - - if (!buyerId || !storeId) { - return res.status(400).json({ error: "Buyer ID and Store ID are required" }); - } - - // Find the store - const store = await Store.findById(storeId); - - if (!store) { - logger.error("Store not found for Telegram chat creation", { storeId }); - return res.status(404).json({ error: "Store not found" }); - } - - // Check if vendorId field exists in store - if (!store.vendorId) { - logger.error("Store missing vendorId field", { storeId, store }); - return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); - } - - // Check if chat already exists - const existingChat = await Chat.findOne({ buyerId, storeId }); - - // If chat exists, just return it - if (existingChat) { - logger.info("Chat already exists, returning existing chat", { - chatId: existingChat._id, - buyerId, - storeId - }); - - // Add initial message to existing chat if provided - if (initialMessage) { - existingChat.messages.push({ - sender: "buyer", - buyerId, - vendorId: store.vendorId, - content: initialMessage, - read: false, - createdAt: new Date() - }); - - existingChat.lastUpdated = new Date(); - await existingChat.save(); - - // Notify vendor about the new message - // This would typically be done through a notification system - logger.info("Added message to existing chat", { - chatId: existingChat._id, - buyerId, - message: initialMessage.substring(0, 50) - }); - } - - return res.status(200).json({ - message: "Using existing chat", - chatId: existingChat._id - }); - } - - // Create a new chat - const chat = new Chat({ - buyerId, - vendorId: store.vendorId, - storeId, - messages: [], - }); - - // Add initial message if provided - if (initialMessage) { - chat.messages.push({ - sender: "buyer", - buyerId, - vendorId: store.vendorId, - content: initialMessage, - read: false, - createdAt: new Date() - }); - } - - chat.lastUpdated = new Date(); - await chat.save(); - - logger.info("New chat created from Telegram", { - chatId: chat._id, - buyerId, - storeId - }); - - return res.status(201).json({ - message: "Chat created successfully", - chatId: chat._id - }); - } catch (error) { - logger.error("Error creating chat from Telegram", { - error: error.message, - stack: error.stack, - buyerId: req.body.buyerId, - storeId: req.body.storeId - }); - return res.status(500).json({ error: "Server error creating chat" }); - } -}; \ No newline at end of file diff --git a/backend/controllers/cryptoController.js b/backend/controllers/cryptoController.js deleted file mode 100644 index 61b9cd0..0000000 --- a/backend/controllers/cryptoController.js +++ /dev/null @@ -1,69 +0,0 @@ -import ky from "ky"; -import logger from "../utils/logger.js" - -// Global object to store the latest crypto prices -const cryptoPrices = { - btc: null, - ltc: null, - xmr: null, - lastUpdated: null, -}; - -/** - * Fetch crypto prices from the CoinGecko API and update the global `cryptoPrices` object. - */ -const fetchCryptoPrices = async () => { - try { - const url = - "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,litecoin,monero&vs_currencies=gbp"; - - // Fetch using Ky with automatic JSON parsing - const data = await ky.get(url).json(); - - // Update the stored crypto prices - cryptoPrices.btc = data.bitcoin?.gbp ?? null; - cryptoPrices.ltc = data.litecoin?.gbp ?? null; - cryptoPrices.xmr = data.monero?.gbp ?? null; - cryptoPrices.lastUpdated = new Date().toISOString(); - - logger.info("✅ Crypto prices updated", { cryptoPrices }); - } catch (error) { - logger.error("❌ Error fetching crypto prices", { - message: error.message || "Unknown error", - }); - } -}; - -/** - * Starts the automatic crypto price updater. - * @param {number} interval - Update interval in seconds. - */ -const startCryptoPriceUpdater = (interval) => { - logger.info(`🚀 Starting crypto price updater (every ${interval} seconds)`); - fetchCryptoPrices(); // Fetch immediately - setInterval(fetchCryptoPrices, interval * 1000); // Fetch periodically -}; - -/** - * API Route: Get the latest crypto prices. - * @route GET /api/crypto - */ -const getCryptoPrices = async (req, res) => { - try { - res.json({ - success: true, - prices: cryptoPrices, - }); - } catch (error) { - logger.error("❌ Error getting crypto prices", { - message: error.message || "Unknown error", - }); - res.status(500).json({ success: false, error: "Internal Server Error" }); - } -}; - -const returnCryptoPrices = () => { - return cryptoPrices; -}; - -export { startCryptoPriceUpdater, getCryptoPrices, returnCryptoPrices }; \ No newline at end of file diff --git a/backend/controllers/promotion.controller.js b/backend/controllers/promotion.controller.js deleted file mode 100644 index af2ed0c..0000000 --- a/backend/controllers/promotion.controller.js +++ /dev/null @@ -1,271 +0,0 @@ -import Promotion from "../models/Promotion.model.js"; -import logger from "../utils/logger.js"; - -/** - * Get all promotions for a store - */ -export const getPromotions = async (req, res) => { - try { - const { query } = req; - const filter = { storeId: req.user.storeId }; - - // Apply filters if provided - if (query.isActive) { - filter.isActive = query.isActive === 'true'; - } - - if (query.search) { - filter.code = { $regex: query.search, $options: 'i' }; - } - - // Handle date filters - if (query.active === 'true') { - const now = new Date(); - filter.startDate = { $lte: now }; - filter.$or = [ - { endDate: null }, - { endDate: { $gte: now } } - ]; - filter.isActive = true; - } - - const promotions = await Promotion.find(filter).sort({ createdAt: -1 }); - - return res.status(200).json(promotions); - } catch (error) { - logger.error("Error fetching promotions", { error }); - return res.status(500).json({ message: "Failed to fetch promotions", error: error.message }); - } -}; - -/** - * Get a single promotion by ID - */ -export const getPromotionById = async (req, res) => { - try { - const { id } = req.params; - - const promotion = await Promotion.findOne({ - _id: id, - storeId: req.user.storeId - }); - - if (!promotion) { - return res.status(404).json({ message: "Promotion not found" }); - } - - return res.status(200).json(promotion); - } catch (error) { - logger.error("Error fetching promotion", { error }); - return res.status(500).json({ message: "Failed to fetch promotion", error: error.message }); - } -}; - -/** - * Create a new promotion - */ -export const createPromotion = async (req, res) => { - try { - const { - code, - discountType, - discountValue, - minOrderAmount = 0, - maxUsage = null, - isActive = true, - startDate = new Date(), - endDate = null, - description = "" - } = req.body; - - // Validate required fields - if (!code || !discountType || discountValue === undefined) { - return res.status(400).json({ message: "Code, discount type, and discount value are required" }); - } - - // Check if code already exists for this store - const existingPromo = await Promotion.findOne({ - storeId: req.user.storeId, - code: code.toUpperCase() - }); - - if (existingPromo) { - return res.status(400).json({ message: "A promotion with this code already exists" }); - } - - // Create new promotion - const newPromotion = new Promotion({ - storeId: req.user.storeId, - code: code.toUpperCase(), - discountType, - discountValue, - minOrderAmount, - maxUsage, - isActive, - startDate, - endDate, - description - }); - - await newPromotion.save(); - - return res.status(201).json(newPromotion); - } catch (error) { - logger.error("Error creating promotion", { error }); - - if (error.name === 'ValidationError') { - return res.status(400).json({ - message: "Invalid promotion data", - details: Object.values(error.errors).map(err => err.message) - }); - } - - return res.status(500).json({ message: "Failed to create promotion", error: error.message }); - } -}; - -/** - * Update an existing promotion - */ -export const updatePromotion = async (req, res) => { - try { - const { id } = req.params; - const updates = req.body; - - // Ensure the promotion exists and belongs to the vendor's store - const promotion = await Promotion.findOne({ - _id: id, - storeId: req.user.storeId - }); - - if (!promotion) { - return res.status(404).json({ message: "Promotion not found" }); - } - - // Remove storeId from updates if present (can't change store) - if (updates.storeId) { - delete updates.storeId; - } - - // Ensure code is uppercase if present - if (updates.code) { - updates.code = updates.code.toUpperCase(); - - // Check if updated code conflicts with existing promo - const codeExists = await Promotion.findOne({ - storeId: req.user.storeId, - code: updates.code, - _id: { $ne: id } // Exclude current promotion - }); - - if (codeExists) { - return res.status(400).json({ message: "A promotion with this code already exists" }); - } - } - - // Update the promotion - const updatedPromotion = await Promotion.findByIdAndUpdate( - id, - { ...updates, updatedAt: new Date() }, - { new: true, runValidators: true } - ); - - return res.status(200).json(updatedPromotion); - } catch (error) { - logger.error("Error updating promotion", { error }); - - if (error.name === 'ValidationError') { - return res.status(400).json({ - message: "Invalid promotion data", - details: Object.values(error.errors).map(err => err.message) - }); - } - - return res.status(500).json({ message: "Failed to update promotion", error: error.message }); - } -}; - -/** - * Delete a promotion - */ -export const deletePromotion = async (req, res) => { - try { - const { id } = req.params; - - // Ensure the promotion exists and belongs to the vendor's store - const promotion = await Promotion.findOne({ - _id: id, - storeId: req.user.storeId - }); - - if (!promotion) { - return res.status(404).json({ message: "Promotion not found" }); - } - - await Promotion.findByIdAndDelete(id); - - return res.status(200).json({ message: "Promotion deleted successfully" }); - } catch (error) { - logger.error("Error deleting promotion", { error }); - return res.status(500).json({ message: "Failed to delete promotion", error: error.message }); - } -}; - -/** - * Validate a promotion code for a store - */ -export const validatePromotion = async (req, res) => { - try { - const { code, orderTotal } = req.body; - const { storeId } = req.params; - - if (!code || !storeId) { - return res.status(400).json({ message: "Promotion code and store ID are required" }); - } - - // Find the promotion - const promotion = await Promotion.findOne({ - storeId, - code: code.toUpperCase(), - isActive: true, - }); - - if (!promotion) { - return res.status(404).json({ message: "Promotion not found or inactive" }); - } - - // Check if the promotion is valid - if (!promotion.isValid()) { - return res.status(400).json({ message: "Promotion is no longer valid" }); - } - - // Check minimum order amount - if (orderTotal && orderTotal < promotion.minOrderAmount) { - return res.status(400).json({ - message: `Order total must be at least £${promotion.minOrderAmount} to use this promotion`, - minOrderAmount: promotion.minOrderAmount - }); - } - - // Calculate discount if order total provided - let discountAmount = null; - if (orderTotal) { - discountAmount = promotion.calculateDiscount(orderTotal); - } - - return res.status(200).json({ - promotion: { - _id: promotion._id, - code: promotion.code, - discountType: promotion.discountType, - discountValue: promotion.discountValue, - minOrderAmount: promotion.minOrderAmount, - }, - discountAmount, - message: "Promotion is valid" - }); - } catch (error) { - logger.error("Error validating promotion", { error }); - return res.status(500).json({ message: "Failed to validate promotion", error: error.message }); - } -}; \ No newline at end of file diff --git a/backend/controllers/stock.controller.js b/backend/controllers/stock.controller.js deleted file mode 100644 index 12f1f86..0000000 --- a/backend/controllers/stock.controller.js +++ /dev/null @@ -1,151 +0,0 @@ -import Product from "../models/Product.model.js"; -import Order from "../models/Order.model.js"; -import Store from "../models/Store.model.js"; -import mongoose from "mongoose"; -import logger from "../utils/logger.js"; - -/** - * Updates a product's stock quantity - */ -export const updateStock = async (req, res) => { - try { - const { productId } = req.params; - const { currentStock, stockTracking, lowStockThreshold } = req.body; - - if (currentStock === undefined) { - return res.status(400).json({ message: "Stock quantity is required" }); - } - - // Validate product exists and belongs to the vendor's store - const product = await Product.findOne({ - _id: productId, - storeId: req.user.storeId - }); - - if (!product) { - return res.status(404).json({ message: "Product not found" }); - } - - // Update stock values - let stockStatus = "in_stock"; - if (currentStock <= 0) { - stockStatus = "out_of_stock"; - } else if (currentStock <= (lowStockThreshold || product.lowStockThreshold)) { - stockStatus = "low_stock"; - } - - const updatedProduct = await Product.findByIdAndUpdate( - productId, - { - currentStock: currentStock, - stockTracking: stockTracking !== undefined ? stockTracking : product.stockTracking, - lowStockThreshold: lowStockThreshold !== undefined ? lowStockThreshold : product.lowStockThreshold, - stockStatus: stockStatus - }, - { new: true } - ); - - return res.status(200).json(updatedProduct); - - } catch (error) { - logger.error("Error updating stock", { error }); - return res.status(500).json({ message: "Failed to update stock", error: error.message }); - } -}; - -/** - * Gets stock information for all products in a store - */ -export const getStoreStock = async (req, res) => { - try { - const products = await Product.find({ storeId: req.user.storeId }) - .select('_id name currentStock stockTracking stockStatus lowStockThreshold') - .sort({ stockStatus: 1, name: 1 }); - - return res.status(200).json(products); - } catch (error) { - logger.error("Error fetching store stock", { error }); - return res.status(500).json({ message: "Failed to fetch stock information", error: error.message }); - } -}; - -/** - * Updates stock levels when an order is placed - * This should be called from the order creation logic - */ -export const decreaseStockOnOrder = async (order) => { - try { - // Process each ordered product - for (const item of order.products) { - const product = await Product.findById(item.productId); - - // Skip if product doesn't exist or stock tracking is disabled - if (!product || !product.stockTracking) continue; - - // Calculate new stock level - let newStock = Math.max(0, product.currentStock - item.quantity); - let stockStatus = "in_stock"; - - if (newStock <= 0) { - stockStatus = "out_of_stock"; - } else if (newStock <= product.lowStockThreshold) { - stockStatus = "low_stock"; - } - - // Update the product stock - await Product.findByIdAndUpdate( - item.productId, - { - currentStock: newStock, - stockStatus: stockStatus - }, - { new: true } - ); - } - - return true; - } catch (error) { - logger.error("Error decreasing stock on order", { orderId: order._id, error }); - return false; - } -}; - -/** - * Restores stock when an order is cancelled - */ -export const restoreStockOnCancel = async (order) => { - try { - // Process each ordered product - for (const item of order.products) { - const product = await Product.findById(item.productId); - - // Skip if product doesn't exist or stock tracking is disabled - if (!product || !product.stockTracking) continue; - - // Calculate new stock level - let newStock = product.currentStock + item.quantity; - let stockStatus = "in_stock"; - - if (newStock <= 0) { - stockStatus = "out_of_stock"; - } else if (newStock <= product.lowStockThreshold) { - stockStatus = "low_stock"; - } - - // Update the product stock - await Product.findByIdAndUpdate( - item.productId, - { - currentStock: newStock, - stockStatus: stockStatus - }, - { new: true } - ); - } - - return true; - } catch (error) { - logger.error("Error restoring stock on cancel", { orderId: order._id, error }); - return false; - } -}; \ No newline at end of file diff --git a/backend/controllers/vendor.controller.js b/backend/controllers/vendor.controller.js deleted file mode 100644 index d3e8c21..0000000 --- a/backend/controllers/vendor.controller.js +++ /dev/null @@ -1,39 +0,0 @@ -// Handle notifications from Telegram to vendors -export const notifyVendor = async (req, res) => { - try { - const { buyerId, storeId, message, source } = req.body; - - if (!buyerId || !storeId || !message) { - return res.status(400).json({ error: "Missing required parameters" }); - } - - // Find the store and check if it exists - const store = await Store.findById(storeId); - if (!store) { - logger.warn("Store not found for vendor notification", { storeId }); - return res.status(404).json({ error: "Store not found" }); - } - - // Get the vendor - const vendor = await Vendor.findById(store.vendorId); - if (!vendor) { - logger.warn("Vendor not found for notification", { storeId, vendorId: store.vendorId }); - return res.status(404).json({ error: "Vendor not found" }); - } - - // Future enhancement: could implement WebSocket/Server-Sent Events for real-time notifications - // For now, we'll just track that the notification was processed - - logger.info("Processed vendor notification", { - buyerId, - storeId, - vendorId: vendor._id, - source: source || "unknown" - }); - - return res.status(200).json({ success: true }); - } catch (error) { - logger.error("Error processing vendor notification", { error: error.message, stack: error.stack }); - return res.status(500).json({ error: "Server error processing notification" }); - } -}; \ No newline at end of file diff --git a/backend/index.js b/backend/index.js deleted file mode 100644 index 8f46f19..0000000 --- a/backend/index.js +++ /dev/null @@ -1,112 +0,0 @@ -import express from "express"; -import dotenv from "dotenv"; -import cors from "cors"; -import connectDB from "./config/db.js"; -import logger from "./utils/logger.js"; - -// Routes -import authRoutes from "./routes/auth.routes.js"; -import inviteRoutes from "./routes/invites.routes.js"; -import staffAuthRoutes from "./routes/staffAuth.routes.js"; -import orderRoutes from "./routes/orders.routes.js"; -import productRoutes from "./routes/products.routes.js"; -import categoryRoutes from "./routes/categories.routes.js"; -import shippingRoutes from "./routes/shipping.routes.js"; -import storeRoutes from "./routes/storefront.routes.js"; -import cryptoRoutes from "./routes/crypto.routes.js"; -import blockedUsersRoutes from "./routes/blockedUsers.routes.js"; -import chatRoutes from "./routes/chat.routes.js"; -import stockRoutes from "./routes/stock.routes.js"; -import promotionRoutes from "./routes/promotion.routes.js"; - -import { startCryptoPriceUpdater } from "./controllers/cryptoController.js"; - -// Direct routes for Telegram API to bypass JWT middleware -import { protectTelegramApi } from "./middleware/telegramAuthMiddleware.js"; -import { processTelegramMessage, createTelegramChat } from "./controllers/chat.controller.js"; - -dotenv.config(); - -const app = express(); - -connectDB(); - -// Add security headers and handle CORS -app.use((req, res, next) => { - // Basic security headers - res.setHeader('X-Content-Type-Options', 'nosniff'); - res.setHeader('X-Frame-Options', 'DENY'); - res.setHeader('X-XSS-Protection', '1; mode=block'); - - // Handle CORS - const origin = req.headers.origin; - const host = req.headers.host; - - // For Tor (null origin), use the .onion address if we're on the API domain - if (!origin || origin === 'null') { - if (host.includes('internal-api')) { - res.setHeader('Access-Control-Allow-Origin', 'http://6n6f6krmcudhzalzuqckms5bhc4afxc7xgjngumkafvgzmjmd2tmzeid.onion'); - } else { - res.setHeader('Access-Control-Allow-Origin', `https://${host}`); - } - } else { - res.setHeader('Access-Control-Allow-Origin', origin); - } - - // Always enable credentials since we're using specific origins - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin'); - res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Authorization'); - res.setHeader('Access-Control-Max-Age', '86400'); - - // Log the request for debugging - logger.info(`Request from ${req.ip} - Origin: ${origin || 'null'} - Host: ${host}`); - - // Handle preflight requests - if (req.method === 'OPTIONS') { - res.status(204).end(); - return; - } - - next(); -}); - -// Parse JSON for all routes -app.use(express.json({ limit: "15mb" })); - -// Direct routes for Telegram API to bypass JWT middleware -app.post("/telegram/message", protectTelegramApi, processTelegramMessage); -app.post("/telegram/create", protectTelegramApi, createTelegramChat); -app.get("/telegram/test-auth", protectTelegramApi, (req, res) => { - res.status(200).json({ - success: true, - message: "Authentication successful", - headers: { - authHeader: req.headers.authorization ? req.headers.authorization.substring(0, 10) + "..." : "undefined", - xApiKey: req.headers['x-api-key'] ? req.headers['x-api-key'].substring(0, 10) + "..." : "undefined" - } - }); -}); - -// Register API routes -app.use("/api/products", productRoutes); -app.use("/api/chats", chatRoutes); -app.use("/api/auth", authRoutes); -app.use("/api/staff/auth", staffAuthRoutes); -app.use("/api/invite", inviteRoutes); -app.use("/api/orders", orderRoutes); -app.use("/api/categories", categoryRoutes); -app.use("/api/shipping-options", shippingRoutes); -app.use("/api/storefront", storeRoutes); -app.use("/api/crypto", cryptoRoutes); -app.use("/api/blocked-users", blockedUsersRoutes); -app.use("/api/stock", stockRoutes); -app.use("/api/promotions", promotionRoutes); - -startCryptoPriceUpdater(60); - -const PORT = process.env.PORT || 3001; -app.listen(PORT, () => { - logger.info(`Server running on port ${PORT}`); -}); \ No newline at end of file diff --git a/backend/middleware/apiAuthMiddleware.js b/backend/middleware/apiAuthMiddleware.js deleted file mode 100644 index 65b09ff..0000000 --- a/backend/middleware/apiAuthMiddleware.js +++ /dev/null @@ -1,10 +0,0 @@ -export const protectCrypto = async (req, res, next) => { - if ( - req.headers.authorization && - req.headers.authorization === process.env.INTERNAL_API_KEY - ) { - return next(); - } - - return res.status(403).json({ error: "Forbidden: Invalid API key" }); -}; diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js deleted file mode 100644 index 5e9d283..0000000 --- a/backend/middleware/authMiddleware.js +++ /dev/null @@ -1,32 +0,0 @@ -import jwt from "jsonwebtoken"; -import Vendor from "../models/Vendor.model.js"; - -export const protectVendor = async (req, res, next) => { - if (req.method === "OPTIONS") { - return res.status(200).end(); - } - - let token; - - if ( - req.headers.authorization && - req.headers.authorization.startsWith("Bearer") - ) { - try { - token = req.headers.authorization.split(" ")[1]; - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - const vendor = await Vendor.findById(decoded.id); - if (!vendor) return res.status(401).json({ message: "Unauthorized" }); - - req.user = vendor; - req.user.storeId = vendor.storeId; - - next(); - } catch (error) { - return res.status(401).json({ message: "Token failed" }); - } - } else { - return res.status(401).json({ message: "Not authorized, no token" }); - } -}; diff --git a/backend/middleware/staffAuthMiddleware.js b/backend/middleware/staffAuthMiddleware.js deleted file mode 100644 index beb1433..0000000 --- a/backend/middleware/staffAuthMiddleware.js +++ /dev/null @@ -1,49 +0,0 @@ -import jwt from "jsonwebtoken"; -import Staff from "../models/Staff.model.js"; - -/** - * Middleware to protect staff-only routes - Verify JWT from DB - */ -export const protectStaff = async (req, res, next) => { - let token = req.headers.authorization; - - if (!token || !token.startsWith("Bearer ")) { - return res.status(401).json({ error: "Not authorized, no token" }); - } - - try { - token = token.split(" ")[1]; - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - // Verify staff user exists and token matches stored value - const staff = await Staff.findById(decoded.id); - if (!staff || staff.currentToken !== token) { - return res.status(401).json({ error: "Invalid or expired session" }); - } - - req.user = staff; // Attach staff user data to request - next(); - } catch (error) { - res.status(401).json({ error: "Token is invalid or expired" }); - } -}; - -/** - * 📌 Staff Logout - Remove JWT from Database - */ -export const logoutStaff = async (req, res) => { - try { - const staff = await Staff.findById(req.user.id); - if (!staff) { - return res.status(401).json({ error: "User not found" }); - } - - // Clear stored token - staff.currentToken = null; - await staff.save(); - - res.json({ message: "Logged out successfully" }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}; diff --git a/backend/middleware/telegramAuthMiddleware.js b/backend/middleware/telegramAuthMiddleware.js deleted file mode 100644 index 0c50f5a..0000000 --- a/backend/middleware/telegramAuthMiddleware.js +++ /dev/null @@ -1,70 +0,0 @@ -import logger from "../utils/logger.js"; - -// Middleware for protecting Telegram API routes -export const protectTelegramApi = async (req, res, next) => { - // Log the headers for debugging - logger.info("Telegram API request headers:", { - authorization: req.headers.authorization ? req.headers.authorization.substring(0, 10) + "..." : "undefined", - "x-api-key": req.headers['x-api-key'] ? req.headers['x-api-key'].substring(0, 10) + "..." : "undefined", - method: req.method, - path: req.path, - allHeaders: JSON.stringify(req.headers) - }); - - // Full debug for non-production environments - logger.info("FULL HEADER DEBUG (KEYS ONLY):", Object.keys(req.headers)); - logger.info("AUTH HEADER TYPE:", typeof req.headers.authorization); - - const expectedKey = process.env.INTERNAL_API_KEY; - logger.info("Expected API Key (first 10 chars):", expectedKey ? expectedKey.substring(0, 10) + "..." : "undefined"); - - // Check if the environment variable is actually defined - if (!expectedKey) { - logger.error("INTERNAL_API_KEY environment variable is not defined"); - return res.status(500).json({ error: "Server configuration error" }); - } - - // Check if API key is in the expected header - if (req.headers.authorization === expectedKey) { - logger.info("Telegram API auth successful via Authorization header"); - return next(); - } - - // Also try x-api-key as a fallback - if (req.headers['x-api-key'] === expectedKey) { - logger.info("Telegram API auth successful via x-api-key header"); - return next(); - } - - // Try trimming whitespace - if (req.headers.authorization && req.headers.authorization.trim() === expectedKey) { - logger.info("Telegram API auth successful via Authorization header (after trimming)"); - return next(); - } - - // Also try x-api-key with trimming - if (req.headers['x-api-key'] && req.headers['x-api-key'].trim() === expectedKey) { - logger.info("Telegram API auth successful via x-api-key header (after trimming)"); - return next(); - } - - // Check for Bearer prefix and try to extract the token - if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { - const token = req.headers.authorization.substring(7).trim(); - if (token === expectedKey) { - logger.info("Telegram API auth successful via Bearer token in Authorization header"); - return next(); - } - } - - logger.warn("Telegram API auth failed:", { - expectedKeyPrefix: expectedKey ? expectedKey.substring(0, 5) + "..." : "undefined", - expectedKeyLength: expectedKey ? expectedKey.length : 0, - authHeaderPrefix: req.headers.authorization ? req.headers.authorization.substring(0, 5) + "..." : "undefined", - authHeaderLength: req.headers.authorization ? req.headers.authorization.length : 0, - xApiKeyPrefix: req.headers['x-api-key'] ? req.headers['x-api-key'].substring(0, 5) + "..." : "undefined", - xApiKeyLength: req.headers['x-api-key'] ? req.headers['x-api-key'].length : 0 - }); - - return res.status(401).json({ error: "Unauthorized: Invalid API key" }); -}; \ No newline at end of file diff --git a/backend/middleware/vendorAuthMiddleware.js b/backend/middleware/vendorAuthMiddleware.js deleted file mode 100644 index 5e9d283..0000000 --- a/backend/middleware/vendorAuthMiddleware.js +++ /dev/null @@ -1,32 +0,0 @@ -import jwt from "jsonwebtoken"; -import Vendor from "../models/Vendor.model.js"; - -export const protectVendor = async (req, res, next) => { - if (req.method === "OPTIONS") { - return res.status(200).end(); - } - - let token; - - if ( - req.headers.authorization && - req.headers.authorization.startsWith("Bearer") - ) { - try { - token = req.headers.authorization.split(" ")[1]; - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - const vendor = await Vendor.findById(decoded.id); - if (!vendor) return res.status(401).json({ message: "Unauthorized" }); - - req.user = vendor; - req.user.storeId = vendor.storeId; - - next(); - } catch (error) { - return res.status(401).json({ message: "Token failed" }); - } - } else { - return res.status(401).json({ message: "Not authorized, no token" }); - } -}; diff --git a/backend/models/BlockedUser.model.js b/backend/models/BlockedUser.model.js deleted file mode 100644 index b309b96..0000000 --- a/backend/models/BlockedUser.model.js +++ /dev/null @@ -1,14 +0,0 @@ -import mongoose from "mongoose"; - -const BlockedUserSchema = new mongoose.Schema({ - telegramUserId: { type: Number, required: true, unique: true }, - reason: { type: String }, - blockedBy: { - type: mongoose.Schema.Types.ObjectId, - ref: "Staff", - required: true - }, - blockedAt: { type: Date, default: Date.now } -}); - -export default mongoose.model("BlockedUser", BlockedUserSchema); \ No newline at end of file diff --git a/backend/models/Buyer.model.js b/backend/models/Buyer.model.js deleted file mode 100644 index 7633e8d..0000000 --- a/backend/models/Buyer.model.js +++ /dev/null @@ -1,10 +0,0 @@ -import mongoose from 'mongoose'; - -const BuyerSchema = new mongoose.Schema({ - telegramId: { type: Number, required: true, unique: true }, - username: { type: String, required: true }, - createdAt: { type: Date, default: Date.now }, - banned: { type: Boolean, default: false } -}); - -export default mongoose.model('Buyer', BuyerSchema); diff --git a/backend/models/Chat.model.js b/backend/models/Chat.model.js deleted file mode 100644 index 2428e9c..0000000 --- a/backend/models/Chat.model.js +++ /dev/null @@ -1,68 +0,0 @@ -import mongoose from "mongoose"; - -const MessageSchema = new mongoose.Schema({ - sender: { - type: String, - enum: ["buyer", "vendor"], - required: true - }, - buyerId: { - type: String, - required: true - }, - vendorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Vendor", - required: true - }, - content: { - type: String, - required: true - }, - attachments: [{ - type: String, - required: false - }], - read: { - type: Boolean, - default: false - }, - createdAt: { - type: Date, - default: Date.now - } -}); - -const ChatSchema = new mongoose.Schema({ - buyerId: { - type: String, - required: true - }, - vendorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Vendor", - required: true - }, - storeId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Store", - required: true - }, - messages: [MessageSchema], - orderId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Order", - required: false - }, - lastUpdated: { - type: Date, - default: Date.now - } -}); - -// Create indexes for faster queries -ChatSchema.index({ buyerId: 1, vendorId: 1 }); -ChatSchema.index({ vendorId: 1, lastUpdated: -1 }); -ChatSchema.index({ buyerId: 1, lastUpdated: -1 }); - -export default mongoose.model("Chat", ChatSchema); \ No newline at end of file diff --git a/backend/models/Escrow.model.js b/backend/models/Escrow.model.js deleted file mode 100644 index e8f54ae..0000000 --- a/backend/models/Escrow.model.js +++ /dev/null @@ -1,43 +0,0 @@ -import mongoose from "mongoose"; - -const EscrowSchema = new mongoose.Schema({ - orderId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Order", - required: true, - }, - buyerId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Buyer", - required: true, - }, - vendorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Vendor", - required: true, - }, - amount: { - type: Number, - required: true, - min: 0.01, - }, - currency: { - type: String, - enum: ["ltc", "btc", "xmr"], - required: true, - }, - status: { - type: String, - enum: ["held", "released", "disputed"], - default: "held", - }, - releaseDate: { - type: Date, - required: true, - default: function () { - return new Date(Date.now() + 8 * 24 * 60 * 60 * 1000); // Auto set to 8 days from now - }, - }, -}); - -export default mongoose.model("Escrow", EscrowSchema); diff --git a/backend/models/Invitation.model.js b/backend/models/Invitation.model.js deleted file mode 100644 index d3a12ca..0000000 --- a/backend/models/Invitation.model.js +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose from "mongoose"; - -const InvitationSchema = new mongoose.Schema({ - code: { - type: String, - required: true, - unique: true, - }, - createdBy: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Staffs', - required: true, - }, - isUsed: { - type: Boolean, - default: false, - }, - usedBy: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Vendor', - default: null, - }, - expiresAt: { - type: Date, - required: true, - }, - createdAt: { - type: Date, - default: Date.now, - }, - usedAt: { - type: Date, - default: null, - } -}); - -InvitationSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); -InvitationSchema.index({ code: 1 }); - -export default mongoose.model("Invitation", InvitationSchema); diff --git a/backend/models/Order.model.js b/backend/models/Order.model.js deleted file mode 100644 index b19115b..0000000 --- a/backend/models/Order.model.js +++ /dev/null @@ -1,100 +0,0 @@ -import mongoose from "mongoose"; -import AutoIncrement from "mongoose-sequence"; - -const connection = mongoose.connection; - -const OrderSchema = new mongoose.Schema({ - orderId: { type: Number, unique: true }, - - vendorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Vendor", - required: true, - }, - storeId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Store", - required: true, - }, - - pgpAddress: { type: String, required: true }, - orderDate: { type: Date, default: Date.now, }, - txid: { type: Array, default: [] }, - - products: [ - { - productId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Product", - required: true, - }, - quantity: { type: Number, required: true, min: 0.1 }, - pricePerUnit: { type: Number, required: true, min: 0.01 }, - totalItemPrice: { type: Number, required: true, min: 0.01 }, - }, - ], - - shippingMethod: { type: Object }, - - totalPrice: { type: Number, required: true, min: 0.01 }, - - // Promotion fields - promotion: { - type: mongoose.Schema.Types.ObjectId, - ref: "Promotion", - default: null - }, - promotionCode: { - type: String, - default: null - }, - discountAmount: { - type: Number, - default: 0, - min: 0 - }, - subtotalBeforeDiscount: { - type: Number, - default: 0, - min: 0 - }, - - status: { - type: String, - enum: [ - "unpaid", - "cancelled", - "confirming", - "paid", - "shipped", - "disputed", - "completed", - "acknowledged" - ], - default: "unpaid", - }, - - paymentAddress: { type: String, required: true }, - - wallet: { - type: mongoose.Schema.Types.ObjectId, - ref: "Wallet", - }, - - cryptoTotal: { type: Number, required: true, default: 0 }, - //txid: { type: String, default: null }, - - telegramChatId: { type: String, default: null }, - telegramBuyerId: { type: String, default: null }, - telegramUsername: { type: String, default: null }, - trackingNumber: { type: String, default: null }, - - escrowExpiresAt: { - type: Date, - required: true, - default: () => new Date(Date.now() + 8 * 24 * 60 * 60 * 1000), - }, -}); - -OrderSchema.plugin(AutoIncrement(connection), { inc_field: "orderId" }); -export default mongoose.model("Order", OrderSchema); diff --git a/backend/models/Product.model.js b/backend/models/Product.model.js deleted file mode 100644 index 869d68b..0000000 --- a/backend/models/Product.model.js +++ /dev/null @@ -1,69 +0,0 @@ -import mongoose from "mongoose"; - -const ProductSchema = new mongoose.Schema({ - storeId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Store", - required: true, - }, - category: { - type: mongoose.Schema.Types.ObjectId, - required: true, - ref: "Store.categories" - }, - - name: { type: String, required: true }, - description: { type: String }, - - unitType: { - type: String, - enum: ["pcs", "gr", "kg"], - required: true, - }, - - // Add inventory tracking fields - stockTracking: { type: Boolean, default: true }, - currentStock: { - type: Number, - default: 0, - validate: { - validator: function(value) { - return !this.stockTracking || value >= 0; - }, - message: "Stock cannot be negative" - } - }, - lowStockThreshold: { type: Number, default: 10 }, - stockStatus: { - type: String, - enum: ["in_stock", "low_stock", "out_of_stock"], - default: "out_of_stock" - }, - - pricing: [ - { - minQuantity: { - type: Number, - required: true, - validate: { - validator: function(value) { - if (this.parent().unitType === "gr") { - return value >= 0.1; - } - return value >= 1; - }, - message: "Invalid minQuantity for unitType" - } - }, - pricePerUnit: { - type: Number, - required: true, - min: 0.01 - }, - }, - ], - - image: { type: String }, -}); - -export default mongoose.model("Product", ProductSchema); diff --git a/backend/models/Promotion.model.js b/backend/models/Promotion.model.js deleted file mode 100644 index 1e39292..0000000 --- a/backend/models/Promotion.model.js +++ /dev/null @@ -1,126 +0,0 @@ -import mongoose from "mongoose"; - -const PromotionSchema = new mongoose.Schema( - { - storeId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Store", - required: [true, "Store ID is required"] - }, - code: { - type: String, - required: [true, "Promotion code is required"], - trim: true, - uppercase: true, - minlength: [3, "Promotion code must be at least 3 characters"], - maxlength: [20, "Promotion code cannot exceed 20 characters"] - }, - discountType: { - type: String, - required: [true, "Discount type is required"], - enum: { - values: ["percentage", "fixed"], - message: "Discount type must be either percentage or fixed" - } - }, - discountValue: { - type: Number, - required: [true, "Discount value is required"], - validate: { - validator: function(value) { - if (this.discountType === "percentage") { - return value > 0 && value <= 100; - } - return value > 0; - }, - message: props => - props.value <= 0 - ? "Discount value must be greater than 0" - : "Percentage discount cannot exceed 100%" - } - }, - minOrderAmount: { - type: Number, - default: 0, - min: [0, "Minimum order amount cannot be negative"] - }, - maxUsage: { - type: Number, - default: null - }, - usageCount: { - type: Number, - default: 0 - }, - isActive: { - type: Boolean, - default: true - }, - startDate: { - type: Date, - default: Date.now - }, - endDate: { - type: Date, - default: null - }, - description: { - type: String, - trim: true, - maxlength: [200, "Description cannot exceed 200 characters"] - } - }, - { - timestamps: true - } -); - -// Compound index to ensure unique promo codes per store -PromotionSchema.index({ storeId: 1, code: 1 }, { unique: true }); - -// Check if a promotion is valid and can be applied -PromotionSchema.methods.isValid = function() { - const now = new Date(); - - // Check if promotion is active - if (!this.isActive) return false; - - // Check if promotion has expired - if (this.endDate && now > this.endDate) return false; - - // Check if promotion has reached max usage (if set) - if (this.maxUsage !== null && this.usageCount >= this.maxUsage) return false; - - return true; -}; - -// Calculate discount amount for a given order total -PromotionSchema.methods.calculateDiscount = function(orderTotal) { - if (!this.isValid()) { - return 0; - } - - if (orderTotal < this.minOrderAmount) { - return 0; - } - - let discountAmount = 0; - - if (this.discountType === "percentage") { - discountAmount = (orderTotal * this.discountValue) / 100; - } else { - // Fixed amount discount - discountAmount = this.discountValue; - - // Ensure discount doesn't exceed order total - if (discountAmount > orderTotal) { - discountAmount = orderTotal; - } - } - - return parseFloat(discountAmount.toFixed(2)); -}; - -const Promotion = mongoose.model("Promotion", PromotionSchema); - -export default Promotion; \ No newline at end of file diff --git a/backend/models/PromotionUse.model.js b/backend/models/PromotionUse.model.js deleted file mode 100644 index 3396e5c..0000000 --- a/backend/models/PromotionUse.model.js +++ /dev/null @@ -1,54 +0,0 @@ -import mongoose from 'mongoose'; - -const PromotionUseSchema = new mongoose.Schema( - { - promotionId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Promotion', - required: [true, 'Promotion ID is required'] - }, - orderId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Order', - required: [true, 'Order ID is required'] - }, - userId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - required: [true, 'User ID is required'] - }, - storeId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Store', - required: [true, 'Store ID is required'] - }, - code: { - type: String, - required: [true, 'Promotion code is required'] - }, - discountType: { - type: String, - enum: ['percentage', 'fixed'], - required: [true, 'Discount type is required'] - }, - discountValue: { - type: Number, - required: [true, 'Discount value is required'] - }, - discountAmount: { - type: Number, - required: [true, 'Discount amount is required'] - }, - orderTotal: { - type: Number, - required: [true, 'Order total is required'] - } - }, - { - timestamps: true - } -); - -const PromotionUse = mongoose.model('PromotionUse', PromotionUseSchema); - -export default PromotionUse; \ No newline at end of file diff --git a/backend/models/Staff.model.js b/backend/models/Staff.model.js deleted file mode 100644 index ca66789..0000000 --- a/backend/models/Staff.model.js +++ /dev/null @@ -1,11 +0,0 @@ -import mongoose from "mongoose"; - -const StaffSchema = new mongoose.Schema({ - username: { type: String, required: true, unique: true }, - passwordHash: { type: String, required: true }, - role: { type: String, enum: ["admin", "moderator"], default: "moderator" }, - currentToken: { type: String, default: null }, - createdAt: { type: Date, default: Date.now }, -}); - -export default mongoose.model("Staff", StaffSchema); \ No newline at end of file diff --git a/backend/models/Store.model.js b/backend/models/Store.model.js deleted file mode 100644 index 89d6bcb..0000000 --- a/backend/models/Store.model.js +++ /dev/null @@ -1,132 +0,0 @@ -import mongoose from "mongoose"; -import { type } from "os"; - -const CategorySchema = new mongoose.Schema({ - _id: { - type: mongoose.Schema.Types.ObjectId, - auto: true - }, - name: { - type: String, - required: true - }, - parentId: { - type: mongoose.Schema.Types.ObjectId, - default: null - } -}, { _id: true }); - -const StoreSchema = new mongoose.Schema({ - vendorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Vendor", - required: true - }, - storeName: { - type: String, - required: true - }, - welcomeMessage: { - type: String, - default: "Welcome to my store!" - }, - telegramToken: { - type: String, - default: "" - }, - pgpKey: { - type: String, - default: "" - }, - createdAt: { - type: Date, - default: Date.now - }, - shipsFrom: { - type: String, - default: "UK", - }, - shipsTo: { - type: String, - default: "UK", - }, - categories: [CategorySchema], - - shippingOptions: [ - { - name: { - type: String, - required: true - }, - price: { - type: Number, - required: true, - min: 0 - } - } - ], - - wallets:{ - type: Object, - default: { - "litecoin": "", - "bitcoin": "", - "monero":"" - } - }, - - feeRate:{ - type: Number, - default: 2 - } -}); - -// Add a method to get category hierarchy -StoreSchema.methods.getCategoryHierarchy = function() { - const categories = this.categories.toObject(); - - // Helper function to build tree structure - const buildTree = (parentId = null) => { - return categories - .filter(cat => - (!parentId && !cat.parentId) || - (cat.parentId?.toString() === parentId?.toString()) - ) - .map(cat => ({ - ...cat, - children: buildTree(cat._id) - })); - }; - - return buildTree(); -}; - -// Add validation to prevent circular references -CategorySchema.pre('save', function(next) { - if (!this.parentId) { - return next(); - } - - const checkCircular = (categoryId, parentId) => { - if (!parentId) return false; - if (categoryId.toString() === parentId.toString()) return true; - - const parent = this.parent().categories.id(parentId); - if (!parent) return false; - - return checkCircular(categoryId, parent.parentId); - }; - - if (checkCircular(this._id, this.parentId)) { - next(new Error('Circular reference detected in category hierarchy')); - } else { - next(); - } -}); - -// Add index for better query performance -StoreSchema.index({ 'categories.name': 1, 'categories.parentId': 1 }); - -const Store = mongoose.model("Store", StoreSchema); - -export default Store; \ No newline at end of file diff --git a/backend/models/TelegramUser.model.js b/backend/models/TelegramUser.model.js deleted file mode 100644 index 8fa6a0a..0000000 --- a/backend/models/TelegramUser.model.js +++ /dev/null @@ -1,22 +0,0 @@ -import mongoose from "mongoose"; - -const { Schema, model, Types } = mongoose; - -/** - * Defines the schema for Telegram users. - * - `telegramUserId`: Unique Telegram user ID. - * - `stores`: Array of objects storing store references and chat IDs. - * - `createdAt`: Timestamp for when the user was added. - */ -const TelegramUserSchema = new Schema({ - telegramUserId: { type: Number, required: true, unique: true }, - stores: [ - { - store: { type: Types.ObjectId, ref: "Store", required: true }, - chatId: { type: Number, required: true } - } - ], - createdAt: { type: Date, default: Date.now } -}); - -export default model("TelegramUser", TelegramUserSchema); \ No newline at end of file diff --git a/backend/models/Vendor.model.js b/backend/models/Vendor.model.js deleted file mode 100644 index 4b016eb..0000000 --- a/backend/models/Vendor.model.js +++ /dev/null @@ -1,13 +0,0 @@ -import mongoose from "mongoose"; - -const VendorSchema = new mongoose.Schema({ - username: { type: String, required: true, unique: true }, - passwordHash: { type: String, required: true }, - currentToken: { type: String, default: null }, - storeId: { type: mongoose.Schema.Types.ObjectId, ref: "Store", default: null }, - pgpKey: { type: String, default: ""}, - lastOnline: { type: Date, default: Date.now }, - createdAt: { type: Date, default: Date.now }, -}); - -export default mongoose.model("Vendor", VendorSchema); diff --git a/backend/models/Wallet.model.js b/backend/models/Wallet.model.js deleted file mode 100644 index c2ee49d..0000000 --- a/backend/models/Wallet.model.js +++ /dev/null @@ -1,56 +0,0 @@ -import mongoose from "mongoose"; -import crypto from "crypto"; - -// Set default values if environment variables are not available -const encryptionKeyHex = process.env.ENCRYPTION_KEY || '48c66ee5a54e596e2029ea832a512401099533ece34cb0fbbb8c4023ca68ba8e'; -const encryptionIvHex = process.env.ENCRYPTION_IV || '539e26d426cd4bac9844a8e446d63ab1'; - -const algorithm = "aes-256-cbc"; -const encryptionKey = Buffer.from(encryptionKeyHex, "hex"); -const iv = Buffer.from(encryptionIvHex, "hex"); - -function encrypt(text) { - const cipher = crypto.createCipheriv(algorithm, encryptionKey, iv); - let encrypted = cipher.update(text, "utf8", "hex"); - encrypted += cipher.final("hex"); - return encrypted; -} - -function decrypt(text) { - const decipher = crypto.createDecipheriv(algorithm, encryptionKey, iv); - let decrypted = decipher.update(text, "hex", "utf8"); - decrypted += decipher.final("utf8"); - return decrypted; -} - -const WalletSchema = new mongoose.Schema({ - walletName: { - type: String, - }, - orderId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Order", - required: true, - unique: true, - }, - address: { - type: String, - required: true, - }, - encryptedPrivateKey: { - type: String, - required: true, - }, -}); - -WalletSchema.pre("save", function (next) { - if (!this.isModified("encryptedPrivateKey")) return next(); - this.encryptedPrivateKey = encrypt(this.encryptedPrivateKey); - next(); -}); - -WalletSchema.methods.getDecryptedPrivateKey = function () { - return decrypt(this.encryptedPrivateKey); -}; - -export default mongoose.model("Wallet", WalletSchema); \ No newline at end of file diff --git a/backend/routes/auth.routes.js b/backend/routes/auth.routes.js deleted file mode 100644 index f99bac3..0000000 --- a/backend/routes/auth.routes.js +++ /dev/null @@ -1,158 +0,0 @@ -import express from "express"; -import bcrypt from "bcryptjs"; -import jwt from "jsonwebtoken"; -import Vendor from "../models/Vendor.model.js"; -import Store from "../models/Store.model.js"; -import Invitation from "../models/Invitation.model.js"; -import { protectVendor } from "../middleware/vendorAuthMiddleware.js"; - -const router = express.Router(); - -/** - * Register a New Vendor (and create a corresponding Store) - * @route POST /api/auth/register - */ -router.post("/register", async (req, res) => { - const { username, password, invitationCode } = req.body; - - try { - if (!username || !password || !invitationCode) { - return res.status(400).json({ error: "All fields are required." }); - } - - // Verify invitation code - const invitation = await Invitation.findOne({ - code: invitationCode, - isUsed: false, - }); - - console.log(`Invitation: ${invitation}`); - - if (!invitation) { - return res.status(400).json({ error: "Invalid or used invitation code." }); - } - - // Check if vendor already exists - const existingVendor = await Vendor.findOne({ username }); - if (existingVendor) { - return res.status(400).json({ error: "Vendor already exists." }); - } - - // Hash the password - const hashedPassword = await bcrypt.hash(password, 10); - - // Create the vendor - const vendor = new Vendor({ - username, - passwordHash: hashedPassword, - }); - await vendor.save(); - - // Create a store for this vendor - const store = new Store({ - vendorId: vendor._id, - storeName: `${username}'s Store`, - welcomeMessage: "Welcome to my store!", - categories: [], - }); - await store.save(); - - // Attach `storeId` to vendor - vendor.storeId = store._id; - await vendor.save(); - - // Mark invitation as used - invitation.isUsed = true; - invitation.usedBy = vendor._id; - invitation.usedAt = new Date(); - await invitation.save(); - - return res - .status(201) - .json({ message: "Vendor registered successfully", store }); - } catch (error) { - console.error("Error registering vendor:", error); - return res.status(500).json({ error: error.message }); - } -}); - -/** - * Vendor Login - * @route POST /api/auth/login - */ -router.post("/login", async (req, res) => { - const { username, password } = req.body; - - try { - if (!username || !password) { - return res.status(400).json({ error: "Username and password are required." }); - } - - const vendor = await Vendor.findOne({ username }); - if (!vendor) { - return res.status(401).json({ error: "Invalid credentials." }); - } - - const isMatch = await bcrypt.compare(password, vendor.passwordHash); - if (!isMatch) { - return res.status(401).json({ error: "Invalid credentials." }); - } - - // Generate a JWT - const token = jwt.sign( - { id: vendor._id, role: "vendor" }, - process.env.JWT_SECRET, - { expiresIn: "7d" } - ); - - // Store the token in the DB to identify the current session - vendor.currentToken = token; - await vendor.save(); - - return res.json({ token, role: "vendor" }); - } catch (error) { - console.error("Error logging in vendor:", error); - return res.status(500).json({ error: error.message }); - } -}); - -/** - * Vendor Logout - * @route POST /api/auth/logout - * @access Private (Vendors only) - */ -router.post("/logout", protectVendor, async (req, res) => { - try { - await Vendor.findByIdAndUpdate(req.user._id, { currentToken: null }); - return res.json({ message: "Successfully logged out." }); - } catch (error) { - console.error("Error logging out vendor:", error); - return res.status(500).json({ error: "Failed to log out." }); - } -}); - -/** - * Get Vendor Info - * @route GET /api/auth/me - * @access Private (Vendors only) - */ -router.get("/me", protectVendor, async (req, res) => { - try { - const vendor = await Vendor.findById(req.user._id).select("-passwordHash -currentToken"); - - if (!vendor) { - return res.status(404).json({ error: "Vendor not found." }); - } - - vendor.lastOnline = new Date(); - await vendor.save(); - - const store = await Store.findOne({ vendorId: vendor._id }); - return res.json({ vendor, store }); - } catch (error) { - console.error("Error fetching vendor info:", error); - return res.status(500).json({ error: "Failed to fetch vendor data." }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/blockedUsers.routes.js b/backend/routes/blockedUsers.routes.js deleted file mode 100644 index ef9decd..0000000 --- a/backend/routes/blockedUsers.routes.js +++ /dev/null @@ -1,75 +0,0 @@ -import express from "express"; -import { protectStaff } from "../middleware/staffAuthMiddleware.js"; -import BlockedUser from "../models/BlockedUser.model.js"; - -const router = express.Router(); - -/** - * Get all blocked users - * @route GET /api/blocked-users - * @access Private (Staff only) - */ -router.get("/", protectStaff, async (req, res) => { - try { - const blockedUsers = await BlockedUser.find() - .sort({ blockedAt: -1 }); - res.json(blockedUsers); - } catch (error) { - console.error("Error fetching blocked users:", error); - res.status(500).json({ error: "Failed to fetch blocked users" }); - } -}); - -/** - * Block a user - * @route POST /api/blocked-users - * @access Private (Staff only) - */ -router.post("/", protectStaff, async (req, res) => { - try { - const { telegramUserId, reason } = req.body; - - if (!telegramUserId) { - return res.status(400).json({ error: "Telegram user ID is required" }); - } - - const existingBlock = await BlockedUser.findOne({ telegramUserId }); - if (existingBlock) { - return res.status(400).json({ error: "User is already blocked" }); - } - - const blockedUser = await BlockedUser.create({ - telegramUserId, - reason, - blockedBy: req.user._id - }); - - res.status(201).json(blockedUser); - } catch (error) { - console.error("Error blocking user:", error); - res.status(500).json({ error: "Failed to block user" }); - } -}); - -/** - * Unblock a user - * @route DELETE /api/blocked-users/:telegramUserId - * @access Private (Staff only) - */ -router.delete("/:telegramUserId", protectStaff, async (req, res) => { - try { - const { telegramUserId } = req.params; - - const result = await BlockedUser.findOneAndDelete({ telegramUserId }); - if (!result) { - return res.status(404).json({ error: "User is not blocked" }); - } - - res.json({ message: "User unblocked successfully" }); - } catch (error) { - console.error("Error unblocking user:", error); - res.status(500).json({ error: "Failed to unblock user" }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/categories.routes.js b/backend/routes/categories.routes.js deleted file mode 100644 index 631db93..0000000 --- a/backend/routes/categories.routes.js +++ /dev/null @@ -1,154 +0,0 @@ -import express from "express"; -import Store from "../models/Store.model.js"; -import { protectVendor } from "../middleware/vendorAuthMiddleware.js"; -const router = express.Router(); - -/** - * 📌 Fetch all categories for the vendor's store - */ -router.get("/", protectVendor, async (req, res) => { - try { - const store = await Store.findById(req.user.storeId); - if (!store) return res.status(404).json({ message: "Store not found" }); - - res.json(store.categories); - } catch (error) { - res.status(500).json({ message: "Failed to fetch categories", error }); - } -}); - -/** - * 📌 Add a new category - */ -router.post("/", protectVendor, async (req, res) => { - try { - const { name, parentId } = req.body; - const store = await Store.findById(req.user.storeId); - - if (!store) return res.status(404).json({ message: "Store not found" }); - - // Check if the category name already exists at the same level - const categoryExists = store.categories.some(category => - category.name === name && - (!parentId ? !category.parentId : category.parentId?.toString() === parentId) - ); - - if (categoryExists) { - return res.status(400).json({ message: "Category already exists at this level" }); - } - - // If parentId is provided, verify it exists - if (parentId) { - const parentExists = store.categories.some(cat => cat._id.toString() === parentId); - if (!parentExists) { - return res.status(400).json({ message: "Parent category not found" }); - } - } - - // Add the new category with a unique _id, name, and optional parentId - const newCategory = { - name, - ...(parentId && { parentId }) // Only add parentId if it exists - }; - store.categories.push(newCategory); - await store.save(); - - // Get the newly created category with its _id - const createdCategory = store.categories[store.categories.length - 1]; - - res.status(201).json({ - _id: createdCategory._id, - name: createdCategory.name, - parentId: createdCategory.parentId - }); - } catch (error) { - console.error("Error adding category:", error); - res.status(400).json({ message: "Failed to add category", error }); - } -}); - -/** - * 📌 Update a category - */ -router.put("/:categoryId", protectVendor, async (req, res) => { - try { - const { categoryId } = req.params; - const { name } = req.body; - const store = await Store.findById(req.user.storeId); - - if (!store) return res.status(404).json({ message: "Store not found" }); - - // Find the category - const category = store.categories.id(categoryId); - if (!category) { - return res.status(404).json({ message: "Category not found" }); - } - - // Check if the new name already exists at the same level (excluding this category) - const categoryExists = store.categories.some(cat => - cat.name === name && - cat._id.toString() !== categoryId && - (!category.parentId ? !cat.parentId : cat.parentId?.toString() === category.parentId.toString()) - ); - - if (categoryExists) { - return res.status(400).json({ message: "Category name already exists at this level" }); - } - - // Update the category - category.name = name; - await store.save(); - - res.json({ - _id: category._id, - name: category.name, - parentId: category.parentId - }); - } catch (error) { - console.error("Error updating category:", error); - res.status(400).json({ message: "Failed to update category", error }); - } -}); - -/** - * 📌 Delete a category and its subcategories - */ -router.delete("/:categoryId", protectVendor, async (req, res) => { - const { categoryId } = req.params; - - try { - const store = await Store.findById(req.user.storeId); - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - // Find all subcategories recursively - const getAllSubcategoryIds = (categoryId) => { - const subcategories = store.categories.filter(cat => - cat.parentId?.toString() === categoryId - ); - - return [ - categoryId, - ...subcategories.flatMap(subcat => getAllSubcategoryIds(subcat._id.toString())) - ]; - }; - - const categoryIdsToDelete = getAllSubcategoryIds(categoryId); - - // Remove all categories and their subcategories - store.categories = store.categories.filter( - cat => !categoryIdsToDelete.includes(cat._id.toString()) - ); - - // Save the updated store document - await store.save(); - - res.status(200).json({ message: "Category and subcategories deleted successfully" }); - } catch (error) { - console.error("Error deleting category:", error); - res.status(500).json({ message: "Failed to delete category", error }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/chat.routes.js b/backend/routes/chat.routes.js deleted file mode 100644 index 5f07832..0000000 --- a/backend/routes/chat.routes.js +++ /dev/null @@ -1,41 +0,0 @@ -import express from "express"; -import { - getVendorChats, - getChatMessages, - sendVendorMessage, - processTelegramMessage, - getVendorUnreadCounts, - createChat, - createTelegramChat, - markMessagesAsRead -} from "../controllers/chat.controller.js"; -import { protectVendor as vendorAuth } from "../middleware/vendorAuthMiddleware.js"; -import { protectTelegramApi } from "../middleware/telegramAuthMiddleware.js"; - -const router = express.Router(); - -// Routes that require vendor authentication -router.get("/vendor/:vendorId", vendorAuth, getVendorChats); -router.get("/vendor/:vendorId/unread", vendorAuth, getVendorUnreadCounts); -router.get("/:chatId", vendorAuth, getChatMessages); -router.post("/:chatId/message", vendorAuth, sendVendorMessage); -router.post("/:chatId/mark-read", vendorAuth, markMessagesAsRead); -router.post("/create", vendorAuth, createChat); - -// Routes for Telegram client (secured with API key) -router.post("/telegram/message", protectTelegramApi, processTelegramMessage); -router.post("/telegram/create", protectTelegramApi, createTelegramChat); - -// Test route for Telegram API auth -router.get("/telegram/test-auth", protectTelegramApi, (req, res) => { - res.status(200).json({ - success: true, - message: "Authentication successful", - headers: { - authHeader: req.headers.authorization ? req.headers.authorization.substring(0, 10) + "..." : "undefined", - xApiKey: req.headers['x-api-key'] ? req.headers['x-api-key'].substring(0, 10) + "..." : "undefined" - } - }); -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/crypto.routes.js b/backend/routes/crypto.routes.js deleted file mode 100644 index da83392..0000000 --- a/backend/routes/crypto.routes.js +++ /dev/null @@ -1,9 +0,0 @@ -import express from "express"; -import ky from "ky" -import { getCryptoPrices } from "../controllers/cryptoController.js"; - -const router = express.Router(); - -router.get("/", getCryptoPrices); - -export default router; \ No newline at end of file diff --git a/backend/routes/invites.routes.js b/backend/routes/invites.routes.js deleted file mode 100644 index 5236afe..0000000 --- a/backend/routes/invites.routes.js +++ /dev/null @@ -1,21 +0,0 @@ -import express from "express"; -import crypto from "crypto"; -import { protectStaff } from "../middleware/staffAuthMiddleware.js"; -import Invitation from "../models/Invitation.model.js"; - -const router = express.Router(); - -router.post("/generate", protectStaff, async (req, res) => { - try { - const invitationCode = crypto.randomBytes(6).toString("hex"); - - const invitation = new Invitation({ code: invitationCode, createdBy: req.user._id }); - await invitation.save(); - - res.status(201).json({ invitationCode }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - -export default router; diff --git a/backend/routes/orders.routes.js b/backend/routes/orders.routes.js deleted file mode 100644 index beeb289..0000000 --- a/backend/routes/orders.routes.js +++ /dev/null @@ -1,598 +0,0 @@ -import express from "express"; -import { protectVendor } from "../middleware/vendorAuthMiddleware.js"; -import { protectCrypto } from "../middleware/apiAuthMiddleware.js"; -import Order from "../models/Order.model.js"; -import Wallet from "../models/Wallet.model.js"; - -import crypto from "crypto"; -import { setupWallet } from "../utils/litecoin/index.js"; -import { returnCryptoPrices } from "../controllers/cryptoController.js"; -import Store from "../models/Store.model.js"; -import { sendTelegramMessage } from "../utils/telegramUtils.js" -import Product from "../models/Product.model.js"; -import mongoose from "mongoose"; -import { decreaseStockOnOrder, restoreStockOnCancel } from "../controllers/stock.controller.js"; - -const router = express.Router(); - -/** - * 📌 Get Orders for Vendor - * @route GET /api/orders - * @access Private (Vendors only) - */ -router.get("/", protectVendor, async (req, res) => { - try { - // Extract query params for filtering and navigation - const { status, page = 1, limit = 25, before, after, storeId } = req.query; - - // Initialize query object - let query = {}; - - // Use storeId from query parameter if provided, otherwise use the one from user object - query.storeId = storeId || req.user.storeId; - - // If for some reason there's no storeId in user object and none provided in query - if (!query.storeId) { - return res.status(400).json({ error: "No store ID found. Please specify a storeId parameter." }); - } - // Filter by order status if provided - if (status) { - query.status = status; - } - - // Add orderId filters for navigation - if (before) { - query.orderId = { $lt: before }; - } else if (after) { - query.orderId = { $gt: after }; - } - - // Count total orders for pagination - const totalOrders = await Order.countDocuments(query); - - // Fetch orders with pagination and sorting - const orders = await Order.find(query) - .sort({ orderId: -1 }) // Always sort by newest first - .limit(parseInt(limit)) - .skip((parseInt(page) - 1) * parseInt(limit)); - - res.json({ - orders, - page: parseInt(page), - totalPages: Math.ceil(totalOrders / limit), - totalOrders, - }); - } catch (error) { - console.error("Error fetching orders:", error); - res.status(500).json({ error: "Failed to retrieve orders" }); - } -}); - -router.get("/stats", protectVendor, async (req, res) => { - try { - const vendorId = req.user._id; - - const totalOrders = await Order.countDocuments({ vendorId }); - const pendingOrders = await Order.countDocuments({ - vendorId, - status: { $in: ["unpaid", "confirming"] }, - }); - const ongoingOrders = await Order.countDocuments({ - vendorId, - status: { $in: ["paid", "shipped"] }, - }); - const cancelledOrders = await Order.countDocuments({ - vendorId, - status: "cancelled", - }); - const completedOrders = await Order.countDocuments({ - vendorId, - status: "completed", - }); - - res.json({ totalOrders, pendingOrders, ongoingOrders, cancelledOrders, completedOrders }); - } catch (error) { - console.error("Error fetching order stats:", error); - res.status(500).json({ error: "Failed to retrieve order statistics" }); - } -}); - -/** - * 📌 Get Vendor's Best Selling Products - * @route GET /api/orders/top-products - * @access Private (Vendors only) - * @returns Most sold products for the logged-in vendor - */ -router.get("/top-products", protectVendor, async (req, res) => { - try { - const vendorId = req.user._id; - - // Find the vendor's store - const store = await Store.findOne({ vendorId }); - if (!store) { - return res.status(404).json({ message: "Store not found for this vendor" }); - } - - // Aggregate orders to find the vendor's best-selling products - const topProducts = await Order.aggregate([ - // Match only orders for this vendor - { $match: { vendorId: new mongoose.Types.ObjectId(vendorId) } }, - // Unwind the products array - { $unwind: "$products" }, - // Group by product ID and count occurrences - { - $group: { - _id: "$products.productId", - count: { $sum: 1 }, - revenue: { $sum: "$products.price" } - } - }, - // Sort by count in descending order - { $sort: { count: -1 } }, - // Limit to top 10 products - { $limit: 10 } - ]); - - // Get the actual product details - const productIds = topProducts.map(item => item._id); - const products = await Product.find({ - _id: { $in: productIds }, - storeId: store._id - }).select('name price image'); - - // Combine the count with product details - const result = topProducts.map(item => { - const productDetails = products.find(p => p._id.toString() === item._id.toString()); - return { - id: item._id, - name: productDetails?.name || 'Unknown Product', - price: productDetails?.price || 0, - image: productDetails?.image || '', - count: item.count, - revenue: item.revenue - }; - }); - - res.json(result); - } catch (error) { - console.error("Error fetching top products:", error); - res.status(500).json({ error: "Failed to fetch top products data" }); - } -}); - -router.post("/create", protectCrypto, async (req, res) => { - try { - const { vendorId, storeId, products, totalPrice, shippingMethod, telegramChatId, telegramBuyerId, telegramUsername, cryptoCurrency, pgpAddress } = req.body; - - console.log("Create order request:", req.body); - - if (!vendorId || !storeId || !products || !totalPrice || !shippingMethod || !cryptoCurrency || !pgpAddress) { - console.log("Missing required fields"); - return res.status(400).json({ error: "Missing required fields" }); - } - - /* - // TEMPORARILY DISABLED: Stock check disabled to give vendors time to add stock - // Check stock levels before creating order - const outOfStockProducts = []; - for (const item of products) { - const product = await Product.findOne({ _id: item.productId, storeId }); - - if (product && product.stockTracking && product.currentStock < item.quantity) { - outOfStockProducts.push({ - productId: item.productId, - name: product.name, - requested: item.quantity, - available: product.currentStock - }); - } - } - - // If any products are out of stock, return an error - if (outOfStockProducts.length > 0) { - return res.status(400).json({ - error: "Some products are out of stock or have insufficient quantity", - outOfStockProducts - }); - } - */ - - const cryptoPrices = returnCryptoPrices(); - const cryptoRate = cryptoPrices[cryptoCurrency.toLowerCase()]; - - if (!cryptoRate) { - console.log("Invalid or unsupported cryptocurrency"); - return res.status(400).json({ error: "Invalid or unsupported cryptocurrency" }); - } - - const cryptoTotal = (totalPrice / cryptoRate).toFixed(8); - - const walletName = "order_" + crypto.randomBytes(8).toString("hex"); - const walletData = await setupWallet(walletName); - - if (!walletData || !walletData.address || !walletData.privKey) { - return res.status(500).json({ error: "Failed to generate payment address" }); - } - - // ✅ Create Order with `cryptoTotal` - const newOrder = await Order.create({ - vendorId, - storeId, - products, - pgpAddress, - totalPrice, - cryptoTotal, - shippingMethod, - telegramChatId, - telegramUsername, - telegramBuyerId, - status: "unpaid", - paymentAddress: walletData.address, - }); - - // ✅ Link the Wallet to the Order - const newWallet = await Wallet.create({ - walletName, - orderId: newOrder._id, - address: walletData.address, - encryptedPrivateKey: walletData.privKey, - }); - - // ✅ Update Order with Wallet ID - newOrder.wallet = newWallet._id; - await newOrder.save(); - - // TEMPORARILY DISABLED: Stock decrease disabled to give vendors time to add stock - // Decrease stock for ordered products - // await decreaseStockOnOrder(newOrder); - - res.status(201).json({ - message: "Order created successfully", - order: newOrder, - paymentAddress: walletData.address, - walletName: walletName, - cryptoTotal, - }); - - } catch (error) { - console.error("Error creating order:", error); - res.status(500).json({ error: "Failed to create order", details: error.message }); - } -}); - -router.put("/:id/status", protectVendor, async (req, res) => { - try { - const { id } = req.params; - const { status } = req.body; - - if (!status || !["acknowledged", "paid", "shipped", "completed", "cancelled"].includes(status)) { - return res.status(400).json({ message: "Invalid status" }); - } - - // Get storeId from req.user - const storeId = req.user.storeId; - if (!storeId) { - return res.status(400).json({ message: "No store associated with this user" }); - } - - // Find order by ID and storeId - const order = await Order.findOne({ _id: id, storeId }); - if (!order) { - return res.status(404).json({ message: "Order not found" }); - } - - const previousStatus = order.status; - - // TEMPORARILY DISABLED: Stock restoration disabled to give vendors time to add stock - // Handle stock changes based on status transitions - /* - if (status === "cancelled" && previousStatus !== "cancelled") { - // Restore stock quantities if order is cancelled - await restoreStockOnCancel(order); - } - */ - - const store = await Store.findById(order.storeId); - if (!store) return res.status(404).json({ message: "Store not found" }); - - order.status = status; - await order.save(); - - if (store.telegramToken && order.telegramChatId) { - let message = ''; - - switch (status) { - case 'acknowledged': - message = `✅ Your order ${order.orderId} has been acknowledged.`; - break; - case 'paid': - message = `💰 Your order ${order.orderId} has been marked as paid.`; - break; - case 'shipped': - message = `🚚 Your order ${order.orderId} has been shipped!`; - break; - case 'completed': - message = `🎉 Your order ${order.orderId} has been completed.`; - break; - case 'cancelled': - message = `❌ Your order ${order.orderId} has been cancelled.`; - break; - } - - if (message) { - const sent = await sendTelegramMessage(store.telegramToken, order.telegramChatId, message); - if (!sent) { - console.error(`Failed to notify user ${order.telegramChatId} about status update to ${status}.`); - } - } - } - - return res.status(200).json({ message: "Order status updated successfully" }); - } catch (error) { - console.error("❌ Error updating order status:", error); - return res.status(500).json({ error: "Failed to update order status", details: error.message }); - } -}); - -router.get("/:id", protectVendor, async (req, res) => { - if (!req.params.id) return res.status(400).json({ message: "Missing order ID" }); - - try { - // Get storeId from req.user - const storeId = req.user.storeId; - if (!storeId) { - return res.status(400).json({ message: "No store associated with this user" }); - } - - // Find order by ID and storeId - const order = await Order.findOne({ _id: req.params.id, storeId: storeId }); - - if(!order) return res.status(404).json({ message: "Order not found for this store" }); - - return res.status(200).json(order); - } catch (error) { - console.error("Error fetching order:", error); - res.status(500).json({ error: "Failed to retrieve order" }); - } -}) - -/** - * Mark Multiple Orders as Shipped - * @route POST /api/orders/mark-shipped - * @access Private (Vendors only) - */ -router.post("/mark-shipped", protectVendor, async (req, res) => { - try { - const { orderIds } = req.body; - - if (!Array.isArray(orderIds) || orderIds.length === 0) { - return res.status(400).json({ error: "Order IDs array is required" }); - } - - // Get storeId from req.user - const storeId = req.user.storeId; - if (!storeId) { - return res.status(400).json({ error: "No store associated with this user" }); - } - - const updatedOrders = []; - const failedOrders = []; - - // Get store for notifications - const store = await Store.findById(storeId); - if (!store) { - return res.status(404).json({ error: "Store not found" }); - } - - for (const orderId of orderIds) { - try { - // Find and update order using storeId instead of vendorId - const order = await Order.findOne({ _id: orderId, storeId: storeId }); - - if (!order) { - failedOrders.push({ orderId, reason: "Order not found or not associated with this store" }); - continue; - } - - if (order.status !== "paid") { - failedOrders.push({ orderId, reason: "Order must be in paid status to be marked as shipped" }); - continue; - } - - order.status = "shipped"; - await order.save(); - updatedOrders.push(order); - - // Send notification if possible - if (store.telegramToken && order.telegramChatId) { - const message = `🚚 Your order ${order.orderId} has been marked as shipped!`; - const sent = await sendTelegramMessage(store.telegramToken, order.telegramChatId, message); - - if (!sent) { - console.error(`Failed to notify user ${order.telegramChatId} about shipping update.`); - } - } - } catch (error) { - console.error(`Error updating order ${orderId}:`, error); - failedOrders.push({ orderId, reason: "Internal server error" }); - } - } - - return res.json({ - message: "Orders processed", - success: { - count: updatedOrders.length, - orders: updatedOrders.map(o => ({ - id: o._id, - orderId: o.orderId, - status: o.status - })) - }, - failed: { - count: failedOrders.length, - orders: failedOrders - } - }); - - } catch (error) { - console.error("Error marking orders as shipped:", error); - return res.status(500).json({ - error: "Failed to mark orders as shipped", - details: error.message - }); - } -}); - -/** - * Update Order Tracking Number - * @route PUT /api/orders/:id/tracking - * @access Private (Vendors only) - */ -router.put("/:id/tracking", protectVendor, async (req, res) => { - const { id } = req.params; - const { trackingNumber } = req.body; - - if (!id) return res.status(400).json({ error: "Missing order ID" }); - if (!trackingNumber) return res.status(400).json({ error: "Missing tracking number" }); - - try { - // Get storeId from req.user - const storeId = req.user.storeId; - if (!storeId) { - return res.status(400).json({ error: "No store associated with this user" }); - } - - // Use storeId for lookup instead of vendorId - const order = await Order.findOne({ _id: id, storeId: storeId }); - if (!order) return res.status(404).json({ error: "Order not found for this store" }); - - // Only allow tracking updates for paid or shipped orders - if (order.status !== "paid" && order.status !== "shipped") { - return res.status(400).json({ error: "Can only add tracking to paid or shipped orders" }); - } - - // Get store details for notification - const store = await Store.findById(order.storeId); - if (!store) return res.status(404).json({ error: "Store not found" }); - - // Update the order with tracking number - order.trackingNumber = trackingNumber; - await order.save(); - - // Send tracking notification - if (store.telegramToken && order.telegramChatId) { - const message = `📦 Tracking added for order ${order.orderId}\!`; - const sent = await sendTelegramMessage(store.telegramToken, order.telegramChatId, message); - - if (!sent) { - console.error(`Failed to notify user ${order.telegramChatId} about tracking update.`); - } - } - - return res.json({ - message: "Tracking number updated successfully", - order: { - id: order._id, - orderId: order.orderId, - status: order.status, - trackingNumber: order.trackingNumber - } - }); - } catch (error) { - console.error("Error updating tracking number:", error); - return res.status(500).json({ - error: "Failed to update tracking number", - details: error.message - }); - } -}); - -/** - * Delete Order - * @route DELETE /api/orders/:id - * @access Private (Vendors only) - */ -router.delete("/:id", protectVendor, async (req, res) => { - try { - const { id } = req.params; - - // Get storeId from req.user - const storeId = req.user.storeId; - if (!storeId) { - return res.status(400).json({ message: "No store associated with this user" }); - } - - // Find order by ID and storeId - const order = await Order.findOne({ _id: id, storeId: storeId }); - - if (!order) { - return res.status(404).json({ message: "Order not found for this store" }); - } - - // Delete associated wallet if exists - if (order.wallet) { - await Wallet.findByIdAndDelete(order.wallet); - } - - // Delete the order - await Order.findByIdAndDelete(id); - - res.status(200).json({ message: "Order deleted successfully" }); - } catch (error) { - console.error("Error deleting order:", error); - res.status(500).json({ error: "Failed to delete order" }); - } -}); - -/** - * 📌 Get Adjacent Orders (for navigation) - * @route GET /api/orders/adjacent/:orderId - * @access Private (Vendors only) - * @description Returns the immediately previous and next orders by orderId - */ -router.get("/adjacent/:orderId", protectVendor, async (req, res) => { - try { - const { orderId } = req.params; - const numericOrderId = parseInt(orderId); - - if (isNaN(numericOrderId)) { - return res.status(400).json({ error: "Invalid order ID format" }); - } - - // Get the store ID from user or query parameter - const storeId = req.query.storeId || req.user.storeId; - if (!storeId) { - return res.status(400).json({ error: "No store ID found. Please specify a storeId parameter." }); - } - - // Find the next newer order (higher orderId) - const newerOrder = await Order.findOne({ - storeId, - orderId: { $gt: numericOrderId } - }) - .sort({ orderId: 1 }) // Ascending to get the immediately next one - .select('_id orderId') - .lean(); - - // Find the next older order (lower orderId) - const olderOrder = await Order.findOne({ - storeId, - orderId: { $lt: numericOrderId } - }) - .sort({ orderId: -1 }) // Descending to get the immediately previous one - .select('_id orderId') - .lean(); - - res.json({ - current: { orderId: numericOrderId }, - newer: newerOrder || null, - older: olderOrder || null - }); - - } catch (error) { - console.error("Error fetching adjacent orders:", error); - res.status(500).json({ error: "Failed to retrieve adjacent orders" }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/products.routes.js b/backend/routes/products.routes.js deleted file mode 100644 index 88086cf..0000000 --- a/backend/routes/products.routes.js +++ /dev/null @@ -1,456 +0,0 @@ -import express from "express"; -import Product from "../models/Product.model.js"; -import multer from "multer"; -import sharp from "sharp"; -import { protectVendor } from "../middleware/authMiddleware.js"; -import fs from "fs"; -import path, { dirname } from "path"; -import Store from "../models/Store.model.js"; -import mongoose from "mongoose"; -import { fileURLToPath } from "url"; - -const router = express.Router(); - -// Get the current directory and set up a relative uploads path -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const uploadsDir = path.join(process.cwd(), 'uploads'); - -if (!fs.existsSync(uploadsDir)) { - fs.mkdirSync(uploadsDir, { recursive: true }); -} - -const upload = multer({ - storage: multer.memoryStorage(), - limits: { fileSize: 15 * 1024 * 1024 }, - 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({ message: "File size too large. Maximum allowed size is 15 MB." }); - } - return res.status(400).json({ message: "Multer error occurred.", error: err.message }); - } - next(err); -}; - -router.use(handleMulterError) - -// 📌 Upload Image for Product -router.put("/:id/image", protectVendor, upload.single("file"), handleMulterError, async (req, res) => { - try { - const productId = req.params.id; - if (!productId) return res.status(400).json({ message: "Missing product ID." }); - - const product = await Product.findOne({ _id: productId, storeId: req.user.storeId }); - if (!product) { - return res.status(404).json({ message: "Product not found." }); - } - - const file = req.file; - if (!file) return res.status(400).json({ message: "No file uploaded." }); - - // Generate a new filename for the uploaded image - const outputFileName = `${Date.now()}-${file.originalname.split('.').slice(0, -1).join('.')}.jpg`; - const outputFilePath = path.join(uploadsDir, outputFileName); - - await sharp(file.buffer) - .jpeg({ quality: 80 }) - .toFile(outputFilePath); - - // Check the final file size - const stats = fs.statSync(outputFilePath); - if (stats.size > 10 * 1024 * 1024) { - fs.unlinkSync(outputFilePath); - return res.status(400).json({ message: "File size too large after processing." }); - } - - // Update the product in the database - const updatedProduct = await Product.findOneAndUpdate( - { _id: productId, storeId: req.user.storeId }, - { image: outputFileName }, - { new: true } - ); - - if (!updatedProduct) { - fs.unlinkSync(outputFilePath); // Clean up uploaded file if product not found - return res.status(404).json({ message: "Product not found." }); - } - - res.json(updatedProduct); - } catch (error) { - if(req.file){ - const outputFileName = `${Date.now()}-${req.file.originalname.split('.').slice(0, -1).join('.')}.jpg`; - const outputFilePath = path.join(uploadsDir, outputFileName); - - if (fs.existsSync(outputFilePath)) { - fs.unlinkSync(outputFilePath); - } - } - res.status(500).json({ message: "Failed to upload image.", error }); - } -}); - -router.use(express.json({ limit: "50mb" })); - -router.get("/", protectVendor, async (req, res) => { - try { - if (!req.user.storeId) { - return res.status(400).json({ message: "Store not found for this vendor" }); - } - - const products = await Product.find({ storeId: req.user.storeId }).select("-base64Image"); - res.json(products); - } catch (error) { - res.status(500).json({ message: "Failed to fetch products", error }); - } -}); - -// 📌 Add New Product -router.post("/", protectVendor, async (req, res) => { - try { - if (!req.user.storeId) { - return res.status(400).json({ message: "Store not found for this vendor" }); - } - - const { name, description, category, unitType, pricing } = req.body; - - if (!name || !category || !unitType || !pricing || !pricing.length) { - return res.status(400).json({ message: "Missing required fields" }); - } - - const formattedPricing = pricing.map((tier) => ({ - minQuantity: tier.minQuantity, - pricePerUnit: tier.pricePerUnit, - })); - - const newProduct = new Product({ - storeId: req.user.storeId, - name, - description, - category, - unitType, - pricing: formattedPricing, - }); - - const savedProduct = await newProduct.save(); - res.status(201).json(savedProduct); - } catch (error) { - res.status(400).json({ message: "Failed to add product", error }); - } -}); - -// 📌 Edit a Product by ID -router.put("/:id", protectVendor, async (req, res) => { - try { - const productId = req.params.id; - - const updatableFields = ["name", "description", "category", "unitType"]; - - const updateFields = {}; - - for (const field of updatableFields) { - if (req.body[field] !== undefined) { - updateFields[field] = req.body[field]; - } - } - - if (req.body.pricing !== undefined) { - if (!Array.isArray(req.body.pricing)) { - return res.status(400).json({ message: "Pricing must be an array" }); - } - - updateFields.pricing = req.body.pricing.map((tier) => ({ - minQuantity: tier.minQuantity, - pricePerUnit: tier.pricePerUnit, - })); - } - - const updatedProduct = await Product.findOneAndUpdate( - { _id: productId, storeId: req.user.storeId }, - updateFields, - { new: true } - ); - - if (!updatedProduct) { - return res - .status(404) - .json({ message: "Product not found or not owned by this store" }); - } - - return res.json(updatedProduct); - } catch (error) { - res.status(400).json({ message: "Failed to update product", error }); - } -}); - -router.get("/:id/image", async (req, res) => { - try { - const productId = req.params.id; - const product = await Product.findById(productId); - - if (!product || !product.image) { - return res.status(404).json({ message: "Image not found" }); - } - - const imagePath = path.join(uploadsDir, product.image); - console.log("Image path:", imagePath); - if (fs.existsSync(imagePath)) { - res.sendFile(imagePath); - } else { - res.status(404).json({ message: "Image file does not exist" }); - } - } catch (error) { - console.error("Error fetching image:", error); - res.status(400).json({ message: "Failed to fetch image", error }); - } -}); - -router.delete("/:id", protectVendor, async (req, res) => { - try { - const productId = req.params.id; - - const deletedProduct = await Product.findByIdAndDelete({ _id: productId, storeId: req.user.storeId }); - - if (!deletedProduct) { - return res.status(404).json({ message: "Product not found" }); - } - - res.json({ message: "Product deleted successfully" }); - } catch (error) { - res.status(400).json({ message: "Failed to delete product", error }); - } -}); - -router.get("/:id", protectVendor, async (req, res) => { - try { - const productId = req.params.id; - const product = await Product.findById(productId); - - if (!product) { - return res.status(404).json({ message: "Product not found" }); - } - - res.status(200).json(product); - } catch (error) { - res.status(400).json({ message: "Failed to fetch product", error }); - } -}); - -// Helper function to escape special regex characters -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -// Helper function to standardize unit types -function standardizeUnitType(unit) { - const unitMap = { - 'gram': 'gr', - 'grams': 'gr', - 'g': 'gr', - 'gr': 'gr', - 'piece': 'pcs', - 'pieces': 'pcs', - 'pcs': 'pcs', - 'kilo': 'kg', - 'kilos': 'kg', - 'kilogram': 'kg', - 'kilograms': 'kg', - 'kg': 'kg' - }; - - return unitMap[unit.toLowerCase()] || unit.toLowerCase(); -} - -// 📌 Batch Add Products -router.post("/batch", protectVendor, async (req, res) => { - try { - if (!req.user.storeId) { - return res.status(400).json({ message: "Store not found for this vendor" }); - } - - const { products } = req.body; - - if (!Array.isArray(products) || products.length === 0) { - return res.status(400).json({ message: "Products array is required and cannot be empty" }); - } - - // Get store to validate categories - const store = await Store.findById(req.user.storeId); - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - const processedProducts = []; - const skippedProducts = []; - let completedCount = 0; - - console.log(`Starting batch processing of ${products.length} products...`); - - for (const product of products) { - const { name, category, subcategory, prices } = product; - completedCount++; - - // Validate required fields - if (!name || !category || !prices || !Array.isArray(prices) || prices.length === 0) { - console.log(`Skipping product ${completedCount}/${products.length}: Missing required fields`); - skippedProducts.push({ name: name || 'Unknown', reason: 'Missing required fields' }); - continue; - } - - // Check if product already exists - with escaped special characters - const escapedName = escapeRegExp(name); - const existingProduct = await Product.findOne({ - storeId: req.user.storeId, - name: { $regex: new RegExp(`^${escapedName}$`, 'i') } - }); - - if (existingProduct) { - console.log(`Skipping product ${completedCount}/${products.length}: "${name}" already exists`); - skippedProducts.push({ name, reason: 'Product already exists' }); - continue; - } - - // Find or create main category - let targetCategory; - const existingMainCategory = store.categories.find(cat => - cat.name.toLowerCase() === category.toLowerCase() && !cat.parentId - ); - - if (!existingMainCategory) { - const newCategory = { - _id: new mongoose.Types.ObjectId(), - name: category, - parentId: null - }; - store.categories.push(newCategory); - targetCategory = newCategory; - console.log(`Created new main category: ${category}`); - } else { - targetCategory = existingMainCategory; - } - - // If subcategory is provided, find or create it - if (subcategory) { - const existingSubcategory = store.categories.find(cat => - cat.name.toLowerCase() === subcategory.toLowerCase() && - cat.parentId?.toString() === targetCategory._id.toString() - ); - - if (existingSubcategory) { - targetCategory = existingSubcategory; - } else { - const newSubcategory = { - _id: new mongoose.Types.ObjectId(), - name: subcategory, - parentId: targetCategory._id - }; - store.categories.push(newSubcategory); - targetCategory = newSubcategory; - console.log(`Created new subcategory: ${subcategory} under ${category}`); - } - } - - console.log(targetCategory._id) - - // Convert prices array to pricing format - const pricing = prices.map(price => ({ - minQuantity: price.quantity, - pricePerUnit: price.pricePerUnit - })); - - const standardizedUnitType = standardizeUnitType(prices[0].unit); - - // Validate unit type - if (!["pcs", "gr", "kg"].includes(standardizedUnitType)) { - console.log(`Skipping product ${completedCount}/${products.length}: Invalid unit type "${prices[0].unit}"`); - skippedProducts.push({ name, reason: `Invalid unit type: ${prices[0].unit}` }); - continue; - } - - const newProduct = new Product({ - storeId: req.user.storeId, - name, - category: targetCategory._id, - unitType: standardizedUnitType, - pricing - }); - - try { - await store.save(); - const savedProduct = await newProduct.save(); - processedProducts.push(savedProduct); - console.log(`Processed ${completedCount}/${products.length}: Successfully added "${name}"`); - } catch (error) { - console.error(`Failed to save product ${completedCount}/${products.length}: "${name}"`, error); - skippedProducts.push({ name, reason: `Save error: ${error.message}` }); - } - } - - console.log(`Batch processing completed. Processed: ${processedProducts.length}, Skipped: ${skippedProducts.length}`); - - if (processedProducts.length === 0) { - return res.status(400).json({ - message: "No valid products were processed", - totalSubmitted: products.length, - totalProcessed: 0, - skippedProducts - }); - } - - res.status(201).json({ - message: "Products batch processed successfully", - totalSubmitted: products.length, - totalProcessed: processedProducts.length, - totalSkipped: skippedProducts.length, - products: processedProducts, - skippedProducts, - categories: store.categories - }); - - } catch (error) { - console.error("Batch processing error:", error); - res.status(500).json({ - message: "Failed to process product batch", - error: error.message - }); - } -}); - -// 📌 Disable stock tracking for all products (one-time setup route) -router.post("/disable-stock-tracking", protectVendor, async (req, res) => { - try { - // Ensure the user has admin privileges or apply appropriate restrictions here - if (!req.user.storeId) { - return res.status(400).json({ message: "Store not found for this vendor" }); - } - - // Update all products for this store to disable stock tracking - const result = await Product.updateMany( - { storeId: req.user.storeId }, - { - stockTracking: false, - stockStatus: "in_stock" // Set a default status to avoid UI issues - } - ); - - res.status(200).json({ - message: "Stock tracking disabled for all products", - modifiedCount: result.modifiedCount, - matchedCount: result.matchedCount - }); - } catch (error) { - console.error("Error disabling stock tracking:", error); - res.status(500).json({ message: "Failed to disable stock tracking", error: error.message }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/promotion.routes.js b/backend/routes/promotion.routes.js deleted file mode 100644 index 0daa955..0000000 --- a/backend/routes/promotion.routes.js +++ /dev/null @@ -1,24 +0,0 @@ -import express from "express"; -import { - getPromotions, - getPromotionById, - createPromotion, - updatePromotion, - deletePromotion, - validatePromotion -} from "../controllers/promotion.controller.js"; -import { protectVendor } from "../middleware/vendorAuthMiddleware.js"; - -const router = express.Router(); - -// Vendor routes for managing their own promotions (protected) -router.get("/", protectVendor, getPromotions); -router.get("/:id", protectVendor, getPromotionById); -router.post("/", protectVendor, createPromotion); -router.put("/:id", protectVendor, updatePromotion); -router.delete("/:id", protectVendor, deletePromotion); - -// Public route for validating a promotion code -router.post("/validate/:storeId", validatePromotion); - -export default router; \ No newline at end of file diff --git a/backend/routes/shipping.routes.js b/backend/routes/shipping.routes.js deleted file mode 100644 index cac664f..0000000 --- a/backend/routes/shipping.routes.js +++ /dev/null @@ -1,151 +0,0 @@ -import express from "express"; -import Store from "../models/Store.model.js"; -import { protectVendor } from "../middleware/authMiddleware.js"; // Protect the vendor -import mongoose from "mongoose"; // Import mongoose to use ObjectId - -const router = express.Router(); - -/** - * 📌 Get Shipping Options for Vendor's Store - * @route GET /api/shipping-options - * @access Private (Vendors only) - */ -router.get("/", protectVendor, async (req, res) => { - try { - if (!req.user.storeId) { - return res - .status(400) - .json({ message: "Store not found for this vendor" }); - } - - const store = await Store.findOne({ vendorId: req.user._id }).select( - "shippingOptions" - ); - - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - res.json(store.shippingOptions); - } catch (error) { - res - .status(500) - .json({ message: "Failed to fetch shipping options", error }); - } -}); - -/** - * 📌 Add New Shipping Option for Vendor's Store - * @route POST /api/shipping-options - * @access Private (Vendors only) - */ -router.post("/", protectVendor, async (req, res) => { - const { name, price } = req.body; - - if (!name || !price) { - return res.status(400).json({ message: "Missing required fields" }); - } - - try { - // Find the store by vendorId (user) - const store = await Store.findOne({ vendorId: req.user._id }); - - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - // Add the new shipping option to the store's shippingOptions array - store.shippingOptions.push({ name, price }); - - // Save the store with the new shipping option - await store.save(); - - res.status(201).json(store.shippingOptions); - } catch (error) { - res.status(500).json({ message: "Failed to add shipping option", error }); - } -}); - -/** - * 📌 Delete Shipping Option for Vendor's Store - * @route DELETE /api/shipping-options/:id - * @access Private (Vendors only) - */ -router.delete("/:id", protectVendor, async (req, res) => { - const { id } = req.params; - - try { - const store = await Store.findOne({ vendorId: req.user._id }); - - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - // Use find to find the shipping option by its 'id' field - const shippingOption = store.shippingOptions.find( - (option) => option.id.toString() === id - ); - - if (!shippingOption) { - return res.status(404).json({ message: "Shipping option not found" }); - } - - // Remove the shipping option - const index = store.shippingOptions.indexOf(shippingOption); - store.shippingOptions.splice(index, 1); // Remove the shipping option from the array - - await store.save(); // Save the updated store - - res.status(204).json({ message: "Shipping option deleted successfully" }); - } catch (error) { - console.error(error); - res - .status(500) - .json({ message: "Failed to delete shipping option", error }); - } -}); - -/** - * 📌 Edit Shipping Option for Vendor's Store - * @route PUT /api/shipping-options/:id - * @access Private (Vendors only) - */ -router.put("/:id", protectVendor, async (req, res) => { - const { name, price } = req.body; - const { id } = req.params; - - if (!name || !price) { - return res.status(400).json({ message: "Missing required fields" }); - } - - try { - const store = await Store.findOne({ vendorId: req.user._id }); - - if (!store) { - return res.status(404).json({ message: "Store not found" }); - } - - // Use find to find the shipping option by its 'id' field - const shippingOption = store.shippingOptions.find( - (option) => option.id.toString() === id - ); - - if (!shippingOption) { - return res.status(404).json({ message: "Shipping option not found" }); - } - - shippingOption.name = name; // Update the name - shippingOption.price = price; // Update the price - - await store.save(); // Save the updated store - - res.json(shippingOption); // Return the updated shipping option - } catch (error) { - console.error(error); - res - .status(500) - .json({ message: "Failed to update shipping option", error }); - } -}); - -export default router; \ No newline at end of file diff --git a/backend/routes/staffAuth.routes.js b/backend/routes/staffAuth.routes.js deleted file mode 100644 index 6bc9f94..0000000 --- a/backend/routes/staffAuth.routes.js +++ /dev/null @@ -1,84 +0,0 @@ -import express from "express"; -import bcrypt from "bcryptjs"; -import jwt from "jsonwebtoken"; -import Staff from "../models/Staff.model.js"; -import { protectStaff, logoutStaff } from "../middleware/staffAuthMiddleware.js"; - -const router = express.Router(); - -/** - * 📌 Staff Login - Store JWT in Database - */ -router.post("/login", async (req, res) => { - const { username, password } = req.body; - - try { - const staff = await Staff.findOne({ username }); - if (!staff) { - return res.status(401).json({ error: "Staff user not found" }); - } - - const isMatch = await bcrypt.compare(password, staff.passwordHash); - if (!isMatch) { - return res.status(401).json({ error: "Invalid credentials" }); - } - - // Generate JWT for Staff/Admin - const token = jwt.sign( - { id: staff._id, role: staff.role }, - process.env.JWT_SECRET, - { expiresIn: "7d" } - ); - - // Store token in database - staff.currentToken = token; - await staff.save(); - - res.json({ token, role: staff.role }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - -/** - * 📌 Staff Logout - Remove JWT from Database - */ -router.post("/logout", protectStaff, logoutStaff); - -/** - * 📌 Force Logout All Staff Users (Admin Only) - */ -router.post("/logout/all", protectStaff, async (req, res) => { - try { - if (req.user.role !== "admin") { - return res.status(403).json({ error: "Access restricted to admins only" }); - } - - await Staff.updateMany({}, { currentToken: null }); - - res.json({ message: "All staff users have been logged out" }); - } catch (error) { - res.status(500).json({ error: "Failed to log out all staff users" }); - } -}); - -/** - * 📌 Check Staff Sessions (Admin Only) - */ -router.get("/sessions", protectStaff, async (req, res) => { - try { - if (req.user.role !== "admin") { - return res.status(403).json({ error: "Access restricted to admins only" }); - } - - const activeSessions = await Staff.find({ currentToken: { $ne: null } }) - .select("username role currentToken createdAt"); - - res.json({ activeSessions }); - } catch (error) { - res.status(500).json({ error: "Failed to fetch active sessions" }); - } -}); - - -export default router; \ No newline at end of file diff --git a/backend/routes/stock.routes.js b/backend/routes/stock.routes.js deleted file mode 100644 index 166a3bf..0000000 --- a/backend/routes/stock.routes.js +++ /dev/null @@ -1,13 +0,0 @@ -import express from "express"; -import { protectVendor } from "../middleware/authMiddleware.js"; -import { updateStock, getStoreStock } from "../controllers/stock.controller.js"; - -const router = express.Router(); - -// Get all product stock information for a store -router.get("/", protectVendor, getStoreStock); - -// Update stock for a specific product -router.put("/:productId", protectVendor, updateStock); - -export default router; \ No newline at end of file diff --git a/backend/routes/storefront.routes.js b/backend/routes/storefront.routes.js deleted file mode 100644 index f0f0a66..0000000 --- a/backend/routes/storefront.routes.js +++ /dev/null @@ -1,265 +0,0 @@ -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; \ No newline at end of file diff --git a/backend/scripts/disableAllStockTracking.js b/backend/scripts/disableAllStockTracking.js deleted file mode 100644 index cdbe6e5..0000000 --- a/backend/scripts/disableAllStockTracking.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -import disableStockTrackingForAllProducts from '../utils/disableStockTracking.js'; -import logger from '../utils/logger.js'; - -logger.info('Starting script to disable stock tracking for all products'); - -disableStockTrackingForAllProducts() - .then((result) => { - logger.info('Operation completed successfully!'); - logger.info(`Modified ${result.modifiedCount} products out of ${result.matchedCount} total products`); - process.exit(0); - }) - .catch((error) => { - logger.error('Operation failed:', error); - process.exit(1); - }); \ No newline at end of file diff --git a/backend/test.js b/backend/test.js deleted file mode 100644 index ebbb247..0000000 --- a/backend/test.js +++ /dev/null @@ -1,8 +0,0 @@ -import logger from './utils/logger.js'; - -logger.info('Backend integration test successful!'); -console.log('Backend integration test successful!'); - -export default function testBackendIntegration() { - return { success: true, message: 'Backend integration test successful!' }; -} \ No newline at end of file diff --git a/backend/utils/createAdmin.js b/backend/utils/createAdmin.js deleted file mode 100644 index 5d266d4..0000000 --- a/backend/utils/createAdmin.js +++ /dev/null @@ -1,44 +0,0 @@ -import mongoose from "mongoose"; -import bcrypt from "bcryptjs"; -import dotenv from "dotenv"; -import Staff from "../models/Staff.model.js"; - -dotenv.config(); - -mongoose - .connect(process.env.MONGO_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, - }) - .then(() => console.log("✅ MongoDB Connected")) - .catch((err) => console.error("❌ MongoDB Connection Error:", err)); - -const createAdmin = async () => { - const username = "admin"; - const password = "88sO)£2igu-:"; - - try { - const existingAdmin = await Staff.findOne({ username }); - if (existingAdmin) { - console.log("⚠️ Admin user already exists."); - process.exit(1); - } - - const hashedPassword = await bcrypt.hash(password, 10); - - const admin = new Staff({ - username, - passwordHash: hashedPassword, - role: "admin", - }); - - await admin.save(); - console.log(`✅ Admin user '${username}' created successfully!`); - process.exit(); - } catch (error) { - console.error("❌ Error creating admin user:", error); - process.exit(1); - } -}; - -createAdmin(); diff --git a/backend/utils/createFakeOrder.js b/backend/utils/createFakeOrder.js deleted file mode 100644 index 49c34ba..0000000 --- a/backend/utils/createFakeOrder.js +++ /dev/null @@ -1,56 +0,0 @@ -import mongoose from "mongoose"; -import dotenv from "dotenv"; -import Order from "../models/Order.model.js"; // Adjust path if needed -import Vendor from "../models/Vendor.model.js"; // Import Vendor model - -dotenv.config(); - -// ✅ Connect to MongoDB -const mongoUri = process.env.MONGO_URI || "mongodb://localhost:27017/yourDatabaseName"; -mongoose - .connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true }) - .then(() => console.log("✅ Connected to MongoDB")) - .catch((err) => console.error("❌ MongoDB Connection Error:", err)); - -// ✅ Insert Fake Order for an Existing Vendor -async function insertFakeOrder() { - try { - // ✅ Find an existing vendor - const existingVendor = await Vendor.findOne(); - if (!existingVendor) { - console.log("❌ No vendors found. Create a vendor first."); - return; - } - - console.log(`✅ Using Vendor: ${existingVendor.username} (${existingVendor._id})`); - - const fakeOrder = new Order({ - buyerId: new mongoose.Types.ObjectId(), // Fake buyer - vendorId: existingVendor._id, // Assign to existing vendor - storeId: new mongoose.Types.ObjectId(), - products: [ - { - productId: new mongoose.Types.ObjectId(), - quantity: 2, - pricePerUnit: 25.99, - totalItemPrice: 51.98, - }, - ], - totalPrice: 51.98, - status: "paid", - paymentAddress: "ltc1qxyzfakeaddress123456", - txid: "faketxid1234567890abcdef", - escrowExpiresAt: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000), // 8 days from now - }); - - const savedOrder = await fakeOrder.save(); - console.log("✅ Fake Order Inserted:", savedOrder); - } catch (error) { - console.error("❌ Error inserting fake order:", error); - } finally { - mongoose.connection.close(); // Close DB connection - } -} - -// ✅ Run Script -insertFakeOrder(); diff --git a/backend/utils/createInvitation.js b/backend/utils/createInvitation.js deleted file mode 100644 index 2566df4..0000000 --- a/backend/utils/createInvitation.js +++ /dev/null @@ -1,67 +0,0 @@ -import mongoose from "mongoose"; -import dotenv from "dotenv"; -import Staff from "../models/Staff.model.js" -import Invitation from "../models/Invitation.model.js" -import crypto from "crypto"; - - -dotenv.config(); - -// ✅ Connect to MongoDB -const mongoUri = process.env.MONGO_URI; -mongoose - .connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true }) - .then(() => console.log("✅ Connected to MongoDB")) - .catch((err) => console.error("❌ MongoDB Connection Error:", err)); - -const generateInviteCode = () => { - return crypto.randomBytes(16).toString('hex'); -}; - -const createInvitation = async (staffEmail) => { - try { - // Find staff member - const staff = await Staff.findOne({ username: "admin" }); - if (!staff) { - throw new Error("Staff member not found"); - } - - // Check if staff has permission to create invitations - if (!['admin', 'support'].includes(staff.role)) { - throw new Error("Insufficient permissions to create vendor invitations"); - } - - // Generate unique invite code - const inviteCode = generateInviteCode(); - - // Create invitation - const invitation = await Invitation.create({ - code: inviteCode, - createdBy: staff._id, - expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now - isUsed: false - }); - - console.log(`✅ Vendor invitation created successfully! -Code: ${invitation.code} -Created by: ${staff.email} -Expires: ${invitation.expiresAt} - `); - - // Exit process after creating invitation - process.exit(0); - } catch (error) { - console.error("❌ Error creating invitation:", error.message); - process.exit(1); - } -}; - -// Get staff email from command line argument -const staffEmail = process.argv[2]; - -if (!staffEmail) { - console.error("❌ Please provide staff email: node createInvitation.js "); - process.exit(1); -} - -createInvitation(staffEmail); \ No newline at end of file diff --git a/backend/utils/createKeys.js b/backend/utils/createKeys.js deleted file mode 100644 index cb5c357..0000000 --- a/backend/utils/createKeys.js +++ /dev/null @@ -1,7 +0,0 @@ -import crypto from "crypto"; - -const encryptionKey = crypto.randomBytes(32).toString("hex"); // 32 bytes (256-bit) -const iv = crypto.randomBytes(16).toString("hex"); // 16 bytes (128-bit) - -console.log("Encryption Key:", encryptionKey); -console.log("IV:", iv); \ No newline at end of file diff --git a/backend/utils/disableStockTracking.js b/backend/utils/disableStockTracking.js deleted file mode 100644 index 055ad0a..0000000 --- a/backend/utils/disableStockTracking.js +++ /dev/null @@ -1,55 +0,0 @@ -import mongoose from 'mongoose'; -import dotenv from 'dotenv'; -import Product from '../models/Product.model.js'; -import logger from './logger.js'; - -// Load environment variables -dotenv.config(); - -// Connect to MongoDB -mongoose.connect(process.env.MONGO_URI) - .then(() => logger.info('MongoDB connected')) - .catch(err => { - logger.error('MongoDB connection error:', err); - process.exit(1); - }); - -async function disableStockTrackingForAllProducts() { - try { - logger.info('Disabling stock tracking for all products...'); - - const result = await Product.updateMany( - {}, // Empty filter matches all documents - { - stockTracking: false, - stockStatus: "in_stock" // Set a default status - } - ); - - logger.info(`Stock tracking disabled for ${result.modifiedCount} products out of ${result.matchedCount} total products`); - - return result; - } catch (error) { - logger.error('Error disabling stock tracking:', error); - throw error; - } finally { - // Close database connection - mongoose.connection.close(); - logger.info('Database connection closed'); - } -} - -// Execute the function if this script is run directly -if (process.argv[1].includes('disableStockTracking.js')) { - disableStockTrackingForAllProducts() - .then(() => { - logger.info('Script completed successfully'); - process.exit(0); - }) - .catch(err => { - logger.error('Script failed:', err); - process.exit(1); - }); -} - -export default disableStockTrackingForAllProducts; \ No newline at end of file diff --git a/backend/utils/encryptPgp.js b/backend/utils/encryptPgp.js deleted file mode 100644 index b4ede13..0000000 --- a/backend/utils/encryptPgp.js +++ /dev/null @@ -1,54 +0,0 @@ -import * as openpgp from 'openpgp'; -import logger from './logger.js'; - -/** - * Encrypts a message using PGP - * @param {string} message - The message to encrypt - * @param {string} publicKey - PGP public key for encryption - * @returns {Promise} - The encrypted message - */ -export const encryptWithPGP = async (message, publicKey) => { - try { - // Parse the public key - const decodedPublicKey = await openpgp.readKey({ armoredKey: publicKey }); - - // Encrypt the message - const encrypted = await openpgp.encrypt({ - message: await openpgp.createMessage({ text: message }), - encryptionKeys: decodedPublicKey - }); - - return encrypted; - } catch (error) { - logger.error('Error during PGP encryption', { error: error.message }); - throw new Error('Failed to encrypt message: ' + error.message); - } -}; - -/** - * Decrypts a message using PGP - * @param {string} encryptedMessage - The encrypted message - * @param {string} privateKey - PGP private key for decryption - * @param {string} passphrase - Passphrase for the private key - * @returns {Promise} - The decrypted message - */ -export const decryptWithPGP = async (encryptedMessage, privateKey, passphrase) => { - try { - // Parse the private key - const decodedPrivateKey = await openpgp.readPrivateKey({ - armoredKey: privateKey - }); - - // Decrypt the message - const decrypted = await openpgp.decrypt({ - message: await openpgp.readMessage({ armoredMessage: encryptedMessage }), - decryptionKeys: decodedPrivateKey, - config: { allowInsecureDecryptionWithSignature: true } - }); - - return decrypted.data; - } catch (error) { - logger.error('Error during PGP decryption', { error: error.message }); - throw new Error('Failed to decrypt message: ' + error.message); - } -}; \ No newline at end of file diff --git a/backend/utils/litecoin/index.js b/backend/utils/litecoin/index.js deleted file mode 100644 index 829e8a4..0000000 --- a/backend/utils/litecoin/index.js +++ /dev/null @@ -1,96 +0,0 @@ -import ky from "ky"; - -const rpcUrl = "http://152.53.124.126:9332/"; -const rpcUser = "notiiwasntherexdddd"; -const rpcPassword = "NYwsxePgMrThiapHnfCzUfaEfVlNKZECwvlqhHcWjerlZfcaTp"; - -const headers = { - "Content-Type": "application/json", - Authorization: `Basic ${Buffer.from(`${rpcUser}:${rpcPassword}`).toString( - "base64" - )}`, -}; - -async function callRpc(method, params = [], walletName = null) { - console.log(`Calling RPC method: ${method} with params: ${JSON.stringify(params)}`); - const url = walletName ? `${rpcUrl}wallet/${walletName}` : rpcUrl; - - try { - const response = await ky - .post(url, { - json: { - jsonrpc: "1.0", - id: "curltest", - method, - params, - }, - headers, - }) - .json(); - - console.log( - `RPC Response for method ${method}:`, - JSON.stringify(response, null, 2) - ); - - if (response.error) { - throw new Error(`RPC Error: ${JSON.stringify(response.error, null, 2)}`); - } - - return response.result; - } catch (error) { - console.error(`Error calling RPC method ${method}:`, error); - throw error; - } -} - -async function createWallet(walletName) { - const result = await callRpc("createwallet", [walletName]); - console.log("Wallet created:", result); - return result; -} - -async function generateAddress(walletName = null) { - const address = await callRpc("getnewaddress", [], walletName); - console.log("Generated Address:", address); - return address; -} - -async function checkWalletLoaded(walletName) { - const walletInfo = await callRpc("getwalletinfo", [], walletName); - console.log("Wallet Info:", walletInfo); - return walletInfo; -} - -async function walletExists(walletName) { - try { - await callRpc("getwalletinfo", [], walletName); - return true; - } catch (error) { - return false; - } -} - -async function dumpPrivateKey(address, walletName) { - const privateKey = await callRpc("dumpprivkey", [address], walletName); - console.log("Private Key:", privateKey); - return privateKey; -} - -async function setupWallet(walletName) { - const createResult = await createWallet(walletName); - const address = await generateAddress(walletName); - - console.log("Address:", address); - console.log("Wallet Name:", walletName); - console.log("Create Result:", createResult); - - const privKey = await dumpPrivateKey(address, walletName); - - console.log("Wallet Info:", privKey); - - - return { address, privKey }; -} - -export { setupWallet }; diff --git a/backend/utils/logger.js b/backend/utils/logger.js deleted file mode 100644 index a600c6d..0000000 --- a/backend/utils/logger.js +++ /dev/null @@ -1,77 +0,0 @@ -const colors = { - reset: "\x1b[0m", - bright: "\x1b[1m", - dim: "\x1b[2m", - underscore: "\x1b[4m", - blink: "\x1b[5m", - reverse: "\x1b[7m", - hidden: "\x1b[8m", - - fgBlack: "\x1b[30m", - fgRed: "\x1b[31m", - fgGreen: "\x1b[32m", - fgYellow: "\x1b[33m", - fgBlue: "\x1b[34m", - fgMagenta: "\x1b[35m", - fgCyan: "\x1b[36m", - fgWhite: "\x1b[37m", - - bgBlack: "\x1b[40m", - bgRed: "\x1b[41m", - bgGreen: "\x1b[42m", - bgYellow: "\x1b[43m", - bgBlue: "\x1b[44m", - bgMagenta: "\x1b[45m", - bgCyan: "\x1b[46m", - bgWhite: "\x1b[47m", -}; - -/** - * Formats a timestamp for logging. - * @returns {string} Formatted timestamp - */ -const getTimestamp = () => { - return new Date().toISOString(); -}; - -/** - * Logs an INFO message. - * @param {string} message - Log message - * @param {Object} [data] - Additional data - */ -const info = (message, data = null) => { - console.log(`${colors.fgGreen}[INFO] ${getTimestamp()} - ${message}${colors.reset}`); - if (data) console.log(data); -}; - -/** - * Logs a WARNING message. - * @param {string} message - Log message - * @param {Object} [data] - Additional data - */ -const warn = (message, data = null) => { - console.warn(`${colors.fgYellow}[WARN] ${getTimestamp()} - ${message}${colors.reset}`); - if (data) console.warn(data); -}; - -/** - * Logs an ERROR message. - * @param {string} message - Log message - * @param {Object} [data] - Additional data - */ -const error = (message, data = null) => { - console.error(`${colors.fgRed}[ERROR] ${getTimestamp()} - ${message}${colors.reset}`); - if (data) console.error(data); -}; - -/** - * Logs a DEBUG message. - * @param {string} message - Log message - * @param {Object} [data] - Additional data - */ -const debug = (message, data = null) => { - console.log(`${colors.fgBlue}[DEBUG] ${getTimestamp()} - ${message}${colors.reset}`); - if (data) console.log(data); -}; - -export default { info, warn, error, debug }; \ No newline at end of file diff --git a/backend/utils/telegramUtils.js b/backend/utils/telegramUtils.js deleted file mode 100644 index efcf3d6..0000000 --- a/backend/utils/telegramUtils.js +++ /dev/null @@ -1,171 +0,0 @@ -import ky from "ky"; -import logger from "../utils/logger.js"; -import FormData from "form-data"; -import fs from "fs"; -import path from "path"; - -// Function to escape special characters for MarkdownV2 -const escapeMarkdown = (text) => { - return text.replace(/([_*\[\]()~`>#+=|{}.!\\-])/g, '\\$1'); -}; - -// Function to preserve markdown formatting while escaping other special characters -const preserveMarkdown = (text) => { - // First, temporarily replace valid markdown - text = text - .replace(/\*\*(.*?)\*\*/g, '%%%BOLD%%%$1%%%BOLD%%%') - .replace(/__(.*?)__/g, '%%%ITALIC%%%$1%%%ITALIC%%%') - .replace(/`(.*?)`/g, '%%%CODE%%%$1%%%CODE%%%') - .replace(/\[(.*?)\]\((.*?)\)/g, '%%%LINK_TEXT%%%$1%%%LINK_URL%%%$2%%%LINK%%%'); - - // Escape all special characters - text = escapeMarkdown(text); - - // Restore markdown formatting - return text - .replace(/%%%BOLD%%%(.*?)%%%BOLD%%%/g, '*$1*') - .replace(/%%%ITALIC%%%(.*?)%%%ITALIC%%%/g, '_$1_') - .replace(/%%%CODE%%%(.*?)%%%CODE%%%/g, '`$1`') - .replace(/%%%LINK_TEXT%%%(.*?)%%%LINK_URL%%%(.*?)%%%LINK%%%/g, '[$1]($2)'); -}; - -/** - * Sends a message via a Telegram bot. - * @param {string} telegramToken - The bot's API token. - * @param {number} chatId - The recipient's Telegram chat ID. - * @param {string} message - The message text. - * @param {string} [photoPath] - Optional path to photo file. - * @returns {Promise} - Returns `true` if the message is sent successfully, otherwise `false`. - */ -export const sendTelegramMessage = async (telegramToken, chatId, message, photoPath = null) => { - try { - // Process message text if it exists - const processedMessage = message?.trim() ? preserveMarkdown(message) : ''; - - if (photoPath) { - const formData = new FormData(); - formData.append('chat_id', chatId); - - // Debug information before reading file - logger.info('Attempting to read file:', { - photoPath, - exists: fs.existsSync(photoPath), - stats: fs.existsSync(photoPath) ? fs.statSync(photoPath) : null, - dirname: path.dirname(photoPath), - basename: path.basename(photoPath) - }); - - try { - // Ensure the file exists before reading - if (!fs.existsSync(photoPath)) { - throw new Error(`File does not exist at path: ${photoPath}`); - } - - // Create read stream and append to FormData - const photoStream = fs.createReadStream(photoPath); - formData.append('photo', photoStream); - - if (processedMessage) { - formData.append('caption', processedMessage); - formData.append('parse_mode', 'MarkdownV2'); - } - - // Debug the FormData contents - logger.info('FormData created:', { - boundaryUsed: formData.getBoundary?.() || 'No boundary found', - headers: formData.getHeaders?.() || 'No headers available', - messageLength: processedMessage.length - }); - - // Wait for the entire request to complete - await new Promise((resolve, reject) => { - formData.submit({ - host: 'api.telegram.org', - path: `/bot${telegramToken}/sendPhoto`, - protocol: 'https:', - }, (err, res) => { - if (err) { - reject(err); - return; - } - - let data = ''; - res.on('data', chunk => { - data += chunk; - }); - - res.on('end', () => { - try { - const response = JSON.parse(data); - if (!response.ok) { - reject(new Error(response.description || 'Failed to send photo')); - } else { - resolve(response); - } - } catch (e) { - reject(new Error('Failed to parse Telegram response')); - } - }); - }); - }); - - } catch (error) { - // Log detailed error information - logger.error(`❌ Telegram API Error for chat ${chatId}:`, { - error: error.message, - response: error.response ? await error.response.json().catch(() => ({})) : undefined, - photoPath, - messageLength: processedMessage.length, - fileExists: fs.existsSync(photoPath), - fileStats: fs.existsSync(photoPath) ? fs.statSync(photoPath) : null - }); - throw error; - } - } else { - try { - await ky.post(`https://api.telegram.org/bot${telegramToken}/sendMessage`, { - json: { - chat_id: chatId, - text: processedMessage, - parse_mode: "MarkdownV2", - }, - timeout: 5000, - }); - } catch (error) { - // Log detailed error information - logger.error(`❌ Telegram API Error for chat ${chatId}:`, { - error: error.message, - response: await error.response?.json().catch(() => ({})), - messageLength: processedMessage.length - }); - throw error; - } - } - - logger.info(`✅ Telegram message sent to ${chatId}`); - return true; - } catch (error) { - logger.error(`❌ Failed to send Telegram message to ${chatId}: ${error.message}`); - return false; - } -}; - -/** - * Sends messages in bulk while respecting Telegram's rate limits. - * @param {string} telegramToken - The bot's API token. - * @param {Array} chatIds - An array of chat IDs to send to. - * @param {string} message - The message text. - * @param {string} [photoPath] - Optional path to photo file. - * @returns {Promise} - */ -export const sendBulkTelegramMessages = async (telegramToken, chatIds, message, photoPath = null) => { - for (let i = 0; i < chatIds.length; i++) { - const chatId = chatIds[i]; - await sendTelegramMessage(telegramToken, chatId, message, photoPath); - - if ((i + 1) % 30 === 0) { - logger.info(`⏳ Rate limit pause (sent ${i + 1} messages)...`); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } -}; \ No newline at end of file diff --git a/backend/utils/walletUtils.js b/backend/utils/walletUtils.js deleted file mode 100644 index e69de29..0000000 diff --git a/server.js b/server.js deleted file mode 100644 index 3340f41..0000000 --- a/server.js +++ /dev/null @@ -1,125 +0,0 @@ -// Load environment variables first -import dotenv from 'dotenv'; - -// Load backend-specific environment variables -dotenv.config({ path: '.env.backend' }); -// Then load frontend environment variables (will override if there are duplicates) -dotenv.config(); - -import express from 'express'; -import { createServer } from 'http'; -import { parse } from 'url'; -import next from 'next'; -import cors from 'cors'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; - -// Import your backend routes -import connectDB from './backend/config/db.js'; -import authRoutes from './backend/routes/auth.routes.js'; -import inviteRoutes from './backend/routes/invites.routes.js'; -import staffAuthRoutes from './backend/routes/staffAuth.routes.js'; -import orderRoutes from './backend/routes/orders.routes.js'; -import productRoutes from './backend/routes/products.routes.js'; -import categoryRoutes from './backend/routes/categories.routes.js'; -import shippingRoutes from './backend/routes/shipping.routes.js'; -import storeRoutes from './backend/routes/storefront.routes.js'; -import cryptoRoutes from './backend/routes/crypto.routes.js'; -import blockedUsersRoutes from './backend/routes/blockedUsers.routes.js'; -import chatRoutes from './backend/routes/chat.routes.js'; -import stockRoutes from './backend/routes/stock.routes.js'; -import promotionRoutes from './backend/routes/promotion.routes.js'; -import { startCryptoPriceUpdater } from './backend/controllers/cryptoController.js'; -import { protectTelegramApi } from './backend/middleware/telegramAuthMiddleware.js'; -import { processTelegramMessage, createTelegramChat } from './backend/controllers/chat.controller.js'; -import logger from './backend/utils/logger.js'; - -const dev = process.env.NODE_ENV !== 'production'; -const hostname = 'localhost'; -const port = process.env.PORT || 3000; - -// Initialize Next.js -const app = next({ dev, hostname, port }); -const handle = app.getRequestHandler(); - -app.prepare().then(() => { - const server = express(); - - // Connect to MongoDB - connectDB(); - - // Add security headers and handle CORS - server.use((req, res, next) => { - // Basic security headers - res.setHeader('X-Content-Type-Options', 'nosniff'); - res.setHeader('X-Frame-Options', 'DENY'); - res.setHeader('X-XSS-Protection', '1; mode=block'); - - // Handle CORS - const origin = req.headers.origin; - const host = req.headers.host; - - // CORS handling (simplified for local development) - res.setHeader('Access-Control-Allow-Origin', origin || '*'); - res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin'); - res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Authorization'); - res.setHeader('Access-Control-Max-Age', '86400'); - - // Log the request for debugging - logger.info(`Request from ${req.ip} - Origin: ${origin || 'null'} - Host: ${host}`); - - // Handle preflight requests - if (req.method === 'OPTIONS') { - res.status(204).end(); - return; - } - - next(); - }); - - // Parse JSON for all routes - server.use(express.json({ limit: "15mb" })); - - // Direct routes for Telegram API to bypass JWT middleware - server.post("/api/telegram/message", protectTelegramApi, processTelegramMessage); - server.post("/api/telegram/create", protectTelegramApi, createTelegramChat); - server.get("/api/telegram/test-auth", protectTelegramApi, (req, res) => { - res.status(200).json({ - success: true, - message: "Authentication successful", - headers: { - authHeader: req.headers.authorization ? req.headers.authorization.substring(0, 10) + "..." : "undefined", - xApiKey: req.headers['x-api-key'] ? req.headers['x-api-key'].substring(0, 10) + "..." : "undefined" - } - }); - }); - - // Register API routes - server.use("/api/products", productRoutes); - server.use("/api/chats", chatRoutes); - server.use("/api/auth", authRoutes); - server.use("/api/staff/auth", staffAuthRoutes); - server.use("/api/invite", inviteRoutes); - server.use("/api/orders", orderRoutes); - server.use("/api/categories", categoryRoutes); - server.use("/api/shipping-options", shippingRoutes); - server.use("/api/storefront", storeRoutes); - server.use("/api/crypto", cryptoRoutes); - server.use("/api/blocked-users", blockedUsersRoutes); - server.use("/api/stock", stockRoutes); - server.use("/api/promotions", promotionRoutes); - - // Start crypto price updater - startCryptoPriceUpdater(60); - - // For all other routes, use Next.js - server.all('*', (req, res) => { - return handle(req, res); - }); - - server.listen(port, () => { - console.log(`> Ready on http://${hostname}:${port}`); - }); -}); \ No newline at end of file diff --git a/traefik.yml b/traefik.yml deleted file mode 100644 index 6245722..0000000 --- a/traefik.yml +++ /dev/null @@ -1,8 +0,0 @@ -http: - middlewares: - ip-whitelist: - ipWhiteList: - sourceRange: - - "212.113.116.6" - - "194.26.229.41" - - "138.124.13.13"