Spaces:
Running
Running
import type { Express } from "express"; | |
import { createServer, type Server } from "http"; | |
import { storage } from "./storage"; | |
import { authenticateToken, requireRole, optionalAuth, generateToken, type AuthRequest } from "./middleware/auth"; | |
import { upload } from "./middleware/multer"; | |
import bcrypt from "bcrypt"; | |
import express from "express"; | |
import path from "path"; | |
import { | |
insertUserSchema, insertSellerSchema, insertCategorySchema, | |
insertStoreSchema, insertProductSchema, insertOrderSchema, insertCartItemSchema, | |
type Order | |
} from "@shared/schema"; | |
import { z } from "zod"; | |
import { addPlaceholderImages } from "./add-placeholder-images"; | |
export async function registerRoutes(app: Express): Promise<Server> { | |
// Serve uploaded files | |
app.use('/uploads', express.static(path.join(process.cwd(), 'uploads'))); | |
// Serve attached assets | |
app.use('/attached_assets', express.static(path.join(process.cwd(), 'attached_assets'))); | |
// Auth Routes | |
app.post("/api/auth/register", async (req, res) => { | |
try { | |
const userData = insertUserSchema.parse(req.body); | |
// Check if user already exists | |
const existingUser = await storage.getUserByEmail(userData.email) || | |
await storage.getUserByUsername(userData.username); | |
if (existingUser) { | |
return res.status(400).json({ message: "User already exists" }); | |
} | |
// Hash password | |
const hashedPassword = await bcrypt.hash(userData.password, 10); | |
// Convert latitude/longitude numbers to strings for database insertion | |
const processedUserData = { | |
...userData, | |
password: hashedPassword, | |
// Convert numbers to strings for decimal database fields | |
latitude: userData.latitude ? userData.latitude.toString() : undefined, | |
longitude: userData.longitude ? userData.longitude.toString() : undefined, | |
}; | |
const user = await storage.createUser(processedUserData); | |
const token = generateToken({ | |
id: user.id, | |
type: 'user', | |
username: user.username | |
}); | |
res.json({ | |
token, | |
user: { | |
id: user.id, | |
username: user.username, | |
email: user.email, | |
firstName: user.firstName, | |
lastName: user.lastName | |
} | |
}); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
// Registration error occurred | |
res.status(500).json({ message: "Registration failed" }); | |
} | |
}); | |
app.post("/api/auth/login", async (req, res) => { | |
try { | |
const { email, password } = req.body; | |
const user = await storage.getUserByEmail(email); | |
if (!user || !await bcrypt.compare(password, user.password)) { | |
return res.status(401).json({ message: "Invalid credentials" }); | |
} | |
const token = generateToken({ | |
id: user.id, | |
type: 'user', | |
username: user.username | |
}); | |
res.json({ | |
token, | |
user: { | |
id: user.id, | |
username: user.username, | |
email: user.email, | |
firstName: user.firstName, | |
lastName: user.lastName | |
} | |
}); | |
} catch (error) { | |
res.status(500).json({ message: "Login failed" }); | |
} | |
}); | |
app.post("/api/auth/seller-login", async (req, res) => { | |
try { | |
const { username, password } = req.body; | |
const seller = await storage.getSellerByUsername(username); | |
if (!seller || !await bcrypt.compare(password, seller.password)) { | |
return res.status(401).json({ message: "Invalid credentials" }); | |
} | |
const token = generateToken({ | |
id: seller.id, | |
type: 'seller', | |
username: seller.username | |
}); | |
res.json({ | |
token, | |
seller: { | |
id: seller.id, | |
username: seller.username | |
} | |
}); | |
} catch (error) { | |
res.status(500).json({ message: "Login failed" }); | |
} | |
}); | |
// Token verification endpoint | |
app.get("/api/auth/verify", authenticateToken, async (req: AuthRequest, res) => { | |
try { | |
const user = req.user; | |
if (!user) { | |
return res.status(401).json({ message: "Invalid token" }); | |
} | |
// Return user data based on type | |
if (user.type === 'user') { | |
const fullUser = await storage.getUser(user.id); | |
if (!fullUser) { | |
return res.status(404).json({ message: "User not found" }); | |
} | |
res.json({ | |
user: { | |
id: fullUser.id, | |
username: fullUser.username, | |
email: fullUser.email, | |
firstName: fullUser.firstName, | |
lastName: fullUser.lastName | |
}, | |
userType: 'user' | |
}); | |
} else if (user.type === 'seller') { | |
const seller = await storage.getSeller(user.id); | |
if (!seller) { | |
return res.status(404).json({ message: "Seller not found" }); | |
} | |
res.json({ | |
seller: { | |
id: seller.id, | |
username: seller.username | |
}, | |
userType: 'seller' | |
}); | |
} else { | |
res.json({ userType: 'admin' }); | |
} | |
} catch (error) { | |
// Token verification failed | |
res.status(500).json({ message: "Token verification failed" }); | |
} | |
}); | |
// User Routes | |
app.get("/api/user/profile", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const user = await storage.getUser(req.user!.id); | |
if (!user) { | |
return res.status(404).json({ message: "User not found" }); | |
} | |
const { password, ...userWithoutPassword } = user; | |
res.json(userWithoutPassword); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch profile" }); | |
} | |
}); | |
app.put("/api/user/profile", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const updateData = req.body; | |
// Remove password from update data if present | |
if (updateData.password) { | |
delete updateData.password; | |
} | |
// Remove id from update data if present | |
if (updateData.id) { | |
delete updateData.id; | |
} | |
const updatedUser = await storage.updateUser(req.user!.id, updateData); | |
const { password, ...userWithoutPassword } = updatedUser; | |
res.json(userWithoutPassword); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update profile" }); | |
} | |
}); | |
// Categories Routes | |
app.get("/api/categories", async (req, res) => { | |
try { | |
const categories = await storage.getAllCategories(); | |
res.json(categories); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch categories" }); | |
} | |
}); | |
app.post("/api/categories", async (req, res) => { | |
try { | |
const categoryData = insertCategorySchema.parse(req.body); | |
const category = await storage.createCategory(categoryData); | |
res.json(category); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to create category" }); | |
} | |
}); | |
app.put("/api/categories/:id", async (req, res) => { | |
try { | |
const categoryData = insertCategorySchema.partial().parse(req.body); | |
const category = await storage.updateCategory(req.params.id, categoryData); | |
res.json(category); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to update category" }); | |
} | |
}); | |
app.delete("/api/categories/:id", async (req, res) => { | |
try { | |
await storage.deleteCategory(req.params.id); | |
res.json({ message: "Category deleted successfully" }); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to delete category" }); | |
} | |
}); | |
// Products Routes | |
app.get("/api/products", optionalAuth, async (req: AuthRequest, res) => { | |
try { | |
const { category, search, seller } = req.query; | |
let products; | |
if (search) { | |
products = await storage.searchProducts(search as string); | |
} else if (category) { | |
products = await storage.getProductsByCategory(category as string); | |
} else if (seller) { | |
products = await storage.getProductsBySeller(seller as string); | |
} else { | |
products = await storage.getAllProducts(); | |
} | |
// Add seller and category information | |
const enrichedProducts = await Promise.all(products.map(async (product) => { | |
const seller = await storage.getSeller(product.sellerId); | |
const store = seller ? await storage.getStoreBysellerId(seller.id) : null; | |
const category = await storage.getCategory(product.categoryId); | |
return { | |
...product, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
store: store ? { name: store.name, faceImage: store.faceImage } : null, | |
category: category ? { id: category.id, name: category.name } : null, | |
}; | |
})); | |
res.json(enrichedProducts); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch products" }); | |
} | |
}); | |
app.get("/api/products/featured", async (req, res) => { | |
try { | |
let products = await storage.getFeaturedProductsByCategories(); | |
// Check if we need to create more products for categories with less than 5 | |
const categories = await storage.getAllCategories(); | |
const sellers = await storage.getAllSellers(); | |
if (sellers.length === 0) { | |
return res.json([]); | |
} | |
for (const category of categories) { | |
const categoryProducts = products.filter(p => p.categoryId === category.id); | |
const needed = 5 - categoryProducts.length; | |
if (needed > 0) { | |
// Create missing products with random images | |
for (let i = 0; i < needed; i++) { | |
const randomSeller = sellers[Math.floor(Math.random() * sellers.length)]; | |
const productNumber = categoryProducts.length + i + 1; | |
const newProduct = await storage.createProduct({ | |
title: `${category.name} Product ${productNumber}`, | |
description: `High-quality ${category.name.toLowerCase()} product featuring premium materials and exceptional craftsmanship. Perfect for everyday use with modern design aesthetics.`, | |
price: (Math.floor(Math.random() * 500) + 100).toString(), // Random price between 100-600 | |
stock: Math.floor(Math.random() * 50) + 10, // Random stock between 10-60 | |
images: [ | |
`https://picsum.photos/400/400?random=${Date.now()}&${i}`, | |
`https://picsum.photos/400/400?random=${Date.now()}&${i}&sig=2`, | |
`https://picsum.photos/400/400?random=${Date.now()}&${i}&sig=3` | |
], | |
categoryId: category.id, | |
sellerId: randomSeller.id, | |
isActive: true | |
}); | |
products.push(newProduct); | |
} | |
} | |
} | |
// Add seller and category information | |
const enrichedProducts = await Promise.all(products.map(async (product) => { | |
const seller = await storage.getSeller(product.sellerId); | |
const store = seller ? await storage.getStoreBysellerId(seller.id) : null; | |
const category = await storage.getCategory(product.categoryId); | |
return { | |
...product, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
store: store ? { name: store.name, faceImage: store.faceImage } : null, | |
category: category ? { id: category.id, name: category.name } : null, | |
}; | |
})); | |
res.json(enrichedProducts); | |
} catch (error) { | |
// Featured products fetch failed | |
res.status(500).json({ message: "Failed to fetch featured products" }); | |
} | |
}); | |
app.get("/api/products/:id", async (req, res) => { | |
try { | |
const product = await storage.getProduct(req.params.id); | |
if (!product) { | |
return res.status(404).json({ message: "Product not found" }); | |
} | |
const seller = await storage.getSeller(product.sellerId); | |
const store = seller ? await storage.getStoreBysellerId(seller.id) : null; | |
const category = await storage.getCategory(product.categoryId); | |
const enrichedProduct = { | |
...product, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
store: store ? { | |
id: store.id, | |
name: store.name, | |
description: store.description, | |
faceImage: store.faceImage, | |
bannerImage: store.bannerImage | |
} : null, | |
category: category ? { id: category.id, name: category.name } : null, | |
}; | |
res.json(enrichedProduct); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch product" }); | |
} | |
}); | |
app.post("/api/products", authenticateToken, requireRole(['seller']), upload.array('images', 5), async (req: AuthRequest, res) => { | |
try { | |
// Check if seller has a store first | |
const store = await storage.getStoreBysellerId(req.user!.id); | |
if (!store) { | |
return res.status(400).json({ message: "You must create a store before adding products" }); | |
} | |
// Convert form data strings to appropriate types | |
const formData = { ...req.body }; | |
// Keep price and originalPrice as strings (decimal fields) | |
// Only convert stock to integer | |
if (formData.stock) formData.stock = parseInt(formData.stock, 10); | |
const productData = insertProductSchema.parse({ | |
...formData, | |
sellerId: req.user!.id, | |
images: req.files ? (req.files as Express.Multer.File[]).map(file => `/uploads/${file.filename}`) : [], | |
}); | |
const product = await storage.createProduct(productData); | |
res.json(product); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to create product" }); | |
} | |
}); | |
app.put("/api/products/:id", authenticateToken, requireRole(['seller']), upload.array('images', 5), async (req: AuthRequest, res) => { | |
try { | |
const product = await storage.getProduct(req.params.id); | |
if (!product) { | |
return res.status(404).json({ message: "Product not found" }); | |
} | |
if (product.sellerId !== req.user!.id) { | |
return res.status(403).json({ message: "You can only update your own products" }); | |
} | |
const updateData = { ...req.body }; | |
// Convert form data strings to appropriate types | |
// Keep price and originalPrice as strings (decimal fields) | |
// Only convert stock to integer | |
if (updateData.stock) updateData.stock = parseInt(updateData.stock, 10); | |
if (req.files && (req.files as Express.Multer.File[]).length > 0) { | |
updateData.images = (req.files as Express.Multer.File[]).map(file => `/uploads/${file.filename}`); | |
} | |
const updatedProduct = await storage.updateProduct(req.params.id, updateData); | |
res.json(updatedProduct); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update product" }); | |
} | |
}); | |
app.delete("/api/products/:id", authenticateToken, requireRole(['seller']), async (req: AuthRequest, res) => { | |
try { | |
const product = await storage.getProduct(req.params.id); | |
if (!product) { | |
return res.status(404).json({ message: "Product not found" }); | |
} | |
if (product.sellerId !== req.user!.id) { | |
return res.status(403).json({ message: "You can only delete your own products" }); | |
} | |
await storage.deleteProduct(req.params.id); | |
res.json({ message: "Product deleted successfully" }); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to delete product" }); | |
} | |
}); | |
// Seller Orders Route | |
app.get("/api/seller/orders", authenticateToken, requireRole(['seller']), async (req: AuthRequest, res) => { | |
try { | |
// Get all orders that contain products from this seller | |
const sellerProducts = await storage.getProductsBySeller(req.user!.id); | |
const sellerProductIds = sellerProducts.map(p => p.id); | |
if (sellerProductIds.length === 0) { | |
return res.json([]); | |
} | |
const orders = await storage.getOrdersForSeller(sellerProductIds); | |
// Enrich orders with items (only seller's items) and user info | |
const enrichedOrders = await Promise.all(orders.map(async (order: Order) => { | |
const allItems = await storage.getOrderItems(order.id); | |
const sellerItems = allItems.filter(item => sellerProductIds.includes(item.productId)); | |
const enrichedItems = await Promise.all(sellerItems.map(async (item) => { | |
const product = await storage.getProduct(item.productId); | |
return { ...item, product }; | |
})); | |
// Get user details for this order | |
const user = await storage.getUser(order.userId); | |
return { ...order, items: enrichedItems, user: user ? { | |
firstName: user.firstName, | |
lastName: user.lastName, | |
email: user.email, | |
phone: user.phone | |
} : null }; | |
})); | |
res.json(enrichedOrders); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch seller orders" }); | |
} | |
}); | |
// Update order status by seller | |
app.patch("/api/seller/orders/:id", authenticateToken, requireRole(['seller']), async (req: AuthRequest, res) => { | |
try { | |
const orderId = req.params.id; | |
const { status, paymentStatus } = req.body; | |
// Verify the order contains the seller's products | |
const sellerProducts = await storage.getProductsBySeller(req.user!.id); | |
const sellerProductIds = sellerProducts.map(p => p.id); | |
const orderItems = await storage.getOrderItems(orderId); | |
const hasSellerItems = orderItems.some(item => sellerProductIds.includes(item.productId)); | |
if (!hasSellerItems) { | |
return res.status(403).json({ message: "You can only update orders containing your products" }); | |
} | |
const updates: { status?: string; paymentStatus?: string } = {}; | |
if (status) updates.status = status; | |
if (paymentStatus) updates.paymentStatus = paymentStatus; | |
const updatedOrder = await storage.updateOrderStatusAndPayment(orderId, updates); | |
res.json(updatedOrder); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update order status" }); | |
} | |
}); | |
// Stores Routes | |
app.get("/api/stores", async (req, res) => { | |
try { | |
const stores = await storage.getAllStores(); | |
// Enrich stores with seller information | |
const enrichedStores = await Promise.all(stores.map(async (store) => { | |
const seller = await storage.getSeller(store.sellerId); | |
return { | |
...store, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
}; | |
})); | |
res.json(enrichedStores); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch stores" }); | |
} | |
}); | |
app.get("/api/stores/:id", async (req, res) => { | |
try { | |
const store = await storage.getStore(req.params.id); | |
if (!store) { | |
return res.status(404).json({ message: "Store not found" }); | |
} | |
const seller = await storage.getSeller(store.sellerId); | |
const products = await storage.getProductsBySeller(store.sellerId); | |
const enrichedProducts = await Promise.all(products.map(async (product) => { | |
const category = await storage.getCategory(product.categoryId); | |
return { | |
...product, | |
category: category ? { id: category.id, name: category.name } : null, | |
}; | |
})); | |
res.json({ | |
...store, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
products: enrichedProducts | |
}); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch store" }); | |
} | |
}); | |
app.post("/api/stores", authenticateToken, requireRole(['seller']), upload.fields([ | |
{ name: 'bannerImage', maxCount: 1 }, | |
{ name: 'faceImage', maxCount: 1 } | |
]), async (req: AuthRequest, res) => { | |
try { | |
// Check if seller already has a store | |
const existingStore = await storage.getStoreBysellerId(req.user!.id); | |
if (existingStore) { | |
return res.status(400).json({ message: "You can only create one store per seller account" }); | |
} | |
const files = req.files as { [fieldname: string]: Express.Multer.File[] } || {}; | |
const storeData = insertStoreSchema.parse({ | |
...req.body, | |
sellerId: req.user!.id, | |
bannerImage: files.bannerImage ? `/uploads/${files.bannerImage[0].filename}` : null, | |
faceImage: files.faceImage ? `/uploads/${files.faceImage[0].filename}` : null, | |
}); | |
const store = await storage.createStore(storeData); | |
res.json(store); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to create store" }); | |
} | |
}); | |
app.put("/api/stores/:id", authenticateToken, requireRole(['seller']), upload.fields([ | |
{ name: 'bannerImage', maxCount: 1 }, | |
{ name: 'faceImage', maxCount: 1 } | |
]), async (req: AuthRequest, res) => { | |
try { | |
const store = await storage.getStore(req.params.id); | |
if (!store) { | |
return res.status(404).json({ message: "Store not found" }); | |
} | |
if (store.sellerId !== req.user!.id) { | |
return res.status(403).json({ message: "You can only update your own store" }); | |
} | |
const files = req.files as { [fieldname: string]: Express.Multer.File[] }; | |
const updateData = { ...req.body }; | |
if (files.bannerImage) { | |
updateData.bannerImage = `/uploads/${files.bannerImage[0].filename}`; | |
} | |
if (files.faceImage) { | |
updateData.faceImage = `/uploads/${files.faceImage[0].filename}`; | |
} | |
const updatedStore = await storage.updateStore(req.params.id, updateData); | |
res.json(updatedStore); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update store" }); | |
} | |
}); | |
// Cart Routes | |
app.get("/api/cart", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const cartItems = await storage.getCartItems(req.user!.id); | |
// Enrich cart items with product details | |
const enrichedItems = await Promise.all(cartItems.map(async (item) => { | |
const product = await storage.getProduct(item.productId); | |
const seller = product ? await storage.getSeller(product.sellerId) : null; | |
const store = seller ? await storage.getStoreBysellerId(seller.id) : null; | |
return { | |
...item, | |
product: product ? { | |
...product, | |
seller: seller ? { id: seller.id, username: seller.username } : null, | |
store: store ? { name: store.name } : null, | |
} : null, | |
}; | |
})); | |
res.json(enrichedItems.filter(item => item.product !== null)); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch cart" }); | |
} | |
}); | |
app.post("/api/cart", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const cartItemData = insertCartItemSchema.parse({ | |
...req.body, | |
userId: req.user!.id, | |
}); | |
const cartItem = await storage.addToCart(cartItemData); | |
res.json(cartItem); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to add to cart" }); | |
} | |
}); | |
app.put("/api/cart/:id", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const { quantity } = req.body; | |
const cartItem = await storage.updateCartItem(req.params.id, quantity); | |
res.json(cartItem); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update cart item" }); | |
} | |
}); | |
app.delete("/api/cart/:id", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
await storage.removeFromCart(req.params.id); | |
res.json({ message: "Item removed from cart" }); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to remove from cart" }); | |
} | |
}); | |
// Orders Routes | |
app.get("/api/orders", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const orders = await storage.getOrdersByUser(req.user!.id); | |
// Enrich orders with items | |
const enrichedOrders = await Promise.all(orders.map(async (order) => { | |
const items = await storage.getOrderItems(order.id); | |
const enrichedItems = await Promise.all(items.map(async (item) => { | |
const product = await storage.getProduct(item.productId); | |
return { ...item, product }; | |
})); | |
return { ...order, items: enrichedItems }; | |
})); | |
res.json(enrichedOrders); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch orders" }); | |
} | |
}); | |
app.post("/api/orders", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const orderData = insertOrderSchema.parse({ | |
...req.body, | |
userId: req.user!.id, | |
}); | |
const order = await storage.createOrder(orderData); | |
// Create order items from cart | |
const cartItems = await storage.getCartItems(req.user!.id); | |
await Promise.all(cartItems.map(async (cartItem) => { | |
await storage.createOrderItem({ | |
orderId: order.id, | |
productId: cartItem.productId, | |
quantity: cartItem.quantity, | |
price: req.body.items.find((item: any) => item.productId === cartItem.productId)?.price || "0", | |
}); | |
})); | |
// Clear cart after order | |
await storage.clearCart(req.user!.id); | |
res.json(order); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to create order" }); | |
} | |
}); | |
// Cancel order endpoint | |
app.patch("/api/orders/:id/cancel", authenticateToken, requireRole(['user']), async (req: AuthRequest, res) => { | |
try { | |
const orderId = req.params.id; | |
// Get the order to verify ownership and check if it can be cancelled | |
const order = await storage.getOrder(orderId); | |
if (!order) { | |
return res.status(404).json({ message: "Order not found" }); | |
} | |
// Verify the order belongs to the authenticated user | |
if (order.userId !== req.user!.id) { | |
return res.status(403).json({ message: "You can only cancel your own orders" }); | |
} | |
// Check if the order can be cancelled (only pending orders can be cancelled) | |
if (order.status !== 'pending') { | |
return res.status(400).json({ message: "Order cannot be cancelled. Only pending orders can be cancelled." }); | |
} | |
// Update order status to cancelled | |
const updatedOrder = await storage.updateOrderStatus(orderId, 'cancelled'); | |
res.json(updatedOrder); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to cancel order" }); | |
} | |
}); | |
// Admin Dev Routes (no auth required as per requirements) | |
app.get("/api/admin/sellers", async (req, res) => { | |
try { | |
const sellers = await storage.getAllSellers(); | |
res.json(sellers); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch sellers" }); | |
} | |
}); | |
app.post("/api/admin/sellers", async (req, res) => { | |
try { | |
const sellerData = insertSellerSchema.parse(req.body); | |
// Check if seller already exists | |
const existingSeller = await storage.getSellerByUsername(sellerData.username); | |
if (existingSeller) { | |
return res.status(400).json({ message: "Seller already exists" }); | |
} | |
// Hash password | |
const hashedPassword = await bcrypt.hash(sellerData.password, 10); | |
const seller = await storage.createSeller({ | |
...sellerData, | |
password: hashedPassword, | |
plainTextPassword: sellerData.password, // Store for admin access recovery | |
}); | |
res.json(seller); | |
} catch (error) { | |
if (error instanceof z.ZodError) { | |
return res.status(400).json({ message: "Invalid input data", errors: error.errors }); | |
} | |
res.status(500).json({ message: "Failed to create seller" }); | |
} | |
}); | |
app.get("/api/admin/users", async (req, res) => { | |
try { | |
const users = await storage.getAllUsers(); | |
res.json(users); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch users" }); | |
} | |
}); | |
app.delete("/api/admin/sellers/:id", async (req, res) => { | |
try { | |
// First delete all products by this seller | |
const products = await storage.getProductsBySeller(req.params.id); | |
for (const product of products) { | |
await storage.deleteProduct(product.id); | |
} | |
// Delete all stores by this seller | |
const stores = await storage.getStoresBySeller(req.params.id); | |
for (const store of stores) { | |
await storage.deleteStore(store.id); | |
} | |
// Finally delete the seller | |
await storage.deleteSeller(req.params.id); | |
res.json({ message: "Seller and all associated data deleted successfully" }); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to delete seller" }); | |
} | |
}); | |
// Admin product management | |
app.delete("/api/admin/products/:id", async (req, res) => { | |
try { | |
await storage.deleteProduct(req.params.id); | |
res.json({ message: "Product deleted successfully" }); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to delete product" }); | |
} | |
}); | |
app.put("/api/admin/products/:id", async (req, res) => { | |
try { | |
const updatedProduct = await storage.updateProduct(req.params.id, req.body); | |
res.json(updatedProduct); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update product" }); | |
} | |
}); | |
// Admin Orders Route | |
app.get("/api/admin/orders", async (req, res) => { | |
try { | |
// Get all orders for admin | |
const orders = await storage.getAllOrders(); | |
// Enrich orders with items and user info | |
const enrichedOrders = await Promise.all(orders.map(async (order: Order) => { | |
const items = await storage.getOrderItems(order.id); | |
const enrichedItems = await Promise.all(items.map(async (item) => { | |
const product = await storage.getProduct(item.productId); | |
return { ...item, product }; | |
})); | |
// Get user details for this order | |
const user = await storage.getUser(order.userId); | |
return { ...order, items: enrichedItems, user: user ? { | |
firstName: user.firstName, | |
lastName: user.lastName, | |
email: user.email, | |
phone: user.phone | |
} : null }; | |
})); | |
res.json(enrichedOrders); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch admin orders" }); | |
} | |
}); | |
// Update order status by admin | |
app.patch("/api/admin/orders/:id", async (req, res) => { | |
try { | |
const orderId = req.params.id; | |
const { status, paymentStatus } = req.body; | |
const updates: { status?: string; paymentStatus?: string } = {}; | |
if (status) updates.status = status; | |
if (paymentStatus) updates.paymentStatus = paymentStatus; | |
const updatedOrder = await storage.updateOrderStatusAndPayment(orderId, updates); | |
res.json(updatedOrder); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to update order status" }); | |
} | |
}); | |
// Seller Dashboard Routes | |
app.get("/api/seller/store", authenticateToken, requireRole(['seller']), async (req: AuthRequest, res) => { | |
try { | |
const store = await storage.getStoreBysellerId(req.user!.id); | |
res.json(store); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch store" }); | |
} | |
}); | |
app.get("/api/seller/products", authenticateToken, requireRole(['seller']), async (req: AuthRequest, res) => { | |
try { | |
const products = await storage.getProductsBySeller(req.user!.id); | |
// Add category information | |
const enrichedProducts = await Promise.all(products.map(async (product) => { | |
const category = await storage.getCategory(product.categoryId); | |
return { | |
...product, | |
category: category ? { id: category.id, name: category.name } : null, | |
}; | |
})); | |
res.json(enrichedProducts); | |
} catch (error) { | |
res.status(500).json({ message: "Failed to fetch products" }); | |
} | |
}); | |
const httpServer = createServer(app); | |
return httpServer; | |
} | |