From 5f5ea30c787f7f503dca2046ec2b076abec2ac7f Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Wed, 13 Aug 2025 12:39:57 +0000 Subject: [PATCH] Add ability to view and manage DLC products with role-based access Implement API endpoints for fetching DLC products, their statistics, and individual details, with filtering and permission checks based on user roles and group IDs. Update storage methods to support group-based filtering for customer orders, DLC products, and tasks. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/7c468936-2a88-4686-ac0a-84921a45222d/KsafRhL --- ...-42D0-968D-5A320AC04471}_1755088564042.png | Bin 0 -> 42168 bytes client/src/pages/DlcPage.tsx | 2 +- cookies.txt | 2 +- server/routes.ts | 482 ++++++++++++++++++ server/storage.ts | 48 +- 5 files changed, 524 insertions(+), 10 deletions(-) create mode 100644 attached_assets/{FB17DA62-CEC3-42D0-968D-5A320AC04471}_1755088564042.png diff --git a/attached_assets/{FB17DA62-CEC3-42D0-968D-5A320AC04471}_1755088564042.png b/attached_assets/{FB17DA62-CEC3-42D0-968D-5A320AC04471}_1755088564042.png new file mode 100644 index 0000000000000000000000000000000000000000..e84b22daafce1342ff2c4f1482aba9bdd71cfc08 GIT binary patch literal 42168 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 { + const filteredProducts = (dlcProducts || []).filter(product => { if (searchTerm) { return product.productName.toLowerCase().includes(searchTerm.toLowerCase()) || (product.supplier?.name && product.supplier.name.toLowerCase().includes(searchTerm.toLowerCase())); diff --git a/cookies.txt b/cookies.txt index fec352d..f63d116 100644 --- a/cookies.txt +++ b/cookies.txt @@ -2,4 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -#HttpOnly_localhost FALSE / FALSE 1755174893 connect.sid s%3AnBdh-Dk3_lel7Iwxh8gfP8z9NpAO6ksl.nU8o8o7MiYpWRYfCNtxnZrgrQwhlUHQqUbUPQXD7uMI +#HttpOnly_localhost FALSE / FALSE 1755175168 connect.sid s%3AXOwkelwwA5ZeDs-gJuKcdEXK9b4Bk627.LH21I0KeUC6s6IPDiFTXL31UaqY8BVhwFQNagdkpoKg diff --git a/server/routes.ts b/server/routes.ts index cadd1cc..35d04a0 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -958,6 +958,488 @@ export async function registerRoutes(app: Express): Promise { } }); + // DLC Products routes + app.get('/api/dlc-products', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const { storeId, status, supplierId } = req.query; + let groupIds: number[] | undefined; + + if (user.role === 'admin') { + groupIds = storeId ? [parseInt(storeId as string)] : undefined; + } else { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (storeId && userGroupIds.includes(parseInt(storeId as string))) { + groupIds = [parseInt(storeId as string)]; + } else { + groupIds = userGroupIds; + } + } + + const filters: any = {}; + if (status && status !== 'all') filters.status = status as string; + if (supplierId && supplierId !== 'all') filters.supplierId = parseInt(supplierId as string); + + console.log('DLC Products API called with:', { groupIds, filters, userRole: user.role }); + const dlcProducts = await storage.getDlcProducts(groupIds, filters); + console.log('DLC Products returned:', dlcProducts.length, 'items'); + res.json(dlcProducts); + } catch (error) { + console.error("Error fetching DLC products:", error); + res.status(500).json([]); + } + }); + + app.get('/api/dlc-products/stats', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const { storeId } = req.query; + let groupIds: number[] | undefined; + + if (user.role === 'admin') { + groupIds = storeId ? [parseInt(storeId as string)] : undefined; + } else { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (storeId && userGroupIds.includes(parseInt(storeId as string))) { + groupIds = [parseInt(storeId as string)]; + } else { + groupIds = userGroupIds; + } + } + + const stats = await storage.getDlcStats(groupIds); + res.json(stats); + } catch (error) { + console.error("Error fetching DLC stats:", error); + res.status(500).json({ active: 0, expiringSoon: 0, expired: 0 }); + } + }); + + app.get('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const dlcProduct = await storage.getDlcProduct(id); + + if (!dlcProduct) { + return res.status(404).json({ message: "DLC Product not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(dlcProduct.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + res.json(dlcProduct); + } catch (error) { + console.error("Error fetching DLC product:", error); + res.status(500).json({ message: "Failed to fetch DLC product" }); + } + }); + + app.post('/api/dlc-products', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const data = { + ...req.body, + createdBy: user.id, + }; + + // Check if user has access to the group + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(data.groupId)) { + return res.status(403).json({ message: "Access denied to this group" }); + } + } + + const dlcProduct = await storage.createDlcProduct(data); + res.json(dlcProduct); + } catch (error) { + console.error("Error creating DLC product:", error); + res.status(500).json({ message: "Failed to create DLC product" }); + } + }); + + app.put('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const dlcProduct = await storage.getDlcProduct(id); + + if (!dlcProduct) { + return res.status(404).json({ message: "DLC Product not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(dlcProduct.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + const updatedProduct = await storage.updateDlcProduct(id, req.body); + res.json(updatedProduct); + } catch (error) { + console.error("Error updating DLC product:", error); + res.status(500).json({ message: "Failed to update DLC product" }); + } + }); + + app.delete('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const dlcProduct = await storage.getDlcProduct(id); + + if (!dlcProduct) { + return res.status(404).json({ message: "DLC Product not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(dlcProduct.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + await storage.deleteDlcProduct(id); + res.json({ message: "DLC Product deleted successfully" }); + } catch (error) { + console.error("Error deleting DLC product:", error); + res.status(500).json({ message: "Failed to delete DLC product" }); + } + }); + + app.post('/api/dlc-products/:id/validate', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const dlcProduct = await storage.getDlcProduct(id); + + if (!dlcProduct) { + return res.status(404).json({ message: "DLC Product not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(dlcProduct.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + const validatedProduct = await storage.validateDlcProduct(id, user.id); + res.json(validatedProduct); + } catch (error) { + console.error("Error validating DLC product:", error); + res.status(500).json({ message: "Failed to validate DLC product" }); + } + }); + + // Tasks routes + app.get('/api/tasks', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const { storeId } = req.query; + let groupIds: number[] | undefined; + + if (user.role === 'admin') { + groupIds = storeId ? [parseInt(storeId as string)] : undefined; + } else { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (storeId && userGroupIds.includes(parseInt(storeId as string))) { + groupIds = [parseInt(storeId as string)]; + } else { + groupIds = userGroupIds; + } + } + + console.log('Tasks API called with:', { groupIds, userRole: user.role }); + const tasks = await storage.getTasks(groupIds); + console.log('Tasks returned:', tasks.length, 'items'); + res.json(tasks); + } catch (error) { + console.error("Error fetching tasks:", error); + res.status(500).json([]); + } + }); + + app.post('/api/tasks', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const data = { + ...req.body, + createdBy: user.id, + }; + + // Check if user has access to the group + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(data.groupId)) { + return res.status(403).json({ message: "Access denied to this group" }); + } + } + + const task = await storage.createTask(data); + res.json(task); + } catch (error) { + console.error("Error creating task:", error); + res.status(500).json({ message: "Failed to create task" }); + } + }); + + app.put('/api/tasks/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const task = await storage.getTask(id); + + if (!task) { + return res.status(404).json({ message: "Task not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(task.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + const updatedTask = await storage.updateTask(id, req.body); + res.json(updatedTask); + } catch (error) { + console.error("Error updating task:", error); + res.status(500).json({ message: "Failed to update task" }); + } + }); + + app.delete('/api/tasks/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const task = await storage.getTask(id); + + if (!task) { + return res.status(404).json({ message: "Task not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(task.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + await storage.deleteTask(id); + res.json({ message: "Task deleted successfully" }); + } catch (error) { + console.error("Error deleting task:", error); + res.status(500).json({ message: "Failed to delete task" }); + } + }); + + app.post('/api/tasks/:id/complete', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const task = await storage.getTask(id); + + if (!task) { + return res.status(404).json({ message: "Task not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(task.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + await storage.completeTask(id, user.id); + res.json({ message: "Task completed successfully" }); + } catch (error) { + console.error("Error completing task:", error); + res.status(500).json({ message: "Failed to complete task" }); + } + }); + + // Customer Orders routes + app.get('/api/customer-orders', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const { storeId } = req.query; + let groupIds: number[] | undefined; + + if (user.role === 'admin') { + groupIds = storeId ? [parseInt(storeId as string)] : undefined; + } else { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (storeId && userGroupIds.includes(parseInt(storeId as string))) { + groupIds = [parseInt(storeId as string)]; + } else { + groupIds = userGroupIds; + } + } + + console.log('Customer Orders API called with:', { groupIds, userRole: user.role }); + const customerOrders = await storage.getCustomerOrders(groupIds); + console.log('Customer Orders returned:', customerOrders.length, 'items'); + res.json(customerOrders); + } catch (error) { + console.error("Error fetching customer orders:", error); + res.status(500).json([]); + } + }); + + app.post('/api/customer-orders', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const data = { + ...req.body, + createdBy: user.id, + }; + + // Check if user has access to the group + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(data.groupId)) { + return res.status(403).json({ message: "Access denied to this group" }); + } + } + + const customerOrder = await storage.createCustomerOrder(data); + res.json(customerOrder); + } catch (error) { + console.error("Error creating customer order:", error); + res.status(500).json({ message: "Failed to create customer order" }); + } + }); + + app.put('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const customerOrder = await storage.getCustomerOrder(id); + + if (!customerOrder) { + return res.status(404).json({ message: "Customer order not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(customerOrder.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + const updatedOrder = await storage.updateCustomerOrder(id, req.body); + res.json(updatedOrder); + } catch (error) { + console.error("Error updating customer order:", error); + res.status(500).json({ message: "Failed to update customer order" }); + } + }); + + app.delete('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => { + try { + const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const id = parseInt(req.params.id); + const customerOrder = await storage.getCustomerOrder(id); + + if (!customerOrder) { + return res.status(404).json({ message: "Customer order not found" }); + } + + // Check permissions + if (user.role !== 'admin') { + const userGroupIds = user.userGroups.map(ug => ug.groupId); + if (!userGroupIds.includes(customerOrder.groupId)) { + return res.status(403).json({ message: "Access denied" }); + } + } + + await storage.deleteCustomerOrder(id); + res.json({ message: "Customer order deleted successfully" }); + } catch (error) { + console.error("Error deleting customer order:", error); + res.status(500).json({ message: "Failed to delete customer order" }); + } + }); + // Statistics routes app.get('/api/stats/monthly', isAuthenticated, async (req: any, res) => { try { diff --git a/server/storage.ts b/server/storage.ts index 0233bfc..5b1ec20 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1464,8 +1464,14 @@ export class MemStorage implements IStorage { async getPublicityParticipations(): Promise { return []; } async setPublicityParticipations(): Promise {} - async getCustomerOrders(): Promise { - return Array.from(this.customerOrders.values()).map(order => ({ + async getCustomerOrders(groupIds?: number[]): Promise { + let orders = Array.from(this.customerOrders.values()); + + if (groupIds && groupIds.length > 0) { + orders = orders.filter(order => groupIds.includes(order.groupId)); + } + + return orders.map(order => ({ ...order, supplier: this.suppliers.get(order.supplierId)!, group: this.groups.get(order.groupId)!, @@ -1515,8 +1521,23 @@ export class MemStorage implements IStorage { this.customerOrders.delete(id); } - async getDlcProducts(): Promise { - return Array.from(this.dlcProducts.values()).map(product => ({ + async getDlcProducts(groupIds?: number[], filters?: { status?: string; supplierId?: number; }): Promise { + let products = Array.from(this.dlcProducts.values()); + + if (groupIds && groupIds.length > 0) { + products = products.filter(product => groupIds.includes(product.groupId)); + } + + if (filters) { + if (filters.status) { + products = products.filter(product => product.status === filters.status); + } + if (filters.supplierId) { + products = products.filter(product => product.supplierId === filters.supplierId); + } + } + + return products.map(product => ({ ...product, supplier: this.suppliers.get(product.supplierId)!, group: this.groups.get(product.groupId)!, @@ -1580,12 +1601,17 @@ export class MemStorage implements IStorage { return { ...updatedProduct, dlcDate: new Date(updatedProduct.expiryDate) } as DlcProductFrontend; } - async getDlcStats(): Promise<{ active: number; expiringSoon: number; expired: number; }> { + async getDlcStats(groupIds?: number[]): Promise<{ active: number; expiringSoon: number; expired: number; }> { const today = new Date(); const alertDate = new Date(); alertDate.setDate(today.getDate() + 15); - const products = Array.from(this.dlcProducts.values()); + let products = Array.from(this.dlcProducts.values()); + + if (groupIds && groupIds.length > 0) { + products = products.filter(product => groupIds.includes(product.groupId)); + } + return { active: products.filter(p => new Date(p.expiryDate) > today).length, expiringSoon: products.filter(p => { @@ -1596,8 +1622,14 @@ export class MemStorage implements IStorage { }; } - async getTasks(): Promise { - return Array.from(this.tasks.values()).map(task => ({ + async getTasks(groupIds?: number[]): Promise { + let tasks = Array.from(this.tasks.values()); + + if (groupIds && groupIds.length > 0) { + tasks = tasks.filter(task => groupIds.includes(task.groupId)); + } + + return tasks.map(task => ({ ...task, group: this.groups.get(task.groupId)!, creator: this.users.get(task.createdBy)!