feat: daily timeline advanced UI and calendar integration

This commit is contained in:
2026-06-03 04:08:13 -05:00
parent e944594e06
commit ad8622e243
34 changed files with 2088 additions and 788 deletions
+56 -1
View File
@@ -54,6 +54,59 @@ export async function createExpressApp() {
// Add JSON parser with generous limit for render payloads (timelineElements can be large)
app.use(express.json({ limit: '50mb' }));
// ═══ Dynamic Workspace Serving ═══
let activeWorkspacePath = "";
app.post("/api/config/workspace", express.json(), (req, res) => {
activeWorkspacePath = req.body.path;
res.json({ success: true, path: activeWorkspacePath });
});
app.use("/workspace", (req, res, next) => {
if (activeWorkspacePath) {
const reqPath = decodeURIComponent(req.path);
const fullPath = path.join(activeWorkspacePath, reqPath);
// Validate path traversal
if (!fullPath.startsWith(activeWorkspacePath)) {
return res.status(403).send("Forbidden");
}
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
return res.sendFile(fullPath);
}
}
next();
});
// Upload an asset directly to a brand's workspace folder
app.post("/api/upload/brand", mediaUpload.single("file"), (req, res) => {
try {
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
const { brandId, workspacePath } = req.body;
if (!brandId || !workspacePath) {
return res.status(400).json({ error: "brandId and workspacePath required" });
}
const brandDir = path.join(workspacePath, brandId, "brand");
if (!fs.existsSync(brandDir)) {
fs.mkdirSync(brandDir, { recursive: true });
}
// Move file from UPLOADS_DIR to brandDir
const ext = path.extname(req.file.originalname) || '';
const finalFilename = `${crypto.randomUUID()}${ext}`;
const finalPath = path.join(brandDir, finalFilename);
fs.renameSync(req.file.path, finalPath);
// Return the public URL that routes through our dynamic /workspace middleware
const publicUrl = `http://localhost:3000/workspace/${brandId}/brand/${finalFilename}`;
res.json({ url: publicUrl, path: finalPath });
} catch (error) {
console.error("Brand upload error:", error);
res.status(500).json({ error: "Upload failed" });
}
});
// ═══ Serve uploaded media files ═══
app.use("/api/media", express.static(UPLOADS_DIR, {
maxAge: "1d",
@@ -212,7 +265,7 @@ export async function createExpressApp() {
// Start a render job
app.post("/api/render/start", async (req, res) => {
try {
const { format, width, height, fps, durationInFrames, compositionId, inputProps } = req.body;
const { format, width, height, fps, durationInFrames, compositionId, inputProps, targetPath, brandId } = req.body;
if (!format || !width || !height || !compositionId) {
return res.status(400).json({ error: "Missing required fields: format, width, height, compositionId" });
@@ -227,6 +280,8 @@ export async function createExpressApp() {
durationInFrames: durationInFrames || 150,
compositionId,
inputProps: inputProps || {},
targetPath,
brandId
});
console.log(`🎬 Job created: ${job.id} (${format} ${width}x${height})`);