now files do presigned url setup

This commit is contained in:
user
2026-03-02 21:40:02 +02:00
parent 34aa52ec7c
commit 1310ff3220
9 changed files with 239 additions and 10 deletions

View File

@@ -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);

View File

@@ -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();

View File

@@ -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,
};
},
);

View File

@@ -98,14 +98,13 @@
{new Date(item.uploadedAt).toLocaleString()}
</Table.Cell>
<Table.Cell>
<a
href={item.r2Url}
target="_blank"
rel="noreferrer"
<button
type="button"
onclick={() => void filesVM.openFile(item.id)}
class="text-xs text-primary underline"
>
Open
</a>
</button>
</Table.Cell>
<Table.Cell>
<Button