From dc2f68a2b40af0bcd07361706bc6893dd13442b0 Mon Sep 17 00:00:00 2001 From: Shaheed Azaad <4594-shaheedazaad@users.noreply.gitlab.pavlovia.org> Date: Wed, 16 Jul 2025 12:30:04 +0200 Subject: [PATCH] fixed delete --- src/lib/server/s3.ts | 26 ++++++++++- .../api/experiment/[id]/files/+server.ts | 32 ++++++++++---- src/routes/api/user-storage.ts | 10 +++++ src/routes/experiment/[id]/+page.svelte | 31 ++++++++++--- src/routes/settings/profile/+page.svelte | 43 +++++++++++++++++++ 5 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 src/routes/api/user-storage.ts diff --git a/src/lib/server/s3.ts b/src/lib/server/s3.ts index b2a8d8d..fed1054 100644 --- a/src/lib/server/s3.ts +++ b/src/lib/server/s3.ts @@ -2,8 +2,12 @@ import { S3Client } from '@aws-sdk/client-s3'; import { S3_ENDPOINT, S3_ACCESS_KEY, - S3_SECRET_KEY + S3_SECRET_KEY, + S3_BUCKET } from '$env/static/private'; +import { ListObjectsV2Command } from '@aws-sdk/client-s3'; +import type { ListObjectsV2CommandOutput } from '@aws-sdk/client-s3'; +import { HeadObjectCommand } from '@aws-sdk/client-s3'; const s3 = new S3Client({ region: 'us-east-1', // MinIO ignores region but AWS SDK requires it @@ -15,4 +19,24 @@ const s3 = new S3Client({ forcePathStyle: true // needed for MinIO }); +/** + * Returns the total storage used by a user (in bytes) by summing all S3 objects under their userId prefix. + */ +export async function getUserStorageUsage(userId: string): Promise { + const BUCKET = S3_BUCKET!; + try { + const result = await s3.send(new HeadObjectCommand({ + Bucket: BUCKET, + Key: userId, + })); + return result.ContentLength || 0; + } catch (err: any) { + // If the file does not exist, return 0 + if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) { + return 0; + } + throw err; + } +} + export default s3; \ No newline at end of file diff --git a/src/routes/api/experiment/[id]/files/+server.ts b/src/routes/api/experiment/[id]/files/+server.ts index 2869bbc..3971b19 100644 --- a/src/routes/api/experiment/[id]/files/+server.ts +++ b/src/routes/api/experiment/[id]/files/+server.ts @@ -14,9 +14,14 @@ const s3 = new S3Client({ }); const BUCKET = S3_BUCKET!; -export async function GET({ params }) { +export async function GET({ params, locals, url }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const { id } = params; - const prefix = `experiments/${id}/`; + const userId = locals.user.id; + const type = url?.searchParams?.get('type') || 'experiment_files'; + const prefix = `${userId}/${id}/${type}/`; const result = await s3.send(new ListObjectsV2Command({ Bucket: BUCKET, Prefix: prefix })); const files = (result.Contents || []).map(obj => ({ key: obj.Key, @@ -27,15 +32,18 @@ export async function GET({ params }) { return json({ files }); } -export async function POST({ params, request }) { - console.log(params); +export async function POST({ params, request, locals, url }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const { id } = params; + const userId = locals.user.id; + const type = url?.searchParams?.get('type') || 'experiment_files'; const data = await request.formData(); const file = data.get('file'); const relativePath = data.get('relativePath'); if (!file || typeof file === 'string') throw error(400, 'No file uploaded'); - const key = `experiments/${id}/${relativePath}`; - console.log(key); + const key = `${userId}/${id}/${type}/${relativePath}`; await s3.send(new PutObjectCommand({ Bucket: BUCKET, Key: key, @@ -45,13 +53,18 @@ export async function POST({ params, request }) { return json({ success: true, key }); } -export async function DELETE({ params, url }) { +export async function DELETE({ params, url, locals }) { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } const { id } = params; + const userId = locals.user.id; + const type = url?.searchParams?.get('type') || 'experiment_files'; const key = url.searchParams.get('key'); const prefix = url.searchParams.get('prefix'); if (prefix) { // Delete all objects with this prefix - const fullPrefix = `experiments/${id}/${prefix}`; + const fullPrefix = `${userId}/${id}/${type}/${prefix}`; const result = await s3.send(new ListObjectsV2Command({ Bucket: BUCKET, Prefix: fullPrefix })); const objects = (result.Contents || []).map(obj => ({ Key: obj.Key })); if (objects.length > 0) { @@ -65,6 +78,7 @@ export async function DELETE({ params, url }) { return json({ success: true, deleted: objects.length }); } if (!key) throw error(400, 'Missing key or prefix'); - await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key })); + const fullKey = `${userId}/${id}/${type}/${key}`; + await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: fullKey })); return json({ success: true }); } \ No newline at end of file diff --git a/src/routes/api/user-storage.ts b/src/routes/api/user-storage.ts new file mode 100644 index 0000000..1b027d0 --- /dev/null +++ b/src/routes/api/user-storage.ts @@ -0,0 +1,10 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { getUserStorageUsage } from '$lib/server/s3'; + +export const GET: RequestHandler = async ({ locals }) => { + if (!locals.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + const usage = await getUserStorageUsage(locals.user.id); + return json({ usage }); +}; \ No newline at end of file diff --git a/src/routes/experiment/[id]/+page.svelte b/src/routes/experiment/[id]/+page.svelte index 2e59743..71e5eb5 100644 --- a/src/routes/experiment/[id]/+page.svelte +++ b/src/routes/experiment/[id]/+page.svelte @@ -67,7 +67,19 @@ async function uploadFiles(fileList: FileList) { } async function deleteFile(key: string) { - const url = `/api/experiment/${experimentId}/files?key=${encodeURIComponent(key)}`; + // If key contains experiment_files/, strip everything before and including it + let relativeKey = key; + const expFilesIdx = key.indexOf('/experiment_files/'); + if (expFilesIdx !== -1) { + relativeKey = key.substring(expFilesIdx + '/experiment_files/'.length); + } else { + // fallback: if key contains experimentId, strip up to and including it + const expIdIdx = key.indexOf(experimentId + '/'); + if (expIdIdx !== -1) { + relativeKey = key.substring(expIdIdx + experimentId.length + 1); + } + } + const url = `/api/experiment/${experimentId}/files?key=${encodeURIComponent(relativeKey)}`; const res = await fetch(url, { method: 'DELETE' }); if (res.ok) await fetchFiles(); } @@ -139,15 +151,22 @@ function copyLink() { async function deleteFileOrFolder(key: string, isFolder: boolean) { console.log('deleteFileOrFolder called:', { key, isFolder, experimentId }); - // If folder, send a delete request with a prefix param if (isFolder) { - const url = `/api/experiment/${experimentId}/files?prefix=${encodeURIComponent(key)}`; - console.log('Deleting folder with URL:', url); + // For folders, use the same logic to get the relative prefix + let relativePrefix = key; + const expFilesIdx = key.indexOf('/experiment_files/'); + if (expFilesIdx !== -1) { + relativePrefix = key.substring(expFilesIdx + '/experiment_files/'.length); + } else { + const expIdIdx = key.indexOf(experimentId + '/'); + if (expIdIdx !== -1) { + relativePrefix = key.substring(expIdIdx + experimentId.length + 1); + } + } + const url = `/api/experiment/${experimentId}/files?prefix=${encodeURIComponent(relativePrefix)}`; const res = await fetch(url, { method: 'DELETE' }); - console.log('Folder delete response:', res.status, res.ok); if (res.ok) await fetchFiles(); } else { - console.log('Deleting file with key:', key); await deleteFile(key); } } diff --git a/src/routes/settings/profile/+page.svelte b/src/routes/settings/profile/+page.svelte index 21e22ea..5d004ae 100644 --- a/src/routes/settings/profile/+page.svelte +++ b/src/routes/settings/profile/+page.svelte @@ -1,5 +1,6 @@