From 1310ff322013955f810c0f74cd3f052ec2651f25 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 2 Mar 2026 21:40:02 +0200 Subject: [PATCH] now files do presigned url setup --- .../src/lib/domains/files/files.remote.ts | 20 +++++ .../src/lib/domains/files/files.vm.svelte.ts | 26 ++++++- .../src/lib/domains/mobile/mobile.remote.ts | 33 +++++++- .../main/src/routes/(main)/files/+page.svelte | 9 +-- packages/logic/domains/files/controller.ts | 27 +++++++ packages/logic/domains/files/data.ts | 8 ++ .../logic/domains/files/storage.repository.ts | 77 ++++++++++++++++++- packages/objectstorage/src/client.ts | 41 ++++++++++ packages/objectstorage/src/data.ts | 8 ++ 9 files changed, 239 insertions(+), 10 deletions(-) diff --git a/apps/main/src/lib/domains/files/files.remote.ts b/apps/main/src/lib/domains/files/files.remote.ts index 77f4074..d2a5bb0 100644 --- a/apps/main/src/lib/domains/files/files.remote.ts +++ b/apps/main/src/lib/domains/files/files.remote.ts @@ -51,6 +51,10 @@ const deleteFileInputSchema = v.object({ fileId: v.string(), }); +const getFileAccessUrlInputSchema = v.object({ + fileId: v.string(), +}); + export const deleteFileSC = command(deleteFileInputSchema, async (input) => { const event = getRequestEvent(); const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); @@ -64,6 +68,22 @@ export const deleteFileSC = command(deleteFileInputSchema, async (input) => { : { data: null, error: res.error }; }); +export const getFileAccessUrlSQ = query( + getFileAccessUrlInputSchema, + async (input) => { + const event = getRequestEvent(); + const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); + if (!fctx.userId) { + return unauthorized(fctx); + } + + const res = await fc.getFileAccessUrl(fctx, input.fileId, fctx.userId, 3600); + return res.isOk() + ? { data: res.value, error: null } + : { data: null, error: res.error }; + }, +); + export const cleanupDanglingFilesSC = command(v.object({}), async () => { const event = getRequestEvent(); const fctx = await getFlowExecCtxForRemoteFuncs(event.locals); diff --git a/apps/main/src/lib/domains/files/files.vm.svelte.ts b/apps/main/src/lib/domains/files/files.vm.svelte.ts index 3f880ac..59eade8 100644 --- a/apps/main/src/lib/domains/files/files.vm.svelte.ts +++ b/apps/main/src/lib/domains/files/files.vm.svelte.ts @@ -1,5 +1,10 @@ import type { File } from "@pkg/logic/domains/files/data"; -import { cleanupDanglingFilesSC, deleteFileSC, getFilesSQ } from "./files.remote"; +import { + cleanupDanglingFilesSC, + deleteFileSC, + getFileAccessUrlSQ, + getFilesSQ, +} from "./files.remote"; import { toast } from "svelte-sonner"; class FilesViewModel { @@ -104,6 +109,25 @@ class FilesViewModel { this.cleanupLoading = false; } } + + async openFile(fileId: string) { + try { + const result = await getFileAccessUrlSQ({ fileId }); + if (result?.error || !result?.data?.url) { + toast.error(result?.error?.message || "Failed to open file", { + description: result?.error?.description || "Please try again later", + }); + return; + } + + window.open(result.data.url, "_blank", "noopener,noreferrer"); + } catch (error) { + toast.error("Failed to open file", { + description: + error instanceof Error ? error.message : "Please try again later", + }); + } + } } export const filesVM = new FilesViewModel(); diff --git a/apps/main/src/lib/domains/mobile/mobile.remote.ts b/apps/main/src/lib/domains/mobile/mobile.remote.ts index 4d27c02..43084d5 100644 --- a/apps/main/src/lib/domains/mobile/mobile.remote.ts +++ b/apps/main/src/lib/domains/mobile/mobile.remote.ts @@ -1,4 +1,5 @@ import { getMobileController } from "@pkg/logic/domains/mobile/controller"; +import { getFileController } from "@pkg/logic/domains/files/controller"; import { mobilePaginationSchema, listMobileDeviceMediaFiltersSchema, @@ -12,6 +13,7 @@ import { command, getRequestEvent, query } from "$app/server"; import * as v from "valibot"; const mc = getMobileController(); +const fc = getFileController(); const getDevicesInputSchema = v.object({ search: v.optional(v.string()), @@ -104,9 +106,34 @@ export const getDeviceMediaSQ = query( }); const res = await mc.listDeviceMedia(fctx, filters, input.pagination); - return res.isOk() - ? { data: res.value, error: null } - : { data: null, error: res.error }; + if (res.isErr()) { + return { data: null, error: res.error }; + } + + const rows = res.value.data; + const withAccessUrls = await Promise.all( + rows.map(async (row) => { + const access = await fc.getFileAccessUrl( + fctx, + row.fileId, + fctx.userId!, + 3600, + ); + + return { + ...row, + r2Url: access.isOk() ? access.value.url : null, + }; + }), + ); + + return { + data: { + ...res.value, + data: withAccessUrls, + }, + error: null, + }; }, ); diff --git a/apps/main/src/routes/(main)/files/+page.svelte b/apps/main/src/routes/(main)/files/+page.svelte index e7db948..c8be921 100644 --- a/apps/main/src/routes/(main)/files/+page.svelte +++ b/apps/main/src/routes/(main)/files/+page.svelte @@ -98,14 +98,13 @@ {new Date(item.uploadedAt).toLocaleString()} - void filesVM.openFile(item.id)} class="text-xs text-primary underline" > Open - +