mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
`getS3ClientAndConfig` threw "Invalid upload transport" unless NEXT_PUBLIC_UPLOAD_TRANSPORT was "s3". The deployment does not set that variable — it configures storage through NEXT_PRIVATE_UPLOAD_* (transport s3, bucket dataroom, endpoint http://s3:9000, credentials) — so every call through that helper threw in production while a correctly configured bucket sat beside it. The two put paths keyed on the same unset variable and fell to `.otherwise()`, returning { type: null, data: null }: a server-side upload reported success and stored nothing. There is one object store now, so nothing chooses a transport. The VERCEL_BLOB arms are gone from get/put/copy/delete, CSV exports write through putFileServer, and branding + expired-export deletion go through deleteFile. Safe to remove rather than migrate: the database has no rows to orphan — Document, DocumentVersion, Team and User are all 0, so nothing is stored under a VERCEL_BLOB path. The cleanup queue now carries teamId, which deleteFile needs to resolve the storage region, and its item is a `path` rather than a `blobUrl` — the scheduler was testing `startsWith("https://")` to decide whether to enqueue, which a storage path never matches. Verified with npx tsc --noEmit: 100 -> 99. The one fixed is get-file.ts, whose ts-pattern match is no longer there to be uncallable. Nothing introduced: the two duplicate.ts files that looked new carry identical error counts before and after and were never touched. Co-authored-by: Hanzo Dev <[email protected]>
119 lines
2.9 KiB
TypeScript
119 lines
2.9 KiB
TypeScript
import { CopyObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
|
|
import { DocumentStorageType } from "@prisma/client";
|
|
import { match } from "ts-pattern";
|
|
|
|
import { newId } from "@/lib/id-helper";
|
|
|
|
import { getS3ClientAndConfig } from "./aws-client";
|
|
|
|
export const copyFileServer = async ({
|
|
teamId,
|
|
filePath,
|
|
fileName,
|
|
storageType,
|
|
}: {
|
|
teamId: string;
|
|
filePath: string;
|
|
fileName: string;
|
|
storageType: DocumentStorageType;
|
|
}) => {
|
|
const { type, data } = await match(storageType)
|
|
.with("S3_PATH", async () => copyFileInS3Server({ teamId, filePath }))
|
|
.otherwise(() => {
|
|
return {
|
|
type: null,
|
|
data: null,
|
|
};
|
|
});
|
|
|
|
return { type, data };
|
|
};
|
|
|
|
|
|
const copyFileInS3Server = async ({
|
|
teamId,
|
|
filePath,
|
|
}: {
|
|
teamId: string;
|
|
filePath: string;
|
|
}) => {
|
|
// Get the current document ID from the file path
|
|
const fromDocId = filePath.match(/doc_\w+/)?.[0];
|
|
// Generate a new document ID for copied document prefix
|
|
const toDocId = newId("doc");
|
|
|
|
if (!fromDocId) {
|
|
throw new Error("Invalid file ID");
|
|
}
|
|
|
|
const fromLocation = `${teamId}/${fromDocId}/`;
|
|
const toLocation = `${teamId}/${toDocId}/`;
|
|
|
|
const { config } = getS3ClientAndConfig();
|
|
|
|
const response = await copyFolder(
|
|
{
|
|
fromBucket: config.bucket,
|
|
fromLocation: fromLocation,
|
|
toBucket: config.bucket,
|
|
toLocation: toLocation,
|
|
},
|
|
teamId,
|
|
);
|
|
|
|
console.log("response", response);
|
|
|
|
return {
|
|
type: DocumentStorageType.S3_PATH,
|
|
data: { fromLocation, toLocation },
|
|
};
|
|
};
|
|
|
|
// copies all items in a folder on s3
|
|
async function copyFolder(
|
|
{
|
|
fromBucket,
|
|
fromLocation,
|
|
toBucket = fromBucket,
|
|
toLocation,
|
|
}: {
|
|
fromBucket: string;
|
|
fromLocation: string;
|
|
toBucket: string;
|
|
toLocation: string;
|
|
},
|
|
teamId: string,
|
|
) {
|
|
const { client } = getS3ClientAndConfig();
|
|
let count = 0;
|
|
const recursiveCopy = async function (token?: string) {
|
|
const listCommand = new ListObjectsV2Command({
|
|
Bucket: fromBucket,
|
|
Prefix: fromLocation,
|
|
ContinuationToken: token,
|
|
});
|
|
let list = await client.send(listCommand); // get the list
|
|
if (list.KeyCount && list.Contents) {
|
|
// if items to copy
|
|
const fromObjectKeys = list.Contents.map((content) => content.Key); // get the existing object keys
|
|
for (let fromObjectKey of fromObjectKeys) {
|
|
// loop through items and copy each one
|
|
const toObjectKey = fromObjectKey?.replace(fromLocation, toLocation); // replace with the destination in the key
|
|
// copy the file
|
|
const copyCommand = new CopyObjectCommand({
|
|
Bucket: toBucket,
|
|
CopySource: `${fromBucket}/${fromObjectKey}`,
|
|
Key: toObjectKey,
|
|
});
|
|
await client.send(copyCommand);
|
|
count += 1;
|
|
}
|
|
}
|
|
if (list.NextContinuationToken) {
|
|
recursiveCopy(list.NextContinuationToken);
|
|
}
|
|
return `${count} files copied.`;
|
|
};
|
|
return recursiveCopy();
|
|
}
|