mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import multer from 'multer';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import AdmZip from 'adm-zip';
|
|
import { ApiResponse } from '../types/index.js';
|
|
|
|
// Configure multer for file uploads
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
const uploadDir = path.join(process.cwd(), 'data/uploads/dxt');
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
cb(null, uploadDir);
|
|
},
|
|
filename: (req, file, cb) => {
|
|
const timestamp = Date.now();
|
|
const originalName = path.parse(file.originalname).name;
|
|
cb(null, `${originalName}-${timestamp}.dxt`);
|
|
},
|
|
});
|
|
|
|
const upload = multer({
|
|
storage,
|
|
fileFilter: (req, file, cb) => {
|
|
if (file.originalname.endsWith('.dxt')) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Only .dxt files are allowed'));
|
|
}
|
|
},
|
|
limits: {
|
|
fileSize: 500 * 1024 * 1024, // 500MB limit
|
|
},
|
|
});
|
|
|
|
export const uploadMiddleware = upload.single('dxtFile');
|
|
|
|
// Clean up old DXT server files when installing a new version
|
|
const cleanupOldDxtServer = (serverName: string): void => {
|
|
try {
|
|
const uploadDir = path.join(process.cwd(), 'data/uploads/dxt');
|
|
const serverPattern = `server-${serverName}`;
|
|
|
|
if (fs.existsSync(uploadDir)) {
|
|
const files = fs.readdirSync(uploadDir);
|
|
files.forEach((file) => {
|
|
if (file.startsWith(serverPattern)) {
|
|
const filePath = path.join(uploadDir, file);
|
|
if (fs.statSync(filePath).isDirectory()) {
|
|
fs.rmSync(filePath, { recursive: true, force: true });
|
|
console.log(`Cleaned up old DXT server directory: ${filePath}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to cleanup old DXT server files:', error);
|
|
// Don't fail the installation if cleanup fails
|
|
}
|
|
};
|
|
|
|
export const uploadDxtFile = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
if (!req.file) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: 'No DXT file uploaded',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const dxtFilePath = req.file.path;
|
|
const timestamp = Date.now();
|
|
const tempExtractDir = path.join(path.dirname(dxtFilePath), `temp-extracted-${timestamp}`);
|
|
|
|
try {
|
|
// Extract the DXT file (which is a ZIP archive) to a temporary directory first
|
|
const zip = new AdmZip(dxtFilePath);
|
|
zip.extractAllTo(tempExtractDir, true);
|
|
|
|
// Read and validate the manifest.json
|
|
const manifestPath = path.join(tempExtractDir, 'manifest.json');
|
|
if (!fs.existsSync(manifestPath)) {
|
|
throw new Error('manifest.json not found in DXT file');
|
|
}
|
|
|
|
const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
|
|
const manifest = JSON.parse(manifestContent);
|
|
|
|
// Validate required fields in manifest
|
|
if (!manifest.dxt_version) {
|
|
throw new Error('Invalid manifest: missing dxt_version');
|
|
}
|
|
if (!manifest.name) {
|
|
throw new Error('Invalid manifest: missing name');
|
|
}
|
|
if (!manifest.version) {
|
|
throw new Error('Invalid manifest: missing version');
|
|
}
|
|
if (!manifest.server) {
|
|
throw new Error('Invalid manifest: missing server configuration');
|
|
}
|
|
|
|
// Use server name as the final extract directory for automatic version management
|
|
const finalExtractDir = path.join(path.dirname(dxtFilePath), `server-${manifest.name}`);
|
|
|
|
// Clean up any existing version of this server
|
|
cleanupOldDxtServer(manifest.name);
|
|
if (!fs.existsSync(finalExtractDir)) {
|
|
fs.mkdirSync(finalExtractDir, { recursive: true });
|
|
}
|
|
|
|
// Move the temporary directory to the final location
|
|
fs.renameSync(tempExtractDir, finalExtractDir);
|
|
console.log(`DXT server extracted to: ${finalExtractDir}`);
|
|
|
|
// Clean up the uploaded DXT file
|
|
fs.unlinkSync(dxtFilePath);
|
|
|
|
const response: ApiResponse = {
|
|
success: true,
|
|
data: {
|
|
manifest,
|
|
extractDir: finalExtractDir,
|
|
},
|
|
};
|
|
|
|
res.json(response);
|
|
} catch (extractError) {
|
|
// Clean up files on error
|
|
if (fs.existsSync(dxtFilePath)) {
|
|
fs.unlinkSync(dxtFilePath);
|
|
}
|
|
if (fs.existsSync(tempExtractDir)) {
|
|
fs.rmSync(tempExtractDir, { recursive: true, force: true });
|
|
}
|
|
throw extractError;
|
|
}
|
|
} catch (error) {
|
|
console.error('DXT upload error:', error);
|
|
|
|
let message = 'Failed to process DXT file';
|
|
if (error instanceof Error) {
|
|
message = error.message;
|
|
}
|
|
|
|
res.status(500).json({
|
|
success: false,
|
|
message,
|
|
});
|
|
}
|
|
};
|