From e338b8ff6555918c8934cab929f304d1562d71d9 Mon Sep 17 00:00:00 2001 From: d3rpp Date: Tue, 19 Nov 2024 21:53:15 +1300 Subject: [PATCH] added --- .../components/ui/file-card/file-card.svelte | 165 +++++++++++++++++- src/lib/server/trpc/router/blob_management.ts | 89 ++++++++++ src/lib/server/trpc/router/user.ts | 12 +- src/routes/(app)/app/account/+page.svelte | 2 +- 4 files changed, 257 insertions(+), 11 deletions(-) diff --git a/src/lib/components/ui/file-card/file-card.svelte b/src/lib/components/ui/file-card/file-card.svelte index 53c02e3..0bc1b94 100644 --- a/src/lib/components/ui/file-card/file-card.svelte +++ b/src/lib/components/ui/file-card/file-card.svelte @@ -3,11 +3,12 @@ import * as Card from "@/ui/card"; import * as Tooltip from "@/ui/tooltip"; - import { Button } from "@/ui/button"; + import * as AlertDialog from "@/ui/alert-dialog"; + import { Button, buttonVariants } from "@/ui/button"; + import { Input } from "@/ui/input"; - import { filesize } from "filesize"; import { AnimatedLoading } from "$lib/icons"; - import { Download, X } from "lucide-svelte"; + import { Download, X, Trash2, PenBoxIcon } from "lucide-svelte"; import { import_private_key } from "$lib/client/key_management"; import { @@ -15,6 +16,9 @@ decrypt_blob, } from "$lib/client/encryption"; + import { toast } from "svelte-sonner"; + import { filesize } from "filesize"; + interface File { name: string; size: number; @@ -31,16 +35,34 @@ rpc: ReturnType; } = $props(); + const utils = rpc.createUtils(); + // screw it, we'll just download ALL OF THEM EVERY TIME const symkeyQuery = rpc.blobManagement.downloadSymmeticKey.createQuery({ kid: file.id, }); + const renameFileMutation = rpc.blobManagement.renameBlob.createMutation(); + const deleteFileMutation = rpc.blobManagement.deleteBlob.createMutation(); + let isBusy = $state(false); + let isDeleting = $state(false); + let isRenaming = $state(false); let isLoading = $derived( - isBusy || $symkeyQuery.isLoading || $symkeyQuery.isError, + isBusy || + isDeleting || + isRenaming || + $symkeyQuery.isLoading || + $symkeyQuery.isError || + $renameFileMutation.isPending || + $deleteFileMutation.isPending, ); + let renameValue = $state(""); + + let renameAlertOpen = $state(false); + let deleteAlertOpen = $state(false); + const decodeB64Blob = (b64: string): Uint8Array => { const binaryString = atob(b64); let bytes = new Uint8Array(binaryString.length); @@ -50,6 +72,7 @@ return bytes; }; + // #region download file const downloadFile = async () => { try { isBusy = true; @@ -112,6 +135,41 @@ isBusy = false; }; + // #endregion + // #region rename file + const renameFile = async () => { + isRenaming = true; + + await $renameFileMutation.mutateAsync({ + blob_id: file.id, + new_name: renameValue, + }); + + utils.user.fetchUploadedFileMetadata.refetch(); + + toast.success("Renamed File successfully"); + + renameAlertOpen = false; + isRenaming = false; + renameValue = ""; + }; + // #endregion + // #region delete file + const deleteFile = async () => { + isDeleting = true; + + await $deleteFileMutation.mutateAsync({ + blob_id: file.id, + }); + + utils.user.fetchUploadedFileMetadata.refetch(); + + toast.success("Deleted File siccessfully"); + + deleteAlertOpen = false; + isDeleting = false; + }; + // #endregion @@ -135,7 +193,7 @@ {filesize(file.size)} - + + + + {#snippet child({ props })} + + {/snippet} + + + + + What would you like to rename this to? + + + + + (renameValue = "")} + > + Cancel + + + {#if isRenaming} + + {:else} + Rename + {/if} + + + + + + + {#snippet child({ props })} + + {/snippet} + + + + + Are you sure you want to delete this file? + + + You cannot undo this action. + + + + + Cancel + + + {#if isDeleting} + + {:else} + Delete + {/if} + + + + diff --git a/src/lib/server/trpc/router/blob_management.ts b/src/lib/server/trpc/router/blob_management.ts index 17909b0..5f23033 100644 --- a/src/lib/server/trpc/router/blob_management.ts +++ b/src/lib/server/trpc/router/blob_management.ts @@ -111,4 +111,93 @@ export const blobManagementRouter = trpcInstance.router({ } }), // #endregion + // #region Rename Blob + renameBlob: trpcInstance.procedure + .use(authMiddleware) + .input( + z.object({ + blob_id: z.string().ulid(), + new_name: z.string().min(3).max(100), + }), + ) + .mutation(async (opts) => { + return await DB.transaction(async (tx_db) => { + const affected_rows = await tx_db + .update(encryptedBlobTable) + .set({ + name: opts.input.new_name, + }) + .where( + and( + eq(encryptedBlobTable.kid, opts.input.blob_id), + eq(encryptedBlobTable.owner, opts.ctx.user.id), + ), + ) + .returning({ + id: encryptedBlobTable.kid, + }); + + if (affected_rows.length != 1) { + if (affected_rows.length === 0) { + throw new TRPCError({ + code: "NOT_FOUND", + }); + } + + console.log("UNEXPECTED CONFLICT", { + actual: affected_rows, + expected: [{ id: opts.input.blob_id }], + }); + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + }); + } else { + return opts.input.new_name; + } + }); + }), + // #endregion + // #region Delete Blob + deleteBlob: trpcInstance.procedure + .use(authMiddleware) + .input( + z.object({ + blob_id: z.string().ulid(), + }), + ) + .mutation(async (opts) => { + return await DB.transaction(async (tx_db) => { + const affected_rows = await tx_db + .delete(encryptedBlobTable) + .where( + and( + eq(encryptedBlobTable.kid, opts.input.blob_id), + eq(encryptedBlobTable.owner, opts.ctx.user.id), + ), + ) + .returning({ id: encryptedBlobTable.kid }); + + if (affected_rows.length != 1) { + if (affected_rows.length === 0) { + throw new TRPCError({ + code: "NOT_FOUND", + }); + } + + console.log("UNEXPECTED CONFLICT", { affected_rows }); + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + }); + } + + await tx_db + .delete(symmetricKeyTable) + .where(eq(symmetricKeyTable.kid, opts.input.blob_id)); + + return true; + }); + }), + // #endregion }); diff --git a/src/lib/server/trpc/router/user.ts b/src/lib/server/trpc/router/user.ts index 431baa2..2d9d2ea 100644 --- a/src/lib/server/trpc/router/user.ts +++ b/src/lib/server/trpc/router/user.ts @@ -15,11 +15,13 @@ export const userRouter = trpcInstance.router({ getUploadedFiles: trpcInstance.procedure .use(authMiddleware) .query(async (opts) => { - DB.select({ - kid: encryptedBlobTable.name, - }) - .from(encryptedBlobTable) - .where(eq(encryptedBlobTable.owner, opts.ctx.user.id)); + return ( + await DB.select({ + kid: encryptedBlobTable.name, + }) + .from(encryptedBlobTable) + .where(eq(encryptedBlobTable.owner, opts.ctx.user.id)) + ).length; }), fetchUploadedFileMetadata: trpcInstance.procedure diff --git a/src/routes/(app)/app/account/+page.svelte b/src/routes/(app)/app/account/+page.svelte index 22364a6..6d56887 100644 --- a/src/routes/(app)/app/account/+page.svelte +++ b/src/routes/(app)/app/account/+page.svelte @@ -78,7 +78,7 @@ class="h-4 w-4 text-destructive-foreground" /> {:else} - {$uploadedFileCount.data.uploadedFiles} + {$uploadedFileCount.data} {/if}