lifetracker/apps/web/app/api/db/upload/route.ts
2025-01-31 18:09:27 -08:00

73 lines
3.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import serverConfig from '@lifetracker/shared/config';
import { pgDump, pgRestore } from 'pg-dump-restore';
import { db, getConnectionDetails } from '@lifetracker/db/drizzle';
import { sql } from 'drizzle-orm';
// Define paths for uploaded files and the production DB
const UPLOAD_DIR = path.join(process.cwd(), 'uploads');
const DB_PATH = path.join(serverConfig.dataDir, `lifetracker-${new Date().getTime()}.sql`);
// Ensure upload directory exists
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
export async function POST(req: NextRequest) {
try {
const contentType = req.headers.get('content-type') || '';
if (!contentType.includes('multipart/form-data')) {
return NextResponse.json({ message: 'Invalid content type. Please upload a file.' }, { status: 400 });
}
// Create a writable stream for saving the uploaded file
const formBoundary = contentType.split('boundary=')[1];
const formData = req.body;
// Parse and save the uploaded file
const chunks: Buffer[] = [];
for await (const chunk of formData) {
chunks.push(chunk);
}
const bodyBuffer = Buffer.concat(chunks);
const start = bodyBuffer.indexOf(`\r\n\r\n`) + 4;
const end = bodyBuffer.indexOf(`\r\n--${formBoundary}`, start);
const fileContent = bodyBuffer.slice(start, end);
// Save the uploaded file to the upload directory
const uploadedFilePath = path.join(UPLOAD_DIR, 'uploaded.sql');
fs.writeFileSync(uploadedFilePath, fileContent);
// Back up the existing production database
const backupPath = `${serverConfig.dataDir}/pg-backup-${new Date().getTime()}.sql`;
const { stdout, stderr } = await pgDump(getConnectionDetails(), {
filePath: `${backupPath}`,
});
console.log("Backed up existing data to ", backupPath);
console.log(stdout);
// If it's a SQL file, restore the production database from the uploaded file
if (uploadedFilePath.endsWith('.sql')) {
const { stdout: restoreStdout, stderr: restoreStderr } = await pgRestore(getConnectionDetails(), {
clean: true,
filePath: uploadedFilePath,
});
}
// If it ends in .db, assume it's a sqlite
else if (uploadedFilePath.endsWith('.db')) {
// TODO, if ever
return NextResponse.json({ message: 'Invalid file type. Please upload a .sql file.' }, { status: 400 });
}
else { return NextResponse.json({ message: 'Invalid file type. Please upload a .sql file.' }, { status: 400 }); }
return NextResponse.json({ message: 'Database uploaded and replaced successfully!' }, { status: 200 });
} catch (error) {
console.error('Error handling the uploaded file:', error);
return NextResponse.json({ message: 'Error processing upload.' }, { status: 500 });
}
}