mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
Reels : programmer la publication d'une vidéo déjà montée
Nouvelle page « Programmer un Reel » (/reel/schedule) : envoi ou choix d'une vidéo, description, pages Facebook/TikTok, date et heure. Le Reel est publié tel quel par le planificateur, sans passer par le générateur. - POST /api/posts valide les Reels (une vidéo, pages Facebook/TikTok) - vignette extraite à l'envoi des vidéos pour l'historique Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017tFkVXQDATYfw5aFtjQahi
This commit is contained in:
5 files changed
+335
-4
No files matched your search
+27
-2
@@ -21,6 +21,7 @@ import { externalRouter } from "./routes/external";
|
||||
import { tiktokRouter } from "./routes/tiktok";
|
||||
import { facebookRouter } from "./routes/facebook";
|
||||
import { legalRouter } from "./routes/legal";
|
||||
import { createVideoThumbnail } from "./services/thumbnail";
|
||||
import { insertAudioTrackSchema } from "@shared/schema";
|
||||
import * as musicMetadata from "music-metadata";
|
||||
|
||||
@@ -721,13 +722,20 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
req.file.mimetype
|
||||
);
|
||||
|
||||
// Détection basique basée sur le mimetype ou l'extension
|
||||
const isVideo = req.file.mimetype.startsWith("video/") || /\.(mp4|mov|avi|mkv|webm)$/i.test(req.file.originalname);
|
||||
|
||||
// Vignette extraite tant que la vidéo est sur le disque : elle survit à la
|
||||
// suppression du fichier après publication (historique illustré)
|
||||
const thumbnailUrl = isVideo ? await createVideoThumbnail(req.file.path) : null;
|
||||
|
||||
// Clean up temp file immediately after upload to free disk space
|
||||
await fs.promises.unlink(req.file.path).catch(err => console.error("Failed to cleanup temp file:", err));
|
||||
|
||||
const mediaItem = await storage.createMedia({
|
||||
userId,
|
||||
// Détection basique basée sur le mimetype ou le résultat Cloudinary
|
||||
type: req.file.mimetype.startsWith("video/") || req.file.originalname.match(/\.(mp4|mov|avi|mkv)$/i) ? "video" : "image",
|
||||
type: isVideo ? "video" : "image",
|
||||
thumbnailUrl,
|
||||
cloudinaryPublicId: uploadResult.publicId,
|
||||
originalUrl: uploadResult.originalUrl,
|
||||
facebookFeedUrl: uploadResult.facebookFeedUrl,
|
||||
@@ -1297,6 +1305,23 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
return res.status(400).json({ error: "Les stories nécessitent au moins un média (image ou vidéo)" });
|
||||
}
|
||||
|
||||
// Reel déjà monté : une seule vidéo, publiée telle quelle sur Facebook ou TikTok
|
||||
if (postType === 'reel') {
|
||||
if (finalMediaItems.length !== 1) {
|
||||
return res.status(400).json({ error: "Un Reel nécessite exactement une vidéo" });
|
||||
}
|
||||
const reelMedia = await storage.getMediaById(finalMediaItems[0].mediaId);
|
||||
if (!reelMedia || reelMedia.type !== 'video' || (user.role !== 'admin' && reelMedia.userId !== userId)) {
|
||||
return res.status(400).json({ error: "Le média d'un Reel doit être une de vos vidéos" });
|
||||
}
|
||||
for (const pageId of Array.isArray(pageIds) ? pageIds : []) {
|
||||
const page = await storage.getSocialPage(pageId);
|
||||
if (!page || (page.platform !== 'facebook' && page.platform !== 'tiktok')) {
|
||||
return res.status(400).json({ error: "Les Reels ne peuvent être publiés que sur Facebook ou TikTok" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Security: Verify user has access to all specified pages (unless admin)
|
||||
if (user.role !== 'admin' && pageIds && Array.isArray(pageIds) && pageIds.length > 0) {
|
||||
const accessiblePages = await storage.getUserAccessiblePages(userId);
|
||||
|
||||
Reference in new issue
Block a user