Add named project snapshots

This commit is contained in:
2026-07-25 22:15:22 +02:00
parent 5a0c9019af
commit 7c670fece3
21 changed files with 3076 additions and 7 deletions
@@ -0,0 +1,5 @@
import { db } from "../../db/client.js";
import { ProjectSnapshotRepository } from "../../db/repositories/project-snapshot.repository.js";
export const projectSnapshotStore =
new ProjectSnapshotRepository(db);
@@ -0,0 +1,69 @@
import type { NextFunction, Request, Response } from "express";
import { ProjectRevisionConflictError } from "../../domain/errors/project-revision-conflict.error.js";
import { ProjectSnapshotNameConflictError } from "../../domain/errors/project-snapshot-name-conflict.error.js";
import { createNamedProjectSnapshotSchema } from "../../shared/validation/project-snapshot.schemas.js";
import { projectSnapshotStore } from "../composition/project-snapshot-store.js";
export function listProjectSnapshots(req: Request, res: Response) {
const projectId = getProjectId(req, res);
if (!projectId) {
return;
}
const snapshots = projectSnapshotStore.listByProject(projectId);
if (!snapshots) {
return res.status(404).json({ error: "Project not found" });
}
return res.json(snapshots);
}
export function createNamedProjectSnapshot(
req: Request,
res: Response,
next: NextFunction
) {
const projectId = getProjectId(req, res);
if (!projectId) {
return;
}
const parsed = createNamedProjectSnapshotSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: parsed.error.flatten() });
}
try {
const snapshot = projectSnapshotStore.createNamed({
projectId,
...parsed.data,
});
if (!snapshot) {
return res.status(404).json({ error: "Project not found" });
}
return res.status(201).json(snapshot);
} catch (error) {
if (error instanceof ProjectRevisionConflictError) {
return res.status(409).json({
error: error.message,
code: "PROJECT_REVISION_CONFLICT",
expectedRevision: error.expectedRevision,
currentRevision: error.actualRevision,
});
}
if (error instanceof ProjectSnapshotNameConflictError) {
return res.status(409).json({
error: error.message,
code: "PROJECT_SNAPSHOT_NAME_CONFLICT",
snapshotName: error.snapshotName,
});
}
return next(error);
}
}
function getProjectId(req: Request, res: Response) {
const { projectId } = req.params;
if (typeof projectId !== "string" || !projectId.trim()) {
res.status(400).json({ error: "Invalid projectId" });
return null;
}
return projectId;
}
+6
View File
@@ -22,6 +22,10 @@ import {
redoProjectCommand,
undoProjectCommand,
} from "../controllers/project-command.controller.js";
import {
createNamedProjectSnapshot,
listProjectSnapshots,
} from "../controllers/project-snapshot.controller.js";
export const projectRouter = Router();
@@ -33,6 +37,8 @@ projectRouter.get("/:projectId/history/revisions", listProjectRevisions);
projectRouter.post("/:projectId/commands", executeProjectCommand);
projectRouter.post("/:projectId/history/undo", undoProjectCommand);
projectRouter.post("/:projectId/history/redo", redoProjectCommand);
projectRouter.get("/:projectId/snapshots", listProjectSnapshots);
projectRouter.post("/:projectId/snapshots", createNamedProjectSnapshot);
projectRouter.put("/:projectId", updateProjectSettings);
projectRouter.get("/:projectId/distribution-boards", listDistributionBoardsByProject);
projectRouter.post("/:projectId/distribution-boards", createDistributionBoard);