diff --git a/.replit b/.replit index f9a4cd6..164cd5a 100644 --- a/.replit +++ b/.replit @@ -14,6 +14,10 @@ run = ["npm", "run", "start"] localPort = 5000 externalPort = 80 +[[ports]] +localPort = 33821 +externalPort = 8099 + [[ports]] localPort = 34045 externalPort = 3003 @@ -46,6 +50,10 @@ externalPort = 8081 localPort = 40537 externalPort = 3000 +[[ports]] +localPort = 41803 +externalPort = 8008 + [[ports]] localPort = 42161 externalPort = 6000 diff --git a/replit.md b/replit.md index 2c7e2e7..94b0244 100644 --- a/replit.md +++ b/replit.md @@ -11,6 +11,9 @@ Preferred communication style: Simple, everyday language. ## Recent Changes ### October 10, 2025 +- **Calendar Permissions System**: Implemented comprehensive page-based permissions for calendar access. Admin users see all scheduled posts with full edit/delete rights. Standard users see only posts from their assigned pages via `user_page_permissions` table, but can only edit/delete their own posts (ownership check via `post.userId`). GET /api/scheduled-posts filters posts by accessible pages (admin bypass). DELETE and PATCH /api/scheduled-posts enforce ownership validation (admin can modify any post, users limited to own posts). +- **Scheduled Posts Data Sync Fix**: Fixed critical bug where editing scheduled post time via PATCH /api/scheduled-posts/:id updated `scheduled_posts.scheduledAt` but not `posts.scheduledFor`, causing calendar/database inconsistency. Now both tables are synchronized: updating scheduledAt automatically updates scheduledFor via `storage.updatePost()`. +- **Scheduled Posts Validation**: Added validation to POST /api/posts requiring `pageIds` when `scheduledFor` is provided. Prevents orphaned scheduled posts that would be invisible in calendar. Returns 400 error: "Les posts programmés nécessitent au moins une page cible". - **AI Assistant Admin-Only Access**: Restricted access to AI Assistant to administrators only. Route `/ai` now requires admin role with `adminOnly` prop. Sidebar moved "Assistant IA" link to Administration section (visible only to admins). Backend endpoints (`/api/ai/models`, `/api/ai/generate`, `/api/ai/generations`) protected with `requireAdmin` middleware. Standard users attempting to access AI features see "Accès refusé" message and receive 403 responses. ### October 9, 2025 diff --git a/server/routes.ts b/server/routes.ts index 4b4dcc7..da9b905 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -611,6 +611,11 @@ export async function registerRoutes(app: Express): Promise { // Set status to "scheduled" if scheduledFor is provided, otherwise "draft" if (postFields.scheduledFor) { postFields.status = "scheduled"; + + // Validate that scheduled posts require at least one page + if (!pageIds || !Array.isArray(pageIds) || pageIds.length === 0) { + return res.status(400).json({ error: "Les posts programmés nécessitent au moins une page cible" }); + } } // Create the post @@ -758,7 +763,23 @@ export async function registerRoutes(app: Express): Promise { const start = startDate ? new Date(startDate as string) : undefined; const end = endDate ? new Date(endDate as string) : undefined; - const scheduledPosts = await storage.getScheduledPosts(userId, start, end); + let scheduledPosts; + + if (user.role === 'admin') { + // Admin voit tous les posts programmés + const allUsers = await storage.getAllUsers(); + const allPostsPromises = allUsers.map(u => storage.getScheduledPosts(u.id, start, end)); + const allPostsArrays = await Promise.all(allPostsPromises); + scheduledPosts = allPostsArrays.flat(); + } else { + // User voit uniquement les posts des pages qui lui sont attribuées + const accessiblePages = await storage.getUserAccessiblePages(userId); + const accessiblePageIds = accessiblePages.map(p => p.id); + + const userScheduledPosts = await storage.getScheduledPosts(userId, start, end); + scheduledPosts = userScheduledPosts.filter(sp => accessiblePageIds.includes(sp.pageId)); + } + res.json(scheduledPosts); } catch (error) { console.error("Error fetching scheduled posts:", error); @@ -772,14 +793,19 @@ export async function registerRoutes(app: Express): Promise { const userId = user.id; const { id } = req.params; - // Verify the scheduled post belongs to the user before deleting + // Verify the scheduled post exists const scheduledPost = await storage.getScheduledPost(id); if (!scheduledPost) { return res.status(404).json({ error: "Scheduled post not found" }); } const post = await storage.getPost(scheduledPost.postId); - if (!post || post.userId !== userId) { + if (!post) { + return res.status(404).json({ error: "Post not found" }); + } + + // Admin peut tout supprimer, user peut supprimer uniquement ses propres posts + if (user.role !== 'admin' && post.userId !== userId) { return res.status(403).json({ error: "Unauthorized" }); } @@ -797,14 +823,19 @@ export async function registerRoutes(app: Express): Promise { const userId = user.id; const { id } = req.params; - // Verify the scheduled post belongs to the user before updating + // Verify the scheduled post exists const scheduledPost = await storage.getScheduledPost(id); if (!scheduledPost) { return res.status(404).json({ error: "Scheduled post not found" }); } const post = await storage.getPost(scheduledPost.postId); - if (!post || post.userId !== userId) { + if (!post) { + return res.status(404).json({ error: "Post not found" }); + } + + // Admin peut tout modifier, user peut modifier uniquement ses propres posts + if (user.role !== 'admin' && post.userId !== userId) { return res.status(403).json({ error: "Unauthorized" }); } @@ -814,6 +845,8 @@ export async function registerRoutes(app: Express): Promise { if (scheduledAt) { updateData.scheduledAt = new Date(scheduledAt); + // Synchroniser avec la table posts + await storage.updatePost(scheduledPost.postId, { scheduledFor: new Date(scheduledAt) }); } if (pageId) {