added file domain logic, updated drizzle package
This commit is contained in:
252
packages/logic/domains/files/controller.ts
Normal file
252
packages/logic/domains/files/controller.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import {
|
||||
FileFilters,
|
||||
FileShareRequest,
|
||||
FileUpdateRequest,
|
||||
FileUploadRequest,
|
||||
PaginationOptions,
|
||||
PresignedUploadRequest,
|
||||
} from "./data";
|
||||
import { FlowExecCtx } from "@core/flow.execution.context";
|
||||
import { StorageRepository } from "./storage.repository";
|
||||
import { FileRepository } from "./repository";
|
||||
import { settings } from "@core/settings";
|
||||
import { ResultAsync } from "neverthrow";
|
||||
import { traceResultAsync } from "@core/observability";
|
||||
import { db } from "@pkg/db";
|
||||
|
||||
export class FileController {
|
||||
constructor(
|
||||
private fileRepo: FileRepository,
|
||||
private storageRepo: StorageRepository,
|
||||
private publicUrl: string,
|
||||
) {}
|
||||
|
||||
getFiles(
|
||||
fctx: FlowExecCtx,
|
||||
filters: FileFilters,
|
||||
pagination: PaginationOptions,
|
||||
) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.getFiles",
|
||||
fctx,
|
||||
attributes: { "app.user.id": filters.userId },
|
||||
fn: () => this.fileRepo.getFiles(fctx, filters, pagination),
|
||||
});
|
||||
}
|
||||
|
||||
getFile(fctx: FlowExecCtx, fileId: string, userId: string) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.getFile",
|
||||
fctx,
|
||||
attributes: { "app.user.id": userId, "app.file.id": fileId },
|
||||
fn: () => this.fileRepo.getFileById(fctx, fileId, userId),
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(
|
||||
fctx: FlowExecCtx,
|
||||
userId: string,
|
||||
file: globalThis.File,
|
||||
uploadRequest: FileUploadRequest,
|
||||
) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.uploadFile",
|
||||
fctx,
|
||||
attributes: { "app.user.id": userId, "app.file.name": file.name },
|
||||
fn: () =>
|
||||
ResultAsync.fromPromise(file.arrayBuffer(), (error) => ({
|
||||
code: "INTERNAL_ERROR",
|
||||
message: "Failed to read file buffer",
|
||||
description: "Please try again",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
}))
|
||||
.map((arrayBuffer) => Buffer.from(arrayBuffer))
|
||||
.andThen((buffer) =>
|
||||
this.storageRepo.uploadFile(
|
||||
fctx,
|
||||
buffer,
|
||||
file.name,
|
||||
file.type,
|
||||
userId,
|
||||
{
|
||||
visibility:
|
||||
(uploadRequest.visibility as
|
||||
| "public"
|
||||
| "private") || "private",
|
||||
metadata: uploadRequest.metadata,
|
||||
tags: uploadRequest.tags,
|
||||
processImage: uploadRequest.processImage,
|
||||
processDocument: uploadRequest.processDocument,
|
||||
processVideo: uploadRequest.processVideo,
|
||||
},
|
||||
),
|
||||
)
|
||||
.andThen((fileMetadata) =>
|
||||
this.fileRepo
|
||||
.createFile(fctx, {
|
||||
id: fileMetadata.id,
|
||||
filename: fileMetadata.filename,
|
||||
originalName: fileMetadata.originalName,
|
||||
mimeType: fileMetadata.mimeType,
|
||||
size: fileMetadata.size,
|
||||
hash: fileMetadata.hash,
|
||||
bucketName: fileMetadata.bucketName,
|
||||
objectKey: fileMetadata.objectKey,
|
||||
r2Url: fileMetadata.r2Url,
|
||||
visibility: fileMetadata.visibility,
|
||||
userId: fileMetadata.userId,
|
||||
metadata: fileMetadata.metadata,
|
||||
tags: fileMetadata.tags
|
||||
? [...fileMetadata.tags]
|
||||
: undefined,
|
||||
status: "ready",
|
||||
uploadedAt: fileMetadata.uploadedAt,
|
||||
})
|
||||
.map((dbFile) => ({
|
||||
success: true,
|
||||
file: dbFile,
|
||||
uploadId: fileMetadata.id,
|
||||
})),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
generatePresignedUrl(
|
||||
fctx: FlowExecCtx,
|
||||
userId: string,
|
||||
bucketName: string,
|
||||
request: PresignedUploadRequest,
|
||||
) {
|
||||
const fileId = crypto.randomUUID();
|
||||
const extension = request.filename.split(".").pop() || "";
|
||||
const filename = `${fileId}.${extension}`;
|
||||
const objectKey = `uploads/${userId}/${filename}`;
|
||||
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.generatePresignedUrl",
|
||||
fctx,
|
||||
attributes: { "app.user.id": userId, "app.file.id": fileId },
|
||||
fn: () =>
|
||||
this.storageRepo
|
||||
.generatePresignedUploadUrl(
|
||||
fctx,
|
||||
objectKey,
|
||||
request.mimeType,
|
||||
3600,
|
||||
)
|
||||
.andThen((presignedData) =>
|
||||
this.fileRepo
|
||||
.createFile(fctx, {
|
||||
id: fileId,
|
||||
filename,
|
||||
originalName: request.filename,
|
||||
mimeType: request.mimeType,
|
||||
size: request.size,
|
||||
hash: "",
|
||||
bucketName,
|
||||
objectKey,
|
||||
r2Url: `${this.publicUrl}/${bucketName}/${objectKey}`,
|
||||
visibility: request.visibility || "private",
|
||||
userId,
|
||||
status: "processing",
|
||||
uploadedAt: new Date(),
|
||||
})
|
||||
.map(() => ({
|
||||
...presignedData,
|
||||
fileId,
|
||||
objectKey,
|
||||
})),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
updateFile(
|
||||
fctx: FlowExecCtx,
|
||||
fileId: string,
|
||||
userId: string,
|
||||
updates: FileUpdateRequest,
|
||||
) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.updateFile",
|
||||
fctx,
|
||||
attributes: { "app.user.id": userId, "app.file.id": fileId },
|
||||
fn: () => this.fileRepo.updateFile(fctx, fileId, userId, updates),
|
||||
});
|
||||
}
|
||||
|
||||
deleteFiles(fctx: FlowExecCtx, fileIds: readonly string[], userId: string) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.deleteFiles",
|
||||
fctx,
|
||||
attributes: {
|
||||
"app.user.id": userId,
|
||||
"app.files.count": fileIds.length,
|
||||
},
|
||||
fn: () =>
|
||||
ResultAsync.combine(
|
||||
[...fileIds].map((fileId) =>
|
||||
this.fileRepo.getFileById(fctx, fileId, userId),
|
||||
),
|
||||
)
|
||||
.map((files) => files.map((file) => file.objectKey))
|
||||
.andThen((objectKeys) =>
|
||||
this.storageRepo.deleteFiles(fctx, objectKeys),
|
||||
)
|
||||
.andThen(() =>
|
||||
this.fileRepo.deleteFiles(fctx, fileIds, userId),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
shareFile(
|
||||
fctx: FlowExecCtx,
|
||||
fileId: string,
|
||||
ownerId: string,
|
||||
shareRequest: FileShareRequest,
|
||||
) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.shareFile",
|
||||
fctx,
|
||||
attributes: { "app.user.id": ownerId, "app.file.id": fileId },
|
||||
fn: () => this.fileRepo.shareFile(fctx, fileId, ownerId, shareRequest),
|
||||
});
|
||||
}
|
||||
|
||||
updateFileStatus(
|
||||
fctx: FlowExecCtx,
|
||||
fileId: string,
|
||||
status: string,
|
||||
processingError?: string,
|
||||
) {
|
||||
return traceResultAsync({
|
||||
name: "logic.files.controller.updateFileStatus",
|
||||
fctx,
|
||||
attributes: { "app.file.id": fileId },
|
||||
fn: () =>
|
||||
this.fileRepo.updateFileStatus(
|
||||
fctx,
|
||||
fileId,
|
||||
status,
|
||||
processingError,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getFileController(): FileController {
|
||||
return new FileController(
|
||||
new FileRepository(db),
|
||||
new StorageRepository({
|
||||
bucketName: settings.r2BucketName || "",
|
||||
region: settings.r2Region || "",
|
||||
endpoint: settings.r2Endpoint || "",
|
||||
accessKey: settings.r2AccessKey || "",
|
||||
secretKey: settings.r2SecretKey || "",
|
||||
publicUrl: settings.r2PublicUrl || "",
|
||||
maxFileSize: settings.maxFileSize,
|
||||
allowedMimeTypes: settings.allowedMimeTypes,
|
||||
allowedExtensions: settings.allowedExtensions,
|
||||
}),
|
||||
settings.r2PublicUrl || "",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user