Spaces:
Sleeping
Sleeping
| 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, | |
| }); | |
| } | |
| }; | |