fix colliding drawing IDs
This commit is contained in:
@@ -334,7 +334,20 @@ export const registerDashboardRoutes = (
|
||||
}
|
||||
}
|
||||
|
||||
const updatedDrawing = await prisma.drawing.update({ where: { id }, data });
|
||||
const updateResult = await prisma.drawing.updateMany({
|
||||
where: { id, userId: req.user.id },
|
||||
data,
|
||||
});
|
||||
if (updateResult.count === 0) {
|
||||
return res.status(404).json({ error: "Drawing not found" });
|
||||
}
|
||||
|
||||
const updatedDrawing = await prisma.drawing.findFirst({
|
||||
where: { id, userId: req.user.id },
|
||||
});
|
||||
if (!updatedDrawing) {
|
||||
return res.status(404).json({ error: "Drawing not found" });
|
||||
}
|
||||
invalidateDrawingsCache();
|
||||
|
||||
return res.json({
|
||||
@@ -352,7 +365,12 @@ export const registerDashboardRoutes = (
|
||||
const drawing = await prisma.drawing.findFirst({ where: { id, userId: req.user.id } });
|
||||
if (!drawing) return res.status(404).json({ error: "Drawing not found" });
|
||||
|
||||
await prisma.drawing.delete({ where: { id } });
|
||||
const deleteResult = await prisma.drawing.deleteMany({
|
||||
where: { id, userId: req.user.id },
|
||||
});
|
||||
if (deleteResult.count === 0) {
|
||||
return res.status(404).json({ error: "Drawing not found" });
|
||||
}
|
||||
invalidateDrawingsCache();
|
||||
|
||||
if (config.enableAuditLogging) {
|
||||
@@ -375,6 +393,9 @@ export const registerDashboardRoutes = (
|
||||
const { id } = req.params;
|
||||
const original = await prisma.drawing.findFirst({ where: { id, userId: req.user.id } });
|
||||
if (!original) return res.status(404).json({ error: "Original drawing not found" });
|
||||
if (original.collectionId === "trash") {
|
||||
await ensureTrashCollection(prisma, req.user.id);
|
||||
}
|
||||
|
||||
const newDrawing = await prisma.drawing.create({
|
||||
data: {
|
||||
@@ -443,10 +464,19 @@ export const registerDashboardRoutes = (
|
||||
}
|
||||
|
||||
const sanitizedName = sanitizeText(parsed.data, 100);
|
||||
const updatedCollection = await prisma.collection.update({
|
||||
where: { id },
|
||||
const updateResult = await prisma.collection.updateMany({
|
||||
where: { id, userId: req.user.id },
|
||||
data: { name: sanitizedName },
|
||||
});
|
||||
if (updateResult.count === 0) {
|
||||
return res.status(404).json({ error: "Collection not found" });
|
||||
}
|
||||
const updatedCollection = await prisma.collection.findFirst({
|
||||
where: { id, userId: req.user.id },
|
||||
});
|
||||
if (!updatedCollection) {
|
||||
return res.status(404).json({ error: "Collection not found" });
|
||||
}
|
||||
return res.json(updatedCollection);
|
||||
}));
|
||||
|
||||
@@ -464,7 +494,7 @@ export const registerDashboardRoutes = (
|
||||
where: { collectionId: id, userId: req.user.id },
|
||||
data: { collectionId: null },
|
||||
}),
|
||||
prisma.collection.delete({ where: { id } }),
|
||||
prisma.collection.deleteMany({ where: { id, userId: req.user.id } }),
|
||||
]);
|
||||
invalidateDrawingsCache();
|
||||
|
||||
|
||||
@@ -131,6 +131,21 @@ const makeUniqueName = (base: string, used: Set<string>): string => {
|
||||
return candidate;
|
||||
};
|
||||
|
||||
const findFirstDuplicate = (values: string[]): string | null => {
|
||||
const seen = new Set<string>();
|
||||
for (const value of values) {
|
||||
if (seen.has(value)) return value;
|
||||
seen.add(value);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const normalizeNonEmptyId = (value: unknown): string | null => {
|
||||
if (typeof value !== "string") return null;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
};
|
||||
|
||||
const findSqliteTable = (tables: string[], candidates: string[]): string | null => {
|
||||
const byLower = new Map(tables.map((t) => [t.toLowerCase(), t]));
|
||||
for (const candidate of candidates) {
|
||||
@@ -439,6 +454,28 @@ Drawings: ${drawings.length}
|
||||
message: `Too many drawings (max ${MAX_IMPORT_DRAWINGS})`,
|
||||
});
|
||||
}
|
||||
|
||||
const duplicateCollectionId = findFirstDuplicate(manifest.collections.map((c) => c.id));
|
||||
if (duplicateCollectionId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate collection id in manifest: ${duplicateCollectionId}`,
|
||||
});
|
||||
}
|
||||
const duplicateDrawingId = findFirstDuplicate(manifest.drawings.map((d) => d.id));
|
||||
if (duplicateDrawingId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate drawing id in manifest: ${duplicateDrawingId}`,
|
||||
});
|
||||
}
|
||||
const duplicateDrawingPath = findFirstDuplicate(manifest.drawings.map((d) => d.filePath));
|
||||
if (duplicateDrawingPath) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate drawing file path in manifest: ${duplicateDrawingPath}`,
|
||||
});
|
||||
}
|
||||
for (const drawing of manifest.drawings) {
|
||||
if (!getSafeZipEntry(zip, drawing.filePath)) {
|
||||
return res.status(400).json({
|
||||
@@ -532,6 +569,28 @@ Drawings: ${drawings.length}
|
||||
});
|
||||
}
|
||||
|
||||
const duplicateCollectionId = findFirstDuplicate(manifest.collections.map((c) => c.id));
|
||||
if (duplicateCollectionId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate collection id in manifest: ${duplicateCollectionId}`,
|
||||
});
|
||||
}
|
||||
const duplicateDrawingId = findFirstDuplicate(manifest.drawings.map((d) => d.id));
|
||||
if (duplicateDrawingId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate drawing id in manifest: ${duplicateDrawingId}`,
|
||||
});
|
||||
}
|
||||
const duplicateDrawingPath = findFirstDuplicate(manifest.drawings.map((d) => d.filePath));
|
||||
if (duplicateDrawingPath) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid backup manifest",
|
||||
message: `Duplicate drawing file path in manifest: ${duplicateDrawingPath}`,
|
||||
});
|
||||
}
|
||||
|
||||
type PreparedImportDrawing = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -772,6 +831,31 @@ Drawings: ${drawings.length}
|
||||
});
|
||||
}
|
||||
|
||||
const duplicateDrawingIdRow = db
|
||||
.prepare(
|
||||
`SELECT id FROM "${drawingTable}" WHERE id IS NOT NULL GROUP BY id HAVING COUNT(1) > 1 LIMIT 1`
|
||||
)
|
||||
.get();
|
||||
if (duplicateDrawingIdRow?.id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid legacy DB",
|
||||
message: `Duplicate drawing id in legacy DB: ${String(duplicateDrawingIdRow.id)}`,
|
||||
});
|
||||
}
|
||||
if (collectionTable) {
|
||||
const duplicateCollectionIdRow = db
|
||||
.prepare(
|
||||
`SELECT id FROM "${collectionTable}" WHERE id IS NOT NULL GROUP BY id HAVING COUNT(1) > 1 LIMIT 1`
|
||||
)
|
||||
.get();
|
||||
if (duplicateCollectionIdRow?.id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid legacy DB",
|
||||
message: `Duplicate collection id in legacy DB: ${String(duplicateCollectionIdRow.id)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let latestMigration: string | null = null;
|
||||
const migrationsTable = findSqliteTable(tables, ["_prisma_migrations"]);
|
||||
if (migrationsTable) {
|
||||
@@ -862,6 +946,28 @@ Drawings: ${drawings.length}
|
||||
});
|
||||
}
|
||||
|
||||
const importedCollectionIds = importedCollections
|
||||
.map((c) => normalizeNonEmptyId(c?.id))
|
||||
.filter((id): id is string => id !== null);
|
||||
const duplicateCollectionId = findFirstDuplicate(importedCollectionIds);
|
||||
if (duplicateCollectionId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid legacy DB",
|
||||
message: `Duplicate collection id in legacy DB: ${duplicateCollectionId}`,
|
||||
});
|
||||
}
|
||||
|
||||
const importedDrawingIds = importedDrawings
|
||||
.map((d) => normalizeNonEmptyId(d?.id))
|
||||
.filter((id): id is string => id !== null);
|
||||
const duplicateDrawingId = findFirstDuplicate(importedDrawingIds);
|
||||
if (duplicateDrawingId) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid legacy DB",
|
||||
message: `Duplicate drawing id in legacy DB: ${duplicateDrawingId}`,
|
||||
});
|
||||
}
|
||||
|
||||
type PreparedLegacyDrawing = {
|
||||
importedId: string | null;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user