mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: wrap native uploads into tar.gz via packToTarGz helper
This commit is contained in:
@@ -1,16 +1,22 @@
|
|||||||
import { Readable } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import { createGunzip } from "node:zlib";
|
import { createGunzip, createGzip } from "node:zlib";
|
||||||
import * as tar from "tar-stream";
|
import * as tar from "tar-stream";
|
||||||
import { getArchiveEntryMatcher, getFileExtension } from "@/utils/common";
|
import { getArchiveEntryMatcher, getFileExtension } from "@/utils/common";
|
||||||
import { logger } from "@/lib/logger";
|
import { logger } from "@/lib/logger";
|
||||||
|
|
||||||
const log = logger.child({ module: "features/database/archive-inspect" });
|
const log = logger.child({ module: "features/database/archive-inspect" });
|
||||||
|
|
||||||
export type UploadKind = "raw" | "targz";
|
export type UploadKind = "raw" | "targz" | "wrap";
|
||||||
|
|
||||||
export type InspectResult = {
|
export type InspectResult = {
|
||||||
kind: UploadKind;
|
kind: UploadKind;
|
||||||
|
/** Extension to use when naming the stored file, incl. leading dot. */
|
||||||
storeExtension: string;
|
storeExtension: string;
|
||||||
|
/**
|
||||||
|
* For kind === "wrap": the entry name to give the dump inside the generated
|
||||||
|
* tar.gz (e.g. "backup.dump").
|
||||||
|
*/
|
||||||
|
innerName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class InvalidUploadError extends Error {
|
export class InvalidUploadError extends Error {
|
||||||
@@ -59,8 +65,12 @@ export async function inspectUpload(
|
|||||||
log.info({ dbms, size: buffer.length }, "Inspecting uploaded backup");
|
log.info({ dbms, size: buffer.length }, "Inspecting uploaded backup");
|
||||||
|
|
||||||
if (!isGzip(buffer)) {
|
if (!isGzip(buffer)) {
|
||||||
log.info({ dbms }, "Upload is not gzip, treating as raw dump");
|
const innerName = `backup${getFileExtension(dbms)}`;
|
||||||
return { kind: "raw", storeExtension: getFileExtension(dbms) };
|
log.info(
|
||||||
|
{ dbms, innerName },
|
||||||
|
"Native (non-gzip) upload, will wrap into tar.gz before storing",
|
||||||
|
);
|
||||||
|
return { kind: "wrap", storeExtension: ".tar.gz", innerName };
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries: string[];
|
let entries: string[];
|
||||||
@@ -97,3 +107,31 @@ export async function inspectUpload(
|
|||||||
);
|
);
|
||||||
return { kind: "targz", storeExtension: ".tar.gz" };
|
return { kind: "targz", storeExtension: ".tar.gz" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compresses a single raw dump buffer into a gzip'd tar containing one entry
|
||||||
|
* named `innerName`. Used to normalize native frontend uploads into the same
|
||||||
|
* `.tar.gz` shape the agent produces. The whole archive is built in memory,
|
||||||
|
* consistent with the existing upload path that already holds the full buffer.
|
||||||
|
*/
|
||||||
|
export function packToTarGz(buffer: Buffer, innerName: string): Promise<Buffer> {
|
||||||
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
const pack = tar.pack();
|
||||||
|
const gzip = createGzip();
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
|
gzip.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
gzip.on("end", () => resolve(Buffer.concat(chunks)));
|
||||||
|
gzip.on("error", reject);
|
||||||
|
pack.on("error", reject);
|
||||||
|
|
||||||
|
pack.pipe(gzip);
|
||||||
|
pack.entry({ name: innerName }, buffer, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pack.finalize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user