}
});
/**
* Protected API route to delete a selection and all related data
*
* @route DELETE /api/files/selections/:selectionId
* @authentication Required
* @param {string} selectionId - Selection ID from URL parameter
* @returns {Object} Response confirming deletion
* @error 400 - If selectionId is invalid
* @error 403 - If user doesn't have EDIT permission for the dataset
* @error 404 - If selection not found
* @error 500 - If database operation fails
* @description Deletes a selection and all related labels, label subtypes, and metadata
* This performs a cascading delete to maintain data integrity:
* 1. Deletes all label subtypes for this selection
* 2. Deletes all labels for this selection
* 3. Deletes selection metadata
* 4. Deletes the selection itself
*/
files.delete("/selections/:selectionId", authenticate, async (c) => {
try {
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub;
const selectionId = c.req.param("selectionId");
// Validate selection ID format
if (!isValidFileId(selectionId)) {
return c.json({
error: "Invalid selection ID format"
}, 400);
}
// Connect to database
const db = createDatabase(c.env);
// Get selection and dataset info for permission check
const selectionResult = await db
.select({
id: selection.id,
datasetId: selection.datasetId,
active: selection.active
})
.from(selection)
.where(eq(selection.id, selectionId))
.limit(1);
if (selectionResult.length === 0) {
return c.json({
error: "Selection not found"
}, 404);
}
const selectionRecord = selectionResult[0];
if (!selectionRecord.active) {
return c.json({
error: "Selection is not active"
}, 404);
}
// Check if user has EDIT permission for this dataset
const hasPermission = await checkUserPermission(db, userId, selectionRecord.datasetId, 'EDIT');
if (!hasPermission) {
return c.json({
error: "Access denied: No EDIT permission for this dataset"
}, 403);
}
// Perform cascading delete in correct order (foreign key constraints)
// 1. Delete label subtypes (references labels)
const deletedSubtypes = await db
.delete(labelSubtype)
.where(
inArray(
labelSubtype.labelId,
db.select({ id: label.id })
.from(label)
.where(eq(label.selectionId, selectionId))
)
);
// 2. Delete labels (references selection)
const deletedLabels = await db
.delete(label)
.where(eq(label.selectionId, selectionId));
// 3. Delete selection metadata (references selection)
const deletedMetadata = await db
.delete(selectionMetadata)
.where(eq(selectionMetadata.selectionId, selectionId));
// 4. Delete the selection itself
const deletedSelection = await db
.delete(selection)
.where(eq(selection.id, selectionId));
return c.json({
data: {
selectionId,
deleted: true,
cascadeDeletes: {
subtypes: deletedSubtypes.rowCount || 0,
labels: deletedLabels.rowCount || 0,
metadata: deletedMetadata.rowCount || 0,
selection: deletedSelection.rowCount || 0
}
}
});
} catch (error) {
return c.json(standardErrorResponse(error, "deleting selection"), 500);