Skip to content

Commit

Permalink
added
Browse files Browse the repository at this point in the history
  • Loading branch information
d3rpp committed Nov 19, 2024
1 parent a4209bc commit e338b8f
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 11 deletions.
165 changes: 160 additions & 5 deletions src/lib/components/ui/file-card/file-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
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 {
unwrap_symmetrical_key,
decrypt_blob,
} from "$lib/client/encryption";
import { toast } from "svelte-sonner";
import { filesize } from "filesize";
interface File {
name: string;
size: number;
Expand All @@ -31,16 +35,34 @@
rpc: ReturnType<typeof trpc>;
} = $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);
Expand All @@ -50,6 +72,7 @@
return bytes;
};
// #region download file
const downloadFile = async () => {
try {
isBusy = true;
Expand Down Expand Up @@ -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
</script>

<Card.Root class="w-72">
Expand All @@ -135,7 +193,7 @@
<Card.Content>
{filesize(file.size)}
</Card.Content>
<Card.Footer>
<Card.Footer class="flex flex-row justify-end">
<Button
size="icon"
variant="ghost"
Expand All @@ -150,5 +208,102 @@
<Download />
{/if}
</Button>
<AlertDialog.Root bind:open={renameAlertOpen}>
<AlertDialog.Trigger>
{#snippet child({ props })}
<Button
disabled={isLoading}
size="icon"
variant="ghost"
{...props}
>
<PenBoxIcon />
</Button>
{/snippet}
</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>
What would you like to rename this to?
</AlertDialog.Title>
</AlertDialog.Header>
<Input
name="rename_file"
bind:value={renameValue}
placeholder="New name"
/>
<AlertDialog.Footer>
<AlertDialog.Cancel
class={buttonVariants({
variant: "outline",
})}
onmouseup={() => (renameValue = "")}
>
Cancel
</AlertDialog.Cancel>
<AlertDialog.Action
disabled={renameValue.length < 3 || isRenaming}
class={buttonVariants({
variant: "default",
class: "w-24",
})}
onmouseup={renameFile}
>
{#if isRenaming}
<AnimatedLoading />
{:else}
Rename
{/if}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
<AlertDialog.Root bind:open={deleteAlertOpen}>
<AlertDialog.Trigger>
{#snippet child({ props })}
<Button
disabled={isLoading}
size="icon"
variant="ghost-destructive"
{...props}
>
<Trash2 />
</Button>
{/snippet}
</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>
Are you sure you want to delete this file?
</AlertDialog.Title>
<AlertDialog.Description>
You cannot undo this action.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel
class={buttonVariants({
variant: "outline",
})}
>
Cancel
</AlertDialog.Cancel>
<AlertDialog.Action
disabled={isDeleting}
class={buttonVariants({
variant: "destructive",
class: "w-24",
})}
onmouseup={deleteFile}
>
{#if isDeleting}
<AnimatedLoading />
{:else}
<span class="text-foreground">Delete</span>
{/if}
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
</Card.Footer>
</Card.Root>
89 changes: 89 additions & 0 deletions src/lib/server/trpc/router/blob_management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
12 changes: 7 additions & 5 deletions src/lib/server/trpc/router/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(app)/app/account/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
class="h-4 w-4 text-destructive-foreground"
/>
{:else}
{$uploadedFileCount.data.uploadedFiles}
{$uploadedFileCount.data}
{/if}
</span>
</Card.Content>
Expand Down

0 comments on commit e338b8f

Please sign in to comment.