2026-01-17 17:27:33 +01:00
|
|
|
import {NextResponse} from "next/server";
|
|
|
|
|
import path from "path";
|
|
|
|
|
import type {StorageInput} from "@/features/storages/types";
|
|
|
|
|
import {dispatchStorage} from "@/features/storages/dispatch";
|
|
|
|
|
import {Readable} from "node:stream";
|
|
|
|
|
|
|
|
|
|
export async function GET(
|
|
|
|
|
request: Request,
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
const {searchParams} = new URL(request.url);
|
|
|
|
|
const token = searchParams.get('token');
|
|
|
|
|
const expires = searchParams.get('expires');
|
|
|
|
|
const pathFromUrl = searchParams.get('path');
|
2026-01-24 12:13:55 +01:00
|
|
|
const storageId = searchParams.get('storageId');
|
2026-01-17 17:27:33 +01:00
|
|
|
|
2026-01-24 12:13:55 +01:00
|
|
|
if (!pathFromUrl || !storageId) {
|
|
|
|
|
return NextResponse.json({error: "Missing search params"}, {status: 404})
|
2026-01-17 17:27:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const input: StorageInput = {
|
|
|
|
|
action: "get",
|
|
|
|
|
data: {
|
|
|
|
|
path: pathFromUrl,
|
|
|
|
|
signedUrl: true,
|
|
|
|
|
},
|
2026-01-24 12:13:55 +01:00
|
|
|
metadata: {
|
|
|
|
|
storageId: storageId,
|
|
|
|
|
fileKind: "backups",
|
|
|
|
|
}
|
2026-01-17 17:27:33 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-18 11:45:36 +01:00
|
|
|
console.debug(input);
|
|
|
|
|
|
2026-01-24 12:13:55 +01:00
|
|
|
const result = await dispatchStorage(input, undefined, storageId);
|
2026-01-17 17:27:33 +01:00
|
|
|
|
|
|
|
|
if (!result.success) {
|
2026-01-31 22:15:01 +01:00
|
|
|
return NextResponse.json({error: "Enable to get file from provided storage channel, an error occurred !"})
|
2026-01-17 17:27:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileName = path.basename(pathFromUrl);
|
|
|
|
|
|
|
|
|
|
const crypto = require('crypto');
|
|
|
|
|
const expectedToken = crypto.createHash('sha256').update(`${fileName}${expires}`).digest('hex');
|
|
|
|
|
if (token !== expectedToken) {
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{error: 'Invalid signed token'},
|
|
|
|
|
{status: 403}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const expiresAt = parseInt(expires!, 10);
|
|
|
|
|
if (Date.now() > expiresAt) {
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{error: 'Signed token expired'},
|
|
|
|
|
{status: 403}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 22:15:01 +01:00
|
|
|
if (!result.file || !(result.file instanceof Readable)) {
|
2026-01-17 17:27:33 +01:00
|
|
|
return NextResponse.json(
|
|
|
|
|
{error: "Invalid file payload"},
|
|
|
|
|
{status: 500}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 22:15:01 +01:00
|
|
|
const fileStream = Readable.from(result.file as Readable);
|
2026-01-17 17:27:33 +01:00
|
|
|
|
|
|
|
|
const stream = new ReadableStream({
|
|
|
|
|
start(controller) {
|
|
|
|
|
fileStream.on('data', (chunk) => controller.enqueue(chunk));
|
|
|
|
|
fileStream.on('end', () => controller.close());
|
|
|
|
|
fileStream.on('error', (err) => controller.error(err));
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return new NextResponse(stream, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Disposition': `attachment; filename="${fileName}"`,
|
|
|
|
|
'Content-Type': 'application/octet-stream',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|