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

@@ -43,6 +43,33 @@ export class FileController {
});
}
getFileAccessUrl(
fctx: FlowExecCtx,
fileId: string,
userId: string,
expiresIn: number = 3600,
) {
return traceResultAsync({
name: "logic.files.controller.getFileAccessUrl",
fctx,
attributes: {
"app.user.id": userId,
"app.file.id": fileId,
"app.file.access_ttl_sec": expiresIn,
},
fn: () =>
this.fileRepo
.getFileById(fctx, fileId, userId)
.andThen((file) =>
this.storageRepo.generatePresignedDownloadUrl(
fctx,
file.objectKey,
expiresIn,
),
),
});
}
uploadFile(
fctx: FlowExecCtx,
userId: string,

View File

@@ -91,6 +91,14 @@ export type PresignedUploadResponse = v.InferOutput<
typeof presignedUploadResponseSchema
>;
export const presignedFileAccessResponseSchema = v.object({
url: v.string(),
expiresIn: v.pipe(v.number(), v.integer()),
});
export type PresignedFileAccessResponse = v.InferOutput<
typeof presignedFileAccessResponseSchema
>;
export const fileUploadResultSchema = v.object({
success: v.boolean(),
file: v.optional(fileSchema),

View File

@@ -1,6 +1,9 @@
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import type { PresignedUploadResponse } from "./data";
import type {
PresignedFileAccessResponse,
PresignedUploadResponse,
} from "./data";
import { R2StorageClient } from "@pkg/objectstorage";
import { type Err } from "@pkg/result";
import { fileErrors } from "./errors";
@@ -209,6 +212,78 @@ export class StorageRepository {
});
}
generatePresignedDownloadUrl(
fctx: FlowExecCtx,
objectKey: string,
expiresIn: number,
): ResultAsync<PresignedFileAccessResponse, Err> {
const startedAt = Date.now();
logDomainEvent({
event: "files.storage.presigned_download.started",
fctx,
meta: { objectKey, expiresIn },
});
return ResultAsync.fromPromise(
this.storageClient.generatePresignedDownloadUrl(objectKey, expiresIn),
(error) => {
logDomainEvent({
level: "error",
event: "files.storage.presigned_download.failed",
fctx,
durationMs: Date.now() - startedAt,
error,
meta: { objectKey },
});
return fileErrors.presignedUrlFailed(
fctx,
error instanceof Error ? error.message : String(error),
);
},
).andThen((result) => {
if (result.error) {
logDomainEvent({
level: "error",
event: "files.storage.presigned_download.failed",
fctx,
durationMs: Date.now() - startedAt,
error: result.error,
meta: { objectKey, stage: "storage_response" },
});
return errAsync(
fileErrors.presignedUrlFailed(fctx, String(result.error)),
);
}
const data = result.data;
if (!data?.downloadUrl) {
logDomainEvent({
level: "error",
event: "files.storage.presigned_download.failed",
fctx,
durationMs: Date.now() - startedAt,
error: {
code: "NO_PRESIGNED_DATA",
message: "No presigned download data returned",
},
meta: { objectKey },
});
return errAsync(fileErrors.noPresignedData(fctx));
}
logDomainEvent({
event: "files.storage.presigned_download.succeeded",
fctx,
durationMs: Date.now() - startedAt,
meta: { objectKey, expiresIn },
});
return okAsync({
url: data.downloadUrl,
expiresIn: data.expiresIn,
});
});
}
deleteFile(
fctx: FlowExecCtx,
objectKey: string,