UCDTBEK3CF6YT2H6V57HI6FAFW44BIYYAK3Z2QJ5LJE7QWX7OEYAC
details: error instanceof Error ? error.message : String(error),
},
500
);
}
});
/**
* Protected API route to update an existing dataset
*
* @route PUT /api/datasets/:id
* @authentication Required
* @param {string} id - Dataset ID in URL path
* @body {Object} Dataset update payload:
* - name?: string (optional, max 255 chars)
* - description?: string (optional, max 255 chars)
* - public?: boolean (optional)
* - type?: string (optional)
* - active?: boolean (optional, for soft delete)
* @returns {Object} Response containing:
* - data: The updated dataset object
* @error 400 - If fields are invalid or dataset not found
* @error 403 - If user doesn't own the dataset
* @error 500 - If database operation fails
* @description Updates an existing dataset owned by the authenticated user
* Only the dataset owner can modify it
*/
app.put("/api/datasets/:id", authenticate, async (c) => {
try {
// Get the JWT payload (user info)
const jwtPayload = (c as unknown as { jwtPayload: JWTPayload }).jwtPayload;
const userId = jwtPayload.sub; // User ID from JWT
// Get dataset ID from URL parameters
const datasetId = c.req.param("id");
if (!datasetId) {
return c.json({
error: "Missing dataset ID in URL"
}, 400);
}
// Parse request body
const body = await c.req.json();
const { name, description, public: isPublic, type, active } = body;
// Connect to the database
const sql = neon(c.env.DATABASE_URL);
const db = drizzle(sql);
// First, check if the dataset exists and if the user owns it
const existingDataset = await db
.select({
id: dataset.id,
owner: dataset.owner,
active: dataset.active
})
.from(dataset)
.where(eq(dataset.id, datasetId))
.limit(1);
if (existingDataset.length === 0) {
return c.json({
error: "Dataset not found"
}, 404);
}
// Check ownership
if (existingDataset[0].owner !== userId) {
return c.json({
error: "You don't have permission to modify this dataset"
}, 403);
}
// Validate fields if provided
if (name !== undefined) {
if (typeof name !== 'string' || name.trim().length === 0) {
return c.json({
error: "Invalid field: name must be a non-empty string"
}, 400);
}
if (name.length > 255) {
return c.json({
error: "Field 'name' must be 255 characters or less"
}, 400);
}
}
if (description !== undefined && description !== null && description.length > 255) {
return c.json({
error: "Field 'description' must be 255 characters or less"
}, 400);
}
if (type !== undefined) {
const validTypes = ['organise', 'test', 'train'];
if (!validTypes.includes(type)) {
return c.json({
error: `Field 'type' must be one of: ${validTypes.join(', ')}`
}, 400);
}
}
// Build update object with only provided fields
const updateData: Record<string, unknown> = {
lastModified: new Date(),
modifiedBy: userId,
};
if (name !== undefined) {
updateData.name = name.trim();
}
if (description !== undefined) {
updateData.description = description?.trim() || null;
}
if (isPublic !== undefined) {
updateData.public = Boolean(isPublic);
}
if (type !== undefined) {
updateData.type = type;
}
if (active !== undefined) {
updateData.active = Boolean(active);
}
// Update the dataset
const result = await db
.update(dataset)
.set(updateData)
.where(eq(dataset.id, datasetId))
.returning({
id: dataset.id,
name: dataset.name,
description: dataset.description,
public: dataset.public,
type: dataset.type,
createdAt: dataset.createdAt,
lastModified: dataset.lastModified,
owner: dataset.owner,
active: dataset.active,
});
if (result.length === 0) {
return c.json({
error: "Failed to update dataset"
}, 500);
}
console.log("Updated dataset:", result[0].id, "for user:", userId);
return c.json({
data: result[0]
});
} catch (error) {
console.error("Error updating dataset:", error);
return c.json(
{
error: "Failed to update dataset",
const [showCreateForm, setShowCreateForm] = useState<boolean>(false);
const [createLoading, setCreateLoading] = useState<boolean>(false);
const [createError, setCreateError] = useState<string | null>(null);
const [showForm, setShowForm] = useState<boolean>(false);
const [formLoading, setFormLoading] = useState<boolean>(false);
const [formError, setFormError] = useState<string | null>(null);
const [editingDataset, setEditingDataset] = useState<Dataset | null>(null);
const [isEditMode, setIsEditMode] = useState<boolean>(false);
const response = await fetch("/api/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
id,
name: formData.name,
description: formData.description || undefined,
type: formData.type,
public: formData.public,
}),
});
if (isEditMode && editingDataset) {
// Update existing dataset
const response = await fetch(`/api/datasets/${editingDataset.id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: formData.name,
description: formData.description || undefined,
type: formData.type,
public: formData.public,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);
}
const { data: updatedDataset } = await response.json();
// Update the dataset in the list
setDatasets(prev => prev.map(dataset =>
dataset.id === updatedDataset.id ? updatedDataset : dataset
));
} else {
// Create new dataset
const id = nanoid(12);
const response = await fetch("/api/datasets", {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
id,
name: formData.name,
description: formData.description || undefined,
type: formData.type,
public: formData.public,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);
}
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);
const { data: newDataset } = await response.json();
// Add the new dataset to the list
setDatasets(prev => [newDataset, ...prev]);
setCreateError(err instanceof Error ? err.message : "Failed to create dataset");
console.error("Error creating dataset:", err);
setFormError(err instanceof Error ? err.message : `Failed to ${isEditMode ? 'update' : 'create'} dataset`);
console.error(`Error ${isEditMode ? 'updating' : 'creating'} dataset:`, err);
const handleCancelCreate = () => {
setShowCreateForm(false);
setCreateError(null);
const handleCancelForm = () => {
setShowForm(false);
setFormError(null);
setIsEditMode(false);
setEditingDataset(null);
setFormData({
name: '',
description: '',
type: 'organise',
public: false
});
};
const handleCreateNew = () => {
setIsEditMode(false);
setEditingDataset(null);
const handleEditDataset = (dataset: Dataset) => {
setIsEditMode(true);
setEditingDataset(dataset);
setFormData({
name: dataset.name,
description: dataset.description || '',
type: dataset.type as 'organise' | 'test' | 'train',
public: dataset.public
});
setShowForm(true);
};
const handleDeleteDataset = async (dataset: Dataset) => {
if (!isAuthenticated) return;
const confirmDelete = window.confirm(`Are you sure you want to delete "${dataset.name}"? This action cannot be undone.`);
if (!confirmDelete) return;
try {
const accessToken = await getAccessToken();
const response = await fetch(`/api/datasets/${dataset.id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
active: false
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! Status: ${response.status}`);
}
// Remove the dataset from the list (soft delete)
setDatasets(prev => prev.filter(d => d.id !== dataset.id));
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete dataset");
console.error("Error deleting dataset:", err);
}
};
<TableCell className="font-medium whitespace-normal break-words">{dataset.name}</TableCell>
<TableCell className="text-center">
<TableCell
className="font-medium whitespace-normal break-words cursor-pointer"
onClick={() => onDatasetSelect(dataset.id, dataset.name)}
>
{dataset.name}
</TableCell>
<TableCell
className="text-center cursor-pointer"
onClick={() => onDatasetSelect(dataset.id, dataset.name)}
>
<TableCell className="whitespace-normal break-words">{dataset.description || "—"}</TableCell>
<TableCell className="text-center">
<div className="flex justify-center gap-2">
<Button
onClick={(e) => {
e.stopPropagation();
handleEditDataset(dataset);
}}
variant="ghost"
size="sm"
className="p-1 h-8 w-8"
title="Edit dataset"
>
<Edit className="h-4 w-4" />
</Button>
<Button
onClick={(e) => {
e.stopPropagation();
handleDeleteDataset(dataset);
}}
variant="ghost"
size="sm"
className="p-1 h-8 w-8 text-red-600 hover:text-red-700 hover:bg-red-50"
title="Delete dataset"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>