Add n8n community node, /api/v1/render alias, and job webhooks for OSS automation.

Payment-free self-hosted builds keep full API access with optional webhookUrl callbacks and the published n8n-nodes-songs2vid package source under integrations/n8n.
This commit is contained in:
Atakan Doğan Özban
2026-08-08 16:32:28 +02:00
parent 848607f9e0
commit f9b2a997a2
27 changed files with 7333 additions and 14 deletions
+37 -1
View File
@@ -9,8 +9,13 @@ import { getJobDir } from "../lib/storage";
import type { VideoJobData } from "../lib/types";
import { formatYouTubeErrorForUser } from "../lib/youtube/errors";
import { uploadToYouTube } from "../lib/youtube/upload";
import { deliverJobWebhook } from "../lib/webhooks";
async function updateJobStatus(jobId: string) {
const job = await prisma.job.findUniqueOrThrow({
where: { id: jobId },
select: { webhookUrl: true },
});
const items = await prisma.jobItem.findMany({ where: { jobId } });
const completed = items.filter((i) => i.status === JobItemStatus.COMPLETED).length;
const failed = items.filter((i) => i.status === JobItemStatus.FAILED).length;
@@ -23,13 +28,29 @@ async function updateJobStatus(jobId: string) {
else status = JobStatus.PARTIAL;
}
const completedAt = completed + failed === total ? new Date() : null;
await prisma.job.update({
where: { id: jobId },
data: {
status,
completedAt: completed + failed === total ? new Date() : null,
completedAt,
},
});
if (completedAt) {
const event =
status === JobStatus.COMPLETED
? "job.completed"
: status === JobStatus.FAILED
? "job.failed"
: "job.partial";
await deliverJobWebhook(job.webhookUrl, {
event,
jobId,
status,
completedAt: completedAt.toISOString(),
});
}
}
async function processJobItem(data: VideoJobData) {
@@ -136,6 +157,14 @@ async function processJobItem(data: VideoJobData) {
data: { createdVideoCount: { increment: 1 } },
});
await deliverJobWebhook(item.job.webhookUrl, {
event: "job.item.completed",
jobId: data.jobId,
status: "COMPLETED",
itemId: item.id,
youtubeVideoId,
});
await cleanupFiles([outputPath]);
} catch (err) {
const message = formatYouTubeErrorForUser(err);
@@ -144,6 +173,13 @@ async function processJobItem(data: VideoJobData) {
where: { id: item.id },
data: { status: JobItemStatus.FAILED, error: message },
});
await deliverJobWebhook(item.job.webhookUrl, {
event: "job.item.failed",
jobId: data.jobId,
status: "FAILED",
itemId: item.id,
error: message,
});
throw new Error(message);
} finally {
await updateJobStatus(data.jobId);