feat: editor-v2 S1 — EDL schema, strict validation, /video/edl API, edl_projects store
apps/api/src/media-control/edl.ts: the EdlProject schema (closed enums, integer ms, 3dB steps, <=50 items, <=15 min, filtergraph-metacharacter blocklist on card text) + validateEdlProject — the mirror-assert owner for the worker's edl-plan port. 043_edl_projects: RLS tenant table. EdlController: POST /video/edl, PATCH autosave, GET (project + latest assemble job), POST :id/render (queues kind 'assemble' with the validated project). Tests 130 passing across media-control.
This commit is contained in:
parent
10dfda3b7c
commit
d8de9f5d82
6 changed files with 643 additions and 1 deletions
70
apps/api/src/media-control/edl.controller.ts
Normal file
70
apps/api/src/media-control/edl.controller.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { BadRequestException, Body, Controller, Get, Inject, Param, Patch, Post, Req, UseGuards } from "@nestjs/common";
|
||||
import type { Request } from "express";
|
||||
import { parseId, requireObject, requireSession } from "../http/validation.js";
|
||||
import { RbacGuard, RequirePermission, type AuthenticatedRequest } from "../rbac/rbac.guard.js";
|
||||
import { validateEdlProject } from "./edl.js";
|
||||
import { EdlService, type EdlLane, type EdlProjectRecord, type EdlProjectView } from "./edl.service.js";
|
||||
|
||||
/**
|
||||
* EDL projects (editor-v2 intake): the timeline is the resource, the render is
|
||||
* a queued assemble job. Bodies are parsed with validateEdlProject — closed
|
||||
* enums, integer ms, unknown keys rejected — before anything touches the DB.
|
||||
*/
|
||||
@Controller("video/edl")
|
||||
@UseGuards(RbacGuard)
|
||||
export class EdlController {
|
||||
constructor(@Inject(EdlService) private readonly edl: EdlService) {}
|
||||
|
||||
@Post()
|
||||
@RequirePermission("media.manage")
|
||||
create(
|
||||
@Req() request: Request & AuthenticatedRequest,
|
||||
@Body() body: unknown,
|
||||
): Promise<EdlProjectRecord> {
|
||||
const session = requireSession(request);
|
||||
const project = validateEdlProject(requireObject(body, "body").project);
|
||||
return this.edl.create(session.workspaceId, project, session.userId);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@RequirePermission("media.manage")
|
||||
update(
|
||||
@Req() request: Request & AuthenticatedRequest,
|
||||
@Param("id") id: string,
|
||||
@Body() body: unknown,
|
||||
): Promise<EdlProjectRecord> {
|
||||
const session = requireSession(request);
|
||||
const project = validateEdlProject(requireObject(body, "body").project);
|
||||
return this.edl.update(session.workspaceId, parseId(id, "id"), project, session.userId);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@RequirePermission("media.view")
|
||||
get(
|
||||
@Req() request: Request & AuthenticatedRequest,
|
||||
@Param("id") id: string,
|
||||
): Promise<EdlProjectView> {
|
||||
return this.edl.get(requireSession(request).workspaceId, parseId(id, "id"));
|
||||
}
|
||||
|
||||
@Post(":id/render")
|
||||
@RequirePermission("media.manage")
|
||||
render(
|
||||
@Req() request: Request & AuthenticatedRequest,
|
||||
@Param("id") id: string,
|
||||
@Body() body: unknown,
|
||||
): Promise<{ jobId: string }> {
|
||||
const session = requireSession(request);
|
||||
const input = requireObject(body, "body");
|
||||
const lane: EdlLane | undefined =
|
||||
input.lane === undefined || input.lane === null ? undefined : parseLane(input.lane);
|
||||
return this.edl.render(session.workspaceId, parseId(id, "id"), lane, session.userId);
|
||||
}
|
||||
}
|
||||
|
||||
function parseLane(value: unknown): EdlLane {
|
||||
if (value !== "local" && value !== "remote") {
|
||||
throw new BadRequestException("lane must be one of local, remote");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue