Files
dataroom/lib/files/copy-file-server.ts
zeekayandHanzo Dev 2c4319f61e storage: one object store, ours — and the gate that shut it off is gone
`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]>
2026-08-09 11:23:32 -07:00

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