JHFIJJSLVMQNYIDE6CXWUK5UTB7BTUSY7NCI33WKCWIRZHCSMBHQC
* Protected API route to create a new dataset
*
* @route POST /api/datasets
* @authentication Required
* @body {Object} Dataset creation payload:
* - id: string (nanoid(12) - user generated)
* - name: string (required, max 255 chars)
* - description?: string (optional, max 255 chars)
* - public?: boolean (optional, defaults to false)
* - type?: string (optional, defaults to 'organise')
* @returns {Object} Response containing:
* - data: The created dataset object
* @error 400 - If required fields are missing or invalid
* @error 500 - If database operation fails
* @description Creates a new dataset for the authenticated user
* The user becomes the owner, creator, and modifier of the dataset
*/
app.post("/api/datasets", 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
// Parse request body
const body = await c.req.json();
const { id, name, description, public: isPublic, type } = body;
// Validate required fields
if (!id || typeof id !== 'string') {
return c.json({
error: "Missing or invalid required field: id"
}, 400);
}
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return c.json({
error: "Missing or invalid required field: name"
}, 400);
}
// Validate field lengths
if (id.length !== 12) {
return c.json({
error: "Field 'id' must be exactly 12 characters (nanoid)"
}, 400);
}
if (name.length > 255) {
return c.json({
error: "Field 'name' must be 255 characters or less"
}, 400);
}
if (description && description.length > 255) {
return c.json({
error: "Field 'description' must be 255 characters or less"
}, 400);
}
// Validate type if provided
const validTypes = ['organise', 'test', 'train'];
const datasetType = type || 'organise';
if (!validTypes.includes(datasetType)) {
return c.json({
error: `Field 'type' must be one of: ${validTypes.join(', ')}`
}, 400);
}
// Connect to the database
const sql = neon(c.env.DATABASE_URL);
const db = drizzle(sql);
// Create the dataset
const now = new Date();
const newDataset = {
id: id.trim(),
name: name.trim(),
description: description?.trim() || null,
public: Boolean(isPublic),
type: datasetType,
createdBy: userId,
createdAt: now,
lastModified: now,
modifiedBy: userId,
owner: userId,
active: true,
};
// Insert the dataset
const result = await db.insert(dataset).values(newDataset).returning({
id: dataset.id,
name: dataset.name,
description: dataset.description,
public: dataset.public,
type: dataset.type,
createdAt: dataset.createdAt,
owner: dataset.owner,
});
console.log("Created dataset:", result[0].id, "for user:", userId);
return c.json({
data: result[0]
}, 201);
} catch (error) {
console.error("Error creating dataset:", error);
// Handle unique constraint violations
if (error instanceof Error && error.message.includes('duplicate key')) {
return c.json({
error: "A dataset with this ID already exists"
}, 400);
}
return c.json(
{
error: "Failed to create dataset",
details: error instanceof Error ? error.message : String(error),
},
500
);
}
});
/**
const [showCreateForm, setShowCreateForm] = useState<boolean>(false);
const [createLoading, setCreateLoading] = useState<boolean>(false);
const [createError, setCreateError] = useState<string | null>(null);
// Form state
const [formData, setFormData] = useState({
name: '',
description: '',
type: 'organise' as 'organise' | 'test' | 'train',
public: false
});
const handleCreateDataset = async (e: React.FormEvent) => {
e.preventDefault();
if (!isAuthenticated) return;
setCreateLoading(true);
setCreateError(null);
try {
const accessToken = await getAccessToken();
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}`);
}
const { data: newDataset } = await response.json();
// Add the new dataset to the list
setDatasets(prev => [newDataset, ...prev]);
// Reset form and close modal
setFormData({
name: '',
description: '',
type: 'organise',
public: false
});
setShowCreateForm(false);
} catch (err) {
setCreateError(err instanceof Error ? err.message : "Failed to create dataset");
console.error("Error creating dataset:", err);
} finally {
setCreateLoading(false);
}
};
const handleFormChange = (field: keyof typeof formData, value: string | boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleCancelCreate = () => {
setShowCreateForm(false);
setCreateError(null);
setFormData({
name: '',
description: '',
type: 'organise',
public: false
});
};
{/* Header with Add Button */}
{isAuthenticated && !authLoading && (
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold">Datasets</h2>
<Button
onClick={() => setShowCreateForm(true)}
variant="default"
size="sm"
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
Add Dataset
</Button>
</div>
)}
{/* Create Dataset Modal */}
{showCreateForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md mx-4">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">Create New Dataset</h3>
<Button
onClick={handleCancelCreate}
variant="ghost"
size="sm"
className="p-1"
>
<X className="h-4 w-4" />
</Button>
</div>
{createError && (
<div className="bg-red-50 border border-red-200 text-red-700 px-3 py-2 rounded mb-4">
{createError}
</div>
)}
<form onSubmit={handleCreateDataset} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
Name *
</label>
<input
type="text"
id="name"
required
maxLength={255}
value={formData.name}
onChange={(e) => handleFormChange('name', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter dataset name"
/>
</div>
<div>
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
id="description"
maxLength={255}
value={formData.description}
onChange={(e) => handleFormChange('description', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter description (optional)"
rows={3}
/>
</div>
<div>
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
Type
</label>
<select
id="type"
value={formData.type}
onChange={(e) => handleFormChange('type', e.target.value as 'organise' | 'test' | 'train')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="organise">Organise</option>
<option value="test">Test</option>
<option value="train">Train</option>
</select>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="public"
checked={formData.public}
onChange={(e) => handleFormChange('public', e.target.checked)}
className="mr-2"
/>
<label htmlFor="public" className="text-sm font-medium text-gray-700">
Make this dataset public
</label>
</div>
<div className="flex justify-end space-x-3 pt-4">
<Button
type="button"
onClick={handleCancelCreate}
variant="outline"
disabled={createLoading}
>
Cancel
</Button>
<Button
type="submit"
disabled={createLoading || !formData.name.trim()}
>
{createLoading ? "Creating..." : "Create Dataset"}
</Button>
</div>
</form>
</div>
</div>
)}