fix: move blob migration server-side, restore lightweight list loading
The client-side migration was downloading 25-50MB of base64 data to the browser before showing anything. getAllEntries also lost its lightweight flag. Fix: - New POST /api/storage/migrate-blobs endpoint runs entirely server-side (loads entries one-at-a-time from PostgreSQL, never sends heavy data to browser) - Restore lightweight:true on getAllEntries (strips remaining base64 in API) - Migration fires on mount (fire-and-forget) while list loads independently - Remove client-side migrateEntryBlobs function
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/core/storage/prisma";
|
||||
|
||||
/**
|
||||
* Server-side migration: move base64 attachment data from registry entries
|
||||
* to a separate "registratura-blobs" namespace. This runs entirely on the
|
||||
* server — heavy data never reaches the browser.
|
||||
*
|
||||
* POST /api/storage/migrate-blobs
|
||||
*/
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Check migration flag
|
||||
const flag = await prisma.keyValueStore.findUnique({
|
||||
where: {
|
||||
namespace_key: {
|
||||
namespace: "registratura",
|
||||
key: "__blobs_migrated__",
|
||||
},
|
||||
},
|
||||
});
|
||||
if (flag) {
|
||||
return NextResponse.json({ migrated: 0, alreadyDone: true });
|
||||
}
|
||||
|
||||
// 2. Get ONLY keys (no heavy value column) for registratura entries
|
||||
const keyRows = await prisma.keyValueStore.findMany({
|
||||
where: { namespace: "registratura", key: { startsWith: "entry:" } },
|
||||
select: { key: true },
|
||||
});
|
||||
|
||||
let migrated = 0;
|
||||
|
||||
// 3. Process entries ONE AT A TIME server-side
|
||||
for (const { key } of keyRows) {
|
||||
const row = await prisma.keyValueStore.findUnique({
|
||||
where: { namespace_key: { namespace: "registratura", key } },
|
||||
});
|
||||
if (!row?.value) continue;
|
||||
|
||||
const entry = row.value as Record<string, unknown>;
|
||||
const entryId = (entry.id as string) || key.replace("entry:", "");
|
||||
const blobs: Record<string, unknown> = {};
|
||||
let hasBlobs = false;
|
||||
|
||||
// Strip attachment data
|
||||
const attachments =
|
||||
(entry.attachments as Array<Record<string, unknown>>) ?? [];
|
||||
const strippedAttachments = attachments.map((att) => {
|
||||
const data = att.data as string | undefined;
|
||||
if (
|
||||
data &&
|
||||
typeof data === "string" &&
|
||||
data.length > 1024 &&
|
||||
data !== "__stripped__"
|
||||
) {
|
||||
if (!blobs.attachments) blobs.attachments = {};
|
||||
(blobs.attachments as Record<string, string>)[att.id as string] =
|
||||
data;
|
||||
hasBlobs = true;
|
||||
return { ...att, data: "" };
|
||||
}
|
||||
return att;
|
||||
});
|
||||
|
||||
// Strip closure attachment data
|
||||
let strippedEntry: Record<string, unknown> = {
|
||||
...entry,
|
||||
attachments: strippedAttachments,
|
||||
};
|
||||
const closureInfo = entry.closureInfo as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
if (closureInfo?.attachment) {
|
||||
const closureAtt = closureInfo.attachment as Record<string, unknown>;
|
||||
const data = closureAtt.data as string | undefined;
|
||||
if (
|
||||
data &&
|
||||
typeof data === "string" &&
|
||||
data.length > 1024 &&
|
||||
data !== "__stripped__"
|
||||
) {
|
||||
blobs.closureAttachment = data;
|
||||
hasBlobs = true;
|
||||
strippedEntry = {
|
||||
...strippedEntry,
|
||||
closureInfo: {
|
||||
...closureInfo,
|
||||
attachment: { ...closureAtt, data: "" },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBlobs) {
|
||||
// Save stripped entry + blobs in parallel
|
||||
await Promise.all([
|
||||
prisma.keyValueStore.update({
|
||||
where: { namespace_key: { namespace: "registratura", key } },
|
||||
data: { value: strippedEntry as object },
|
||||
}),
|
||||
prisma.keyValueStore.upsert({
|
||||
where: {
|
||||
namespace_key: { namespace: "registratura-blobs", key: entryId },
|
||||
},
|
||||
update: { value: blobs as object },
|
||||
create: {
|
||||
namespace: "registratura-blobs",
|
||||
key: entryId,
|
||||
value: blobs as object,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
migrated++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Set migration flag
|
||||
await prisma.keyValueStore.upsert({
|
||||
where: {
|
||||
namespace_key: {
|
||||
namespace: "registratura",
|
||||
key: "__blobs_migrated__",
|
||||
},
|
||||
},
|
||||
update: { value: true },
|
||||
create: {
|
||||
namespace: "registratura",
|
||||
key: "__blobs_migrated__",
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ migrated, alreadyDone: false });
|
||||
} catch (error) {
|
||||
console.error("Blob migration error:", error);
|
||||
return NextResponse.json({ error: "Migration failed" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user