Files
LogiFlow/server/routes.ts
T
michaelschal df479800e3 Implement a new hardcoded permissions system for enhanced user access control
This commit replaces the previous dynamic, database-driven permissions system with a hardcoded, role-based access control (RBAC) structure. New files in `client/src/lib/permissions.ts` and `server/permissions.ts` define permissions per module and role, simplifying management and improving performance. Database tables related to roles, permissions, and user roles have been removed, and associated storage interface methods and routes have been deprecated or updated to reflect the new architecture. The `replit.md` file has also been updated to document these significant changes.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a8a78c07-e900-425c-a577-5b4c5894379d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/a8a78c07-e900-425c-a577-5b4c5894379d/7SnSmh4
2025-08-11 19:11:11 +00:00

2211 lines
79 KiB
TypeScript

import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { setupLocalAuth, requireAuth } from "./localAuth";
import { requireModulePermission, requireAdmin } from "./permissions";
console.log('🔍 Using development storage and authentication');
// Alias pour compatibilité
const isAuthenticated = requireAuth;
const setupAuth = setupLocalAuth;
import {
insertGroupSchema,
insertSupplierSchema,
insertOrderSchema,
insertDeliverySchema,
insertUserGroupSchema,
insertPublicitySchema,
insertCustomerOrderSchema,
insertDlcProductSchema,
insertDlcProductFrontendSchema,
insertTaskSchema
} from "@shared/schema";
import { z } from "zod";
export async function registerRoutes(app: Express): Promise<Server> {
// Health check endpoint for Docker
app.get('/api/health', (req, res) => {
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development',
database: 'connected' // We could add a real DB check here if needed
});
});
// Auth middleware
await setupAuth(app);
// Auth routes handled by authSwitch (local or Replit)
// Groups routes
app.get('/api/groups', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims ? req.user.claims.sub : req.user.id : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Admin sees all groups, others see only their assigned groups
if (user.role === 'admin') {
const groups = await storage.getGroups();
res.json(groups);
} else {
const userGroups = user.userGroups.map(ug => ug.group);
res.json(userGroups);
}
} catch (error) {
console.error("Error fetching groups:", error);
res.status(500).json({ message: "Failed to fetch groups" });
}
});
app.post('/api/groups', isAuthenticated, async (req: any, res) => {
try {
// Debug logging pour la création de groupe
console.log('📨 POST /api/groups - Headers:', {
'content-type': req.headers['content-type'],
'content-length': req.headers['content-length'],
'user-agent': req.headers['user-agent']?.substring(0, 50) + '...'
});
console.log('📋 POST /api/groups - Request body:', JSON.stringify(req.body, null, 2));
// Déterminer l'ID utilisateur selon l'environnement
let userId;
if (req.user.claims && req.user.claims.sub) {
userId = req.user.claims.sub; // Production Replit Auth
console.log('🔐 Using Replit Auth user ID:', userId);
} else if (req.user.id) {
userId = req.user.id; // Développement local
console.log('🔐 Using local auth user ID:', userId);
} else {
console.error('❌ No user ID found in request:', { user: req.user });
return res.status(401).json({ message: "User authentication failed" });
}
console.log('🔐 User requesting group creation:', userId);
// Vérifier l'utilisateur
const user = await storage.getUser(userId);
if (!user) {
console.error('❌ User not found:', userId);
return res.status(404).json({ message: "User not found" });
}
console.log('✅ User found:', { username: user.username, role: user.role });
// Vérifier les permissions
if (user.role !== 'admin' && user.role !== 'manager') {
console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager'] });
return res.status(403).json({ message: "Insufficient permissions" });
}
console.log('✅ User has permission to create group');
// Valider les données
console.log('🔍 Validating group data with schema...');
const data = insertGroupSchema.parse(req.body);
console.log('✅ Group data validation passed:', data);
// Créer le groupe
console.log('🏪 Creating group in database...');
const group = await storage.createGroup(data);
console.log('✅ Group creation successful:', { id: group.id, name: group.name });
res.json(group);
} catch (error) {
console.error('❌ Failed to create group:', {
error: error.message,
stack: error.stack,
body: req.body,
userId: req.user?.id || req.user?.claims?.sub || 'unknown'
});
// Erreur de validation Zod
if (error.name === 'ZodError') {
console.error('❌ Validation error details:', error.errors);
return res.status(400).json({
message: "Validation failed",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to create group" });
}
});
app.put('/api/groups/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || (user.role !== 'admin' && user.role !== 'manager')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
const data = insertGroupSchema.partial().parse(req.body);
const group = await storage.updateGroup(id, data);
res.json(group);
} catch (error) {
console.error("Error updating group:", error);
res.status(500).json({ message: "Failed to update group" });
}
});
app.delete('/api/groups/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || (user.role !== 'admin' && user.role !== 'manager')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
await storage.deleteGroup(id);
res.json({ message: "Group deleted successfully" });
} catch (error) {
console.error("Error deleting group:", error);
res.status(500).json({ message: "Failed to delete group" });
}
});
// Suppliers routes
app.get('/api/suppliers', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || (user.role !== 'admin' && user.role !== 'manager')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
// Check if DLC filter is requested
const dlcOnly = req.query.dlc === 'true';
const suppliers = await storage.getSuppliers(dlcOnly);
res.json(suppliers);
} catch (error) {
console.error("Error fetching suppliers:", error);
res.status(500).json({ message: "Failed to fetch suppliers" });
}
});
app.post('/api/suppliers', isAuthenticated, async (req: any, res) => {
try {
// Debug logging pour la création de fournisseur
console.log('📨 POST /api/suppliers - Headers:', {
'content-type': req.headers['content-type'],
'content-length': req.headers['content-length']
});
console.log('📋 POST /api/suppliers - Request body:', JSON.stringify(req.body, null, 2));
// Déterminer l'ID utilisateur selon l'environnement
let userId;
if (req.user.claims && req.user.claims.sub) {
userId = req.user.claims.sub; // Production Replit Auth
console.log('🔐 Using Replit Auth user ID:', userId);
} else if (req.user.id) {
userId = req.user.id; // Développement local
console.log('🔐 Using local auth user ID:', userId);
} else {
console.error('❌ No user ID found in request:', { user: req.user });
return res.status(401).json({ message: "User authentication failed" });
}
console.log('🔐 User requesting supplier creation:', userId);
// Vérifier l'utilisateur
const user = await storage.getUser(userId);
if (!user) {
console.error('❌ User not found:', userId);
return res.status(404).json({ message: "User not found" });
}
console.log('✅ User found:', { username: user.username, role: user.role });
// Vérifier les permissions
if (user.role !== 'admin' && user.role !== 'manager') {
console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager'] });
return res.status(403).json({ message: "Insufficient permissions" });
}
console.log('✅ User has permission to create supplier');
// Valider les données
console.log('🔍 Validating supplier data with schema...');
const data = insertSupplierSchema.parse(req.body);
console.log('✅ Supplier data validation passed:', data);
// Créer le fournisseur
console.log('🚚 Creating supplier in database...');
const supplier = await storage.createSupplier(data);
console.log('✅ Supplier creation successful:', { id: supplier.id, name: supplier.name });
res.json(supplier);
} catch (error) {
console.error('❌ Failed to create supplier:', {
error: error.message,
stack: error.stack,
body: req.body,
userId: req.user?.id || req.user?.claims?.sub || 'unknown'
});
// Erreur de validation Zod
if (error.name === 'ZodError') {
console.error('❌ Validation error details:', error.errors);
return res.status(400).json({
message: "Validation failed",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to create supplier" });
}
});
app.put('/api/suppliers/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || (user.role !== 'admin' && user.role !== 'manager')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
const data = insertSupplierSchema.partial().parse(req.body);
const supplier = await storage.updateSupplier(id, data);
res.json(supplier);
} catch (error) {
console.error("Error updating supplier:", error);
res.status(500).json({ message: "Failed to update supplier" });
}
});
app.delete('/api/suppliers/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || (user.role !== 'admin' && user.role !== 'manager')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
await storage.deleteSupplier(id);
res.json({ message: "Supplier deleted successfully" });
} catch (error) {
console.error("Error deleting supplier:", error);
res.status(500).json({ message: "Failed to delete supplier" });
}
});
// Orders routes
app.get('/api/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 { startDate, endDate, storeId } = req.query;
let orders;
console.log('Orders API called with:', { startDate, endDate, storeId, userRole: user.role });
if (user.role === 'admin') {
let groupIds: number[] | undefined;
// If admin selected a specific store, filter by it
if (storeId) {
groupIds = [parseInt(storeId as string)];
}
console.log('Admin filtering with groupIds:', groupIds);
// Only filter by date if both startDate and endDate are provided
if (startDate && endDate) {
console.log('Fetching orders by date range:', startDate, 'to', endDate);
orders = await storage.getOrdersByDateRange(startDate as string, endDate as string, groupIds);
} else {
console.log('Fetching all orders');
orders = await storage.getOrders(groupIds);
}
} else {
const groupIds = user.userGroups.map(ug => ug.groupId);
console.log('Non-admin filtering with groupIds:', groupIds);
// Only filter by date if both startDate and endDate are provided
if (startDate && endDate) {
orders = await storage.getOrdersByDateRange(startDate as string, endDate as string, groupIds);
} else {
orders = await storage.getOrders(groupIds);
}
}
console.log('Orders returned:', orders.length, 'items');
res.json(orders);
} catch (error) {
console.error("Error fetching orders:", error);
res.status(500).json({ message: "Failed to fetch orders" });
}
});
app.get('/api/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 order = await storage.getOrder(id);
if (!order) {
return res.status(404).json({ message: "Order not found" });
}
// Check if user has access to this order
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(order.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
res.json(order);
} catch (error) {
console.error("Error fetching order:", error);
res.status(500).json({ message: "Failed to fetch order" });
}
});
app.post('/api/orders', isAuthenticated, async (req: any, res) => {
try {
console.log('📦 Order creation started:', {
userId: req.user?.id || req.user?.claims?.sub,
body: req.body,
environment: process.env.NODE_ENV
});
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user) {
console.log('❌ User not found in order creation');
return res.status(404).json({ message: "User not found" });
}
console.log('👤 User found for order creation:', {
id: user.id,
role: user.role,
groupsCount: user.userGroups.length,
groups: user.userGroups.map(ug => ({ groupId: ug.groupId, groupName: ug.group?.name }))
});
const data = insertOrderSchema.parse({
...req.body,
createdBy: user.id,
});
console.log('✅ Order data validated:', data);
// 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)) {
console.log('❌ Access denied to group:', { requestedGroupId: data.groupId, userGroups: userGroupIds });
return res.status(403).json({ message: "Access denied to this group" });
}
}
console.log('🚀 Creating order in storage...');
const order = await storage.createOrder(data);
console.log('✅ Order created successfully:', {
id: order.id,
groupId: order.groupId,
plannedDate: order.plannedDate,
supplierId: order.supplierId
});
res.json(order);
} catch (error) {
console.error("❌ Error creating order:", {
error: error.message,
stack: error.stack,
body: req.body,
userId: req.user?.id || req.user?.claims?.sub || 'unknown'
});
// Erreur de validation Zod
if (error.name === 'ZodError') {
console.error('❌ Order validation error details:', error.errors);
return res.status(400).json({
message: "Validation failed",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to create order" });
}
});
app.put('/api/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 order = await storage.getOrder(id);
if (!order) {
return res.status(404).json({ message: "Order not found" });
}
// Check permissions
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(order.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const data = insertOrderSchema.partial().parse(req.body);
const updatedOrder = await storage.updateOrder(id, data);
res.json(updatedOrder);
} catch (error) {
console.error("Error updating order:", error);
res.status(500).json({ message: "Failed to update order" });
}
});
app.delete('/api/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 order = await storage.getOrder(id);
if (!order) {
return res.status(404).json({ message: "Order not found" });
}
// Check permissions
if (user.role !== 'admin' && user.role !== 'manager') {
return res.status(403).json({ message: "Insufficient permissions" });
}
if (user.role === 'manager') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(order.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
await storage.deleteOrder(id);
res.json({ message: "Order deleted successfully" });
} catch (error) {
console.error("Error deleting order:", error);
res.status(500).json({ message: "Failed to delete order" });
}
});
// Deliveries routes
app.get('/api/deliveries', 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 { startDate, endDate, storeId, withBL } = req.query;
let deliveries;
console.log('Deliveries API called with:', { startDate, endDate, storeId, withBL, userRole: user.role });
if (user.role === 'admin') {
let groupIds: number[] | undefined;
// If admin selected a specific store, filter by it
if (storeId) {
groupIds = [parseInt(storeId as string)];
}
console.log('Admin filtering deliveries with groupIds:', groupIds);
// Only filter by date if both startDate and endDate are provided
if (startDate && endDate) {
console.log('Fetching deliveries by date range:', startDate, 'to', endDate);
deliveries = await storage.getDeliveriesByDateRange(startDate as string, endDate as string, groupIds);
} else {
console.log('Fetching all deliveries');
deliveries = await storage.getDeliveries(groupIds);
}
} else {
const groupIds = user.userGroups.map(ug => ug.groupId);
console.log('Non-admin filtering deliveries with groupIds:', groupIds);
// Only filter by date if both startDate and endDate are provided
if (startDate && endDate) {
deliveries = await storage.getDeliveriesByDateRange(startDate as string, endDate as string, groupIds);
} else {
deliveries = await storage.getDeliveries(groupIds);
}
}
// Filter for BL if requested
if (withBL === 'true') {
deliveries = deliveries.filter((d: any) => d.blNumber && d.status === 'delivered');
}
console.log('Deliveries returned:', deliveries.length, 'items');
res.json(deliveries);
} catch (error) {
console.error("Error fetching deliveries:", error);
res.status(500).json({ message: "Failed to fetch deliveries" });
}
});
app.get('/api/deliveries/: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 delivery = await storage.getDelivery(id);
if (!delivery) {
return res.status(404).json({ message: "Delivery not found" });
}
// Check if user has access to this delivery
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(delivery.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
res.json(delivery);
} catch (error) {
console.error("Error fetching delivery:", error);
res.status(500).json({ message: "Failed to fetch delivery" });
}
});
app.put('/api/deliveries/: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 delivery = await storage.getDelivery(id);
if (!delivery) {
return res.status(404).json({ message: "Delivery not found" });
}
// Check permissions
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(delivery.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const data = insertDeliverySchema.partial().parse(req.body);
const updatedDelivery = await storage.updateDelivery(id, data);
res.json(updatedDelivery);
} catch (error) {
console.error("Error updating delivery:", error);
res.status(500).json({ message: "Failed to update delivery" });
}
});
app.post('/api/deliveries', 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 = insertDeliverySchema.parse({
...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 delivery = await storage.createDelivery(data);
res.json(delivery);
} catch (error) {
console.error("Error creating delivery:", error);
res.status(500).json({ message: "Failed to create delivery" });
}
});
app.put('/api/deliveries/: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 delivery = await storage.getDelivery(id);
if (!delivery) {
return res.status(404).json({ message: "Delivery not found" });
}
// Check permissions
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(delivery.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const data = insertDeliverySchema.partial().parse(req.body);
const updatedDelivery = await storage.updateDelivery(id, data);
res.json(updatedDelivery);
} catch (error) {
console.error("Error updating delivery:", error);
res.status(500).json({ message: "Failed to update delivery" });
}
});
app.delete('/api/deliveries/: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 delivery = await storage.getDelivery(id);
if (!delivery) {
return res.status(404).json({ message: "Delivery not found" });
}
// Check permissions
if (user.role !== 'admin' && user.role !== 'manager') {
return res.status(403).json({ message: "Insufficient permissions" });
}
if (user.role === 'manager') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(delivery.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
await storage.deleteDelivery(id);
res.json({ message: "Delivery deleted successfully" });
} catch (error) {
console.error("Error deleting delivery:", error);
res.status(500).json({ message: "Failed to delete delivery" });
}
});
app.post('/api/deliveries/: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" });
}
if (user.role !== 'admin' && user.role !== 'manager') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
const delivery = await storage.getDelivery(id);
if (!delivery) {
return res.status(404).json({ message: "Delivery not found" });
}
// Check permissions
if (user.role === 'manager') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(delivery.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const { blNumber, blAmount } = req.body;
// BL data is optional - delivery can be validated without it
let blData: any = undefined;
if (blNumber) {
blData = { blNumber };
if (blAmount !== undefined && blAmount !== null && blAmount !== '') {
blData.blAmount = blAmount;
}
}
await storage.validateDelivery(id, blData);
res.json({ message: "Delivery validated successfully" });
} catch (error) {
console.error("Error validating delivery:", error);
res.status(500).json({ message: "Failed to validate delivery" });
}
});
// Statistics routes
app.get('/api/stats/monthly', 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 { year, month, storeId } = req.query;
const currentYear = year ? parseInt(year as string) : new Date().getFullYear();
const currentMonth = month ? parseInt(month as string) : new Date().getMonth() + 1;
let groupIds: number[] | undefined;
if (user.role === 'admin') {
// Admin can view all stores or filter by selected store
groupIds = storeId ? [parseInt(storeId as string)] : undefined;
} else {
// Non-admin users: filter by their assigned groups
const userGroupIds = user.userGroups.map(ug => ug.groupId);
// If a specific store is selected and user has access, filter by it
if (storeId && userGroupIds.includes(parseInt(storeId as string))) {
groupIds = [parseInt(storeId as string)];
} else {
groupIds = userGroupIds;
}
}
const stats = await storage.getMonthlyStats(currentYear, currentMonth, groupIds);
res.json(stats);
} catch (error) {
console.error("Error fetching stats:", error);
res.status(500).json({ message: "Failed to fetch statistics" });
}
});
// User-Group management routes (admin only)
app.post('/api/users/:userId/groups', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const data = insertUserGroupSchema.parse({
userId,
groupId: req.body.groupId,
});
const userGroup = await storage.assignUserToGroup(data);
res.json(userGroup);
} catch (error) {
console.error("Error assigning user to group:", error);
res.status(500).json({ message: "Failed to assign user to group" });
}
});
app.delete('/api/users/:userId/groups/:groupId', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const groupId = parseInt(req.params.groupId);
await storage.removeUserFromGroup(userId, groupId);
res.json({ message: "User removed from group successfully" });
} catch (error) {
console.error("Error removing user from group:", error);
res.status(500).json({ message: "Failed to remove user from group" });
}
});
// Users management routes
app.get('/api/users', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
// Get all users with groups for admin
const allUsers = await storage.getUsers();
const safeUsers = Array.isArray(allUsers) ? allUsers : [];
console.log('🔐 API /api/users - Returning:', { isArray: Array.isArray(safeUsers), length: safeUsers.length });
res.json(safeUsers);
} catch (error) {
console.error("Error fetching users:", error);
// En cas d'erreur, retourner un array vide pour éviter React Error #310
res.status(500).json([]);
}
});
app.post('/api/users', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const currentUser = await storage.getUserWithGroups(userId);
if (!currentUser || currentUser.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
// Schema création utilisateur SANS champs obligatoires pour résoudre le problème de production
const createUserSchema = z.object({
id: z.string().optional(),
username: z.string().min(1, "L'identifiant est obligatoire"),
email: z.union([z.string().email(), z.literal("")]).optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
password: z.string().min(1, "Le mot de passe est obligatoire"),
role: z.enum(['admin', 'manager', 'employee']).optional(),
});
const userData = createUserSchema.parse(req.body);
// Hash password if provided (for local auth)
if (userData.password) {
const { hashPassword } = await import("./localAuth");
userData.password = await hashPassword(userData.password);
}
const newUser = await storage.createUser({
id: userData.id || `manual_${Date.now()}`, // Generate manual ID for created users
...userData,
});
res.json(newUser);
} catch (error) {
console.error("Error creating user:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ message: "Invalid user data", errors: error.errors });
}
// Handle specific database constraint errors
if (error.code === '23505') {
if (error.constraint === 'users_username_key') {
return res.status(409).json({
message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur."
});
}
if (error.constraint === 'users_email_key') {
return res.status(409).json({
message: "Un utilisateur avec cette adresse email existe déjà."
});
}
}
res.status(500).json({ message: "Failed to create user" });
}
});
app.put('/api/users/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
// Schema utilisateur SANS champs obligatoires pour résoudre le problème de production
const updateUserSchema = z.object({
username: z.string().optional(),
role: z.enum(['admin', 'manager', 'employee']).optional(),
firstName: z.union([z.string(), z.literal("")]).optional(),
lastName: z.union([z.string(), z.literal("")]).optional(),
email: z.union([z.string().email(), z.literal("")]).optional(),
password: z.string().optional(),
});
const userData = updateUserSchema.parse(req.body);
// Hash password if provided
if (userData.password) {
const { hashPassword } = await import("./localAuth");
userData.password = await hashPassword(userData.password);
// Mark password as changed
(userData as any).passwordChanged = true;
}
const updatedUser = await storage.updateUser(req.params.id, userData);
res.json(updatedUser);
} catch (error: any) {
console.error("Error updating user:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ message: "Invalid user data", errors: error.errors });
}
// Handle specific database constraint errors
if (error.code === '23505') {
if (error.constraint === 'users_username_key') {
return res.status(409).json({
message: "Un utilisateur avec ce nom d'utilisateur existe déjà. Veuillez choisir un autre nom d'utilisateur."
});
}
if (error.constraint === 'users_email_key') {
return res.status(409).json({
message: "Un utilisateur avec cette adresse email existe déjà. Veuillez utiliser une autre adresse email ou laisser le champ vide."
});
}
}
res.status(500).json({ message: "Failed to update user" });
}
});
app.post('/api/users/:id/groups', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
const { groupId } = req.body;
await storage.assignUserToGroup({
userId: req.params.id,
groupId: parseInt(groupId),
});
res.json({ message: "User assigned to group successfully" });
} catch (error) {
console.error("Error assigning user to group:", error);
res.status(500).json({ message: "Failed to assign user to group" });
}
});
app.delete('/api/users/:id/groups/:groupId', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
await storage.removeUserFromGroup(req.params.id, parseInt(req.params.groupId));
res.json({ message: "User removed from group successfully" });
} catch (error) {
console.error("Error removing user from group:", error);
res.status(500).json({ message: "Failed to remove user from group" });
}
});
// Delete user route
app.delete('/api/users/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
const userToDelete = req.params.id;
// Prevent admin from deleting themselves
if (userToDelete === user.id) {
return res.status(400).json({ message: "Vous ne pouvez pas supprimer votre propre compte" });
}
// Remove user from all groups first
const userWithGroups = await storage.getUserWithGroups(userToDelete);
if (userWithGroups) {
for (const userGroup of userWithGroups.userGroups) {
await storage.removeUserFromGroup(userToDelete, userGroup.groupId);
}
}
// Delete the user
await storage.deleteUser(userToDelete);
res.json({ message: "Utilisateur supprimé avec succès" });
} catch (error) {
console.error("Error deleting user:", error);
res.status(500).json({ message: "Failed to delete user" });
}
});
// Publicity routes
app.get('/api/publicities', 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 { year, storeId } = req.query;
const filterYear = year ? parseInt(year as string) : undefined;
let groupIds: number[] | undefined;
if (user.role === 'admin') {
// Admin can view all publicities or filter by selected store
groupIds = storeId ? [parseInt(storeId as string)] : undefined;
} else {
// Non-admin users: filter by their assigned groups
groupIds = user.userGroups.map(ug => ug.groupId);
}
const publicities = await storage.getPublicities(filterYear, groupIds);
res.json(publicities);
} catch (error) {
console.error("Error fetching publicities:", error);
res.status(500).json({ message: "Failed to fetch publicities" });
}
});
app.get('/api/publicities/: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 publicity = await storage.getPublicity(id);
if (!publicity) {
return res.status(404).json({ message: "Publicity not found" });
}
// Check access permissions
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
const hasAccess = publicity.participations.some(p => userGroupIds.includes(p.groupId));
if (!hasAccess) {
return res.status(403).json({ message: "Access denied" });
}
}
res.json(publicity);
} catch (error) {
console.error("Error fetching publicity:", error);
res.status(500).json({ message: "Failed to fetch publicity" });
}
});
app.post('/api/publicities', 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" });
}
// Check permissions (admin or manager)
if (user.role === 'employee') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const data = insertPublicitySchema.parse({
...req.body,
createdBy: req.user.claims ? req.user.claims.sub : req.user.id
});
const { participatingGroups, ...publicityData } = req.body;
// Create publicity
const newPublicity = await storage.createPublicity(data);
// Set participations
if (participatingGroups && participatingGroups.length > 0) {
await storage.setPublicityParticipations(newPublicity.id, participatingGroups);
}
// Get the complete publicity with relations
const completePublicity = await storage.getPublicity(newPublicity.id);
res.json(completePublicity);
} catch (error) {
console.error("Error creating publicity:", error);
res.status(500).json({ message: "Failed to create publicity" });
}
});
app.put('/api/publicities/: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" });
}
// Check permissions (admin or manager)
if (user.role === 'employee') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
const { participatingGroups, ...publicityData } = req.body;
// Update publicity
const updatedPublicity = await storage.updatePublicity(id, publicityData);
// Update participations
if (participatingGroups !== undefined) {
await storage.setPublicityParticipations(id, participatingGroups);
}
// Get the complete publicity with relations
const completePublicity = await storage.getPublicity(id);
res.json(completePublicity);
} catch (error) {
console.error("Error updating publicity:", error);
res.status(500).json({ message: "Failed to update publicity" });
}
});
app.delete('/api/publicities/: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" });
}
// Check permissions (admin only for deletion)
if (user.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const id = parseInt(req.params.id);
await storage.deletePublicity(id);
res.json({ message: "Publicity deleted successfully" });
} catch (error) {
console.error("Error deleting publicity:", error);
res.status(500).json({ message: "Failed to delete publicity" });
}
});
// Invoice verification routes
app.post('/api/verify-invoice', isAuthenticated, async (req: any, res) => {
try {
const { groupId, invoiceReference } = req.body;
if (!groupId || !invoiceReference) {
return res.status(400).json({ message: "groupId and invoiceReference are required" });
}
const { verifyInvoiceReference } = await import('./nocodbService.js');
const result = await verifyInvoiceReference(groupId, invoiceReference);
res.json(result);
} catch (error) {
console.error("Error verifying invoice:", error);
res.status(500).json({ message: "Failed to verify invoice" });
}
});
app.post('/api/verify-invoices', isAuthenticated, async (req: any, res) => {
try {
const { invoiceReferences } = req.body;
if (!Array.isArray(invoiceReferences)) {
return res.status(400).json({ message: "invoiceReferences must be an array" });
}
// Add supplier name to invoice references for verification
const enrichedReferences = invoiceReferences.map((ref: any) => ({
...ref,
supplierName: ref.supplierName // Include supplier name for matching
}));
const { verifyMultipleInvoiceReferences } = await import('./nocodbService.js');
const results = await verifyMultipleInvoiceReferences(enrichedReferences);
res.json(results);
} catch (error) {
console.error("Error verifying invoices:", error);
res.status(500).json({ message: "Failed to verify invoices" });
}
});
// NocoDB Configuration routes
app.get('/api/nocodb-config', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' });
}
const configs = await storage.getNocodbConfigs();
// Assurer que la réponse est toujours un array
res.json(Array.isArray(configs) ? configs : []);
} catch (error) {
console.error('Error fetching NocoDB configs:', error);
res.status(500).json({ message: 'Erreur lors de la récupération des configurations' });
}
});
app.get('/api/nocodb-config/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' });
}
const id = parseInt(req.params.id);
const config = await storage.getNocodbConfig(id);
if (!config) {
return res.status(404).json({ message: 'Configuration non trouvée' });
}
res.json(config);
} catch (error) {
console.error('Error fetching NocoDB config:', error);
res.status(500).json({ message: 'Erreur lors de la récupération de la configuration' });
}
});
app.post('/api/nocodb-config', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' });
}
const configData = {
...req.body,
createdBy: req.user.claims ? req.user.claims.sub : req.user.id,
};
const config = await storage.createNocodbConfig(configData);
res.status(201).json(config);
} catch (error) {
console.error('Error creating NocoDB config:', error);
res.status(500).json({ message: 'Erreur lors de la création de la configuration' });
}
});
app.put('/api/nocodb-config/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' });
}
const id = parseInt(req.params.id);
const config = await storage.updateNocodbConfig(id, req.body);
res.json(config);
} catch (error) {
console.error('Error updating NocoDB config:', error);
res.status(500).json({ message: 'Erreur lors de la mise à jour de la configuration' });
}
});
app.delete('/api/nocodb-config/:id', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' });
}
const id = parseInt(req.params.id);
await storage.deleteNocodbConfig(id);
res.status(204).send();
} catch (error) {
console.error('Error deleting NocoDB config:', error);
res.status(500).json({ message: 'Erreur lors de la suppression de la configuration' });
}
});
// Get all roles
// Get all permissions
// Get permissions for a specific role
// Set permissions for a role
// Set roles for a user
app.post('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
const { userId } = req.params;
const { roleIds } = req.body;
if (!Array.isArray(roleIds)) {
return res.status(400).json({ message: "roleIds must be an array" });
}
const assignedBy = req.user.claims ? req.user.claims.sub : req.user.id;
await storage.setUserRoles(userId, roleIds, assignedBy);
console.log(`✅ Roles updated for user ${userId}:`, roleIds);
res.json({ message: "User roles updated successfully" });
} catch (error) {
console.error("Error setting user roles:", error);
res.status(500).json({ message: "Failed to update user roles" });
}
});
// Get roles for a specific user
app.get('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Access denied" });
}
const { userId } = req.params;
const userRoles = await storage.getUserRoles(userId);
res.json(Array.isArray(userRoles) ? userRoles : []);
} catch (error) {
console.error("Error fetching user roles:", error);
res.status(500).json([]);
}
});
// Customer Orders routes
app.get('/api/customer-orders', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
const { storeId } = req.query;
// Determine which groups to show
let groupIds;
if (user.role === 'admin' && storeId) {
// Admin filtering by specific store
groupIds = [parseInt(storeId.toString())];
console.log("Customer orders - Admin filtering by store:", { storeId, groupIds });
} else if (user.role === 'admin') {
// Admin viewing all stores - get all groups
const allGroups = await storage.getGroups();
groupIds = allGroups.map(g => g.id);
console.log("Customer orders - Admin viewing all stores:", { groupCount: groupIds.length });
} else {
// Non-admin users see only their assigned stores
groupIds = user.userGroups.map(ug => ug.groupId);
console.log("Customer orders - User assigned stores:", { groupIds });
}
const customerOrders = await storage.getCustomerOrders(groupIds);
console.log("Customer orders returned from storage:", customerOrders?.length || 0, "items");
res.json(customerOrders || []);
} catch (error) {
console.error("Error fetching customer orders:", error);
res.status(500).json({ message: "Failed to fetch customer orders" });
}
});
app.get('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => {
try {
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 if user has access to this order's group
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
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" });
}
}
res.json(customerOrder);
} catch (error) {
console.error("Error fetching customer order:", error);
res.status(500).json({ message: "Failed to fetch customer order" });
}
});
app.post('/api/customer-orders', isAuthenticated, async (req: any, res) => {
try {
console.log("Raw body received:", req.body);
console.log("Body type:", typeof req.body);
console.log("Body keys:", req.body ? Object.keys(req.body) : 'no keys');
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
const data = insertCustomerOrderSchema.parse(req.body);
console.log("Parsed data:", data);
// Check if user has access to the specified 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,
createdBy: userId,
});
res.status(201).json(customerOrder);
} catch (error) {
console.error("Error creating customer order:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ message: "Invalid data", errors: error.errors });
}
res.status(500).json({ message: "Failed to create customer order" });
}
});
app.put('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Check if order exists and user has access
const existingOrder = await storage.getCustomerOrder(id);
if (!existingOrder) {
return res.status(404).json({ message: "Customer order not found" });
}
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.groupId);
if (!userGroupIds.includes(existingOrder.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const data = insertCustomerOrderSchema.partial().parse(req.body);
const customerOrder = await storage.updateCustomerOrder(id, data);
res.json(customerOrder);
} 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 id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Check if order exists and user has access
const existingOrder = await storage.getCustomerOrder(id);
if (!existingOrder) {
return res.status(404).json({ message: "Customer order not found" });
}
// Only admin can delete orders
if (user.role !== 'admin') {
return res.status(403).json({ message: "Only admins can delete customer orders" });
}
await storage.deleteCustomerOrder(id);
res.status(204).send();
} catch (error) {
console.error("Error deleting customer order:", error);
res.status(500).json({ message: "Failed to delete customer order" });
}
});
// ===== ROLE MANAGEMENT ROUTES =====
// Roles routes
// Role-Permission association routes
// User-Role association routes
app.get('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const userRoles = await storage.getUserRoles(userId);
res.json(userRoles);
} catch (error) {
console.error("Error fetching user roles:", error);
res.status(500).json({ message: "Failed to fetch user roles" });
}
});
// POST route for user roles (used by frontend)
app.post('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => {
try {
const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!currentUser || currentUser.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const { roleIds } = req.body;
console.log("🔧 POST User roles API called:", { userId, roleIds, assignedBy: currentUser.id });
if (!Array.isArray(roleIds)) {
return res.status(400).json({ message: "roleIds must be an array" });
}
const assignedBy = currentUser.id;
await storage.setUserRoles(userId, roleIds, assignedBy);
console.log("✅ User roles updated successfully:", { userId, roleIds });
res.json({ message: "User roles updated successfully" });
} catch (error) {
console.error("Error setting user roles:", error);
res.status(500).json({ message: "Failed to update user roles" });
}
});
app.put('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => {
try {
const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!currentUser || currentUser.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const { roleIds } = req.body;
if (!Array.isArray(roleIds)) {
return res.status(400).json({ message: "roleIds must be an array" });
}
const assignedBy = currentUser.id;
await storage.setUserRoles(userId, roleIds, assignedBy);
res.json({ message: "User roles updated successfully" });
} catch (error) {
console.error("Error setting user roles:", error);
res.status(500).json({ message: "Failed to set user roles" });
}
});
app.get('/api/users/:userId/permissions', isAuthenticated, async (req: any, res) => {
try {
const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
if (!user || user.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const userId = req.params.userId;
const permissions = await storage.getUserEffectivePermissions(userId);
res.json(permissions);
} catch (error) {
console.error("Error fetching user permissions:", error);
res.status(500).json({ message: "Failed to fetch user permissions" });
}
});
// Permission checking routes
app.get('/api/users/:userId/has-permission/:permissionName', isAuthenticated, async (req: any, res) => {
try {
const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
const userId = req.params.userId;
const permissionName = req.params.permissionName;
// Users can check their own permissions, admins can check anyone's
if (currentUser?.id !== userId && currentUser?.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const hasPermission = await storage.userHasPermission(userId, permissionName);
res.json({ hasPermission });
} catch (error) {
console.error("Error checking user permission:", error);
res.status(500).json({ message: "Failed to check permission" });
}
});
app.get('/api/users/:userId/has-role/:roleName', isAuthenticated, async (req: any, res) => {
try {
const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id);
const userId = req.params.userId;
const roleName = req.params.roleName;
// Users can check their own roles, admins can check anyone's
if (currentUser?.id !== userId && currentUser?.role !== 'admin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const hasRole = await storage.userHasRole(userId, roleName);
res.json({ hasRole });
} catch (error) {
console.error("Error checking user role:", error);
res.status(500).json({ message: "Failed to check role" });
}
});
// DLC Products routes
app.get('/api/dlc-products', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
const { status, supplierId } = req.query;
// Determine which groups to filter by
let groupIds: number[] = [];
if (user.role === 'admin') {
// Admin can specify a store or see all
if (req.query.storeId) {
groupIds = [parseInt(req.query.storeId)];
}
// If no storeId specified, admin sees all (don't filter by groupIds)
} else {
// Non-admin users see only their assigned groups
groupIds = user.userGroups.map(ug => ug.group.id);
}
const filters: { status?: string; supplierId?: number; } = {};
if (status) filters.status = status;
if (supplierId) filters.supplierId = parseInt(supplierId);
console.log('DLC Products API called with:', {
userId,
userRole: user.role,
groupIds: user.role === 'admin' && !req.query.storeId ? 'all' : groupIds,
filters
});
const dlcProducts = await storage.getDlcProducts(
user.role === 'admin' && !req.query.storeId ? undefined : 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({ message: "Failed to fetch DLC products" });
}
});
app.get('/api/dlc-products/stats', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Determine which groups to filter by
let groupIds: number[] = [];
if (user.role === 'admin') {
if (req.query.storeId) {
groupIds = [parseInt(req.query.storeId)];
}
} else {
groupIds = user.userGroups.map(ug => ug.group.id);
}
const stats = await storage.getDlcStats(
user.role === 'admin' && !req.query.storeId ? undefined : groupIds
);
res.json(stats);
} catch (error) {
console.error("Error fetching DLC stats:", error);
res.status(500).json({ message: "Failed to fetch DLC stats" });
}
});
app.get('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => {
try {
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 if user has access to this product's group
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
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 {
console.log('📨 POST /api/dlc-products - Request body:', JSON.stringify(req.body, null, 2));
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
// Validate access to the specified group
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.group.id);
if (!userGroupIds.includes(req.body.groupId)) {
return res.status(403).json({ message: "Access denied to this store" });
}
}
const validatedData = insertDlcProductFrontendSchema.parse({
...req.body,
createdBy: userId,
});
const dlcProduct = await storage.createDlcProduct(validatedData);
console.log('✅ DLC Product created successfully:', dlcProduct.id);
res.status(201).json(dlcProduct);
} catch (error) {
console.error("❌ Error creating DLC product:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to create DLC product" });
}
});
app.put('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
// First check if the product exists and user has access
const existingProduct = await storage.getDlcProduct(id);
if (!existingProduct) {
return res.status(404).json({ message: "DLC Product not found" });
}
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingProduct.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const validatedData = insertDlcProductFrontendSchema.partial().parse(req.body);
const dlcProduct = await storage.updateDlcProduct(id, validatedData);
res.json(dlcProduct);
} catch (error) {
console.error("Error updating DLC product:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to update DLC product" });
}
});
app.post('/api/dlc-products/:id/validate', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUser(userId);
if (!user || !['admin', 'manager'].includes(user.role)) {
return res.status(403).json({ message: "Insufficient permissions to validate products" });
}
// Check if the product exists and user has access
const existingProduct = await storage.getDlcProduct(id);
if (!existingProduct) {
return res.status(404).json({ message: "DLC Product not found" });
}
if (user.role !== 'admin') {
const userWithGroups = await storage.getUserWithGroups(userId);
const userGroupIds = userWithGroups?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingProduct.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const dlcProduct = await storage.validateDlcProduct(id, userId);
res.json(dlcProduct);
} catch (error) {
console.error("Error validating DLC product:", error);
res.status(500).json({ message: "Failed to validate DLC product" });
}
});
app.delete('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
// Check if the product exists and user has access
const existingProduct = await storage.getDlcProduct(id);
if (!existingProduct) {
return res.status(404).json({ message: "DLC Product not found" });
}
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingProduct.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
// Non-admin users can only delete their own products
if (existingProduct.createdBy !== userId) {
return res.status(403).json({ message: "Can only delete your own products" });
}
}
await storage.deleteDlcProduct(id);
res.status(204).send();
} catch (error) {
console.error("Error deleting DLC product:", error);
res.status(500).json({ message: "Failed to delete DLC product" });
}
});
// Tasks routes
app.get('/api/tasks', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
const storeId = req.query.storeId;
if (!user) {
return res.status(404).json({ message: "User not found" });
}
let groupIds: number[] | undefined;
if (user.role === 'admin') {
// Admin peut voir toutes les tâches ou filtrer par magasin
if (storeId && storeId !== 'all') {
groupIds = [parseInt(storeId)];
}
// Si pas de storeId ou storeId='all', pas de filtrage (toutes les tâches)
} else {
// Non-admin voit seulement ses magasins assignés
const userGroupIds = user.userGroups.map(ug => ug.group.id);
if (storeId && storeId !== 'all') {
const requestedStoreId = parseInt(storeId);
if (userGroupIds.includes(requestedStoreId)) {
groupIds = [requestedStoreId];
} else {
return res.status(403).json({ message: "Access denied to this store" });
}
} else {
groupIds = userGroupIds;
}
}
const tasks = await storage.getTasks(groupIds);
res.json(tasks);
} catch (error) {
console.error("Error fetching tasks:", error);
res.status(500).json({ message: "Failed to fetch tasks" });
}
});
app.get('/api/tasks/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const task = await storage.getTask(id);
if (!task) {
return res.status(404).json({ message: "Task not found" });
}
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(task.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
res.json(task);
} catch (error) {
console.error("Error fetching task:", error);
res.status(500).json({ message: "Failed to fetch task" });
}
});
app.post('/api/tasks', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
if (!user) {
return res.status(404).json({ message: "User not found" });
}
if (user.role !== 'admin') {
const userGroupIds = user.userGroups.map(ug => ug.group.id);
if (!userGroupIds.includes(req.body.groupId)) {
return res.status(403).json({ message: "Access denied to this store" });
}
}
const validatedData = insertTaskSchema.parse({
...req.body,
createdBy: userId,
});
const task = await storage.createTask(validatedData);
res.status(201).json(task);
} catch (error) {
console.error("Error creating task:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to create task" });
}
});
app.put('/api/tasks/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const existingTask = await storage.getTask(id);
if (!existingTask) {
return res.status(404).json({ message: "Task not found" });
}
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingTask.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
}
const validatedData = insertTaskSchema.partial().parse(req.body);
const task = await storage.updateTask(id, validatedData);
res.json(task);
} catch (error) {
console.error("Error updating task:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.errors
});
}
res.status(500).json({ message: "Failed to update task" });
}
});
app.post('/api/tasks/:id/complete', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
console.log("🎯 DEV Task completion request:", { id, userId });
const existingTask = await storage.getTask(id);
if (!existingTask) {
console.log("❌ Task not found:", id);
return res.status(404).json({ message: "Task not found" });
}
console.log("✅ Task found:", existingTask);
const user = await storage.getUserWithGroups(userId);
console.log("👤 User data:", user);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingTask.groupId)) {
console.log("❌ Access denied - group mismatch:", { userGroupIds, taskGroupId: existingTask.groupId });
return res.status(403).json({ message: "Access denied" });
}
}
console.log("🔄 Completing task using storage.completeTask...");
await storage.completeTask(id, userId);
const updatedTask = await storage.getTask(id);
console.log("✅ Task completed successfully:", updatedTask);
res.json(updatedTask);
} catch (error) {
console.error("❌ Error completing task:", error);
res.status(500).json({ message: "Failed to complete task", error: error.message });
}
});
app.delete('/api/tasks/:id', isAuthenticated, async (req: any, res) => {
try {
const id = parseInt(req.params.id);
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const existingTask = await storage.getTask(id);
if (!existingTask) {
return res.status(404).json({ message: "Task not found" });
}
const user = await storage.getUserWithGroups(userId);
if (user?.role !== 'admin') {
const userGroupIds = user?.userGroups.map(ug => ug.group.id) || [];
if (!userGroupIds.includes(existingTask.groupId)) {
return res.status(403).json({ message: "Access denied" });
}
if (existingTask.createdBy !== userId) {
return res.status(403).json({ message: "Can only delete your own tasks" });
}
}
await storage.deleteTask(id);
res.status(204).send();
} catch (error) {
console.error("Error deleting task:", error);
res.status(500).json({ message: "Failed to delete task" });
}
});
// Debug endpoint to check permissions without auth (temporary)
// Debug endpoint to check current user auth status
app.get('/api/debug/auth-status', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims ? req.user.claims.sub : req.user.id;
const user = await storage.getUserWithGroups(userId);
res.json({
isAuthenticated: true,
userId: userId,
user: user ? {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
firstName: user.firstName,
lastName: user.lastName
} : null,
canAccessPermissions: user?.role === 'admin',
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Debug endpoint to check task permissions specifically
// Test CRITIQUE - Comparaison des storages pour permissions tâches
app.get('/api/debug/compare-storages', isAuthenticated, async (req: any, res) => {
try {
console.log('🔍 CRITICAL TEST - Comparing dev vs prod storage for task permissions');
// Test développement
const devTaskPerms = devPermissions.filter(p => p.category === 'gestion_taches');
console.log('📋 DEV storage - Task permissions:', devTaskPerms.length);
// Test production
const prodTaskPerms = prodPermissions.filter(p => p.category === 'gestion_taches');
console.log('📋 PROD storage - Task permissions:', prodTaskPerms.length);
res.json({
development: {
total: devPermissions.length,
task_permissions: devTaskPerms.length,
categories: [...new Set(devPermissions.map(p => p.category))].sort(),
task_details: devTaskPerms
},
production: {
total: prodPermissions.length,
task_permissions: prodTaskPerms.length,
categories: [...new Set(prodPermissions.map(p => p.category))].sort(),
task_details: prodTaskPerms
},
database_verified: true,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('❌ Storage comparison error:', error);
res.status(500).json({ error: error.message });
}
});
// ENDPOINT TEMPORAIRE - Test production storage avec gestion d'erreur détaillée
// Debug/Fix endpoint for production permission issues
const httpServer = createServer(app);
return httpServer;
}