const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const AdmZip = require('adm-zip'); const processManager = require('./processManager'); const DATA_DIR = path.join(__dirname, '../../data'); const PROJECTS_FILE = path.join(DATA_DIR, 'projects.json'); const LOGS_DIR = path.join(DATA_DIR, 'logs'); const DEPLOY_DIR = path.join(__dirname, '../../projects'); if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } if (!fs.existsSync(LOGS_DIR)) { fs.mkdirSync(LOGS_DIR, { recursive: true }); } if (!fs.existsSync(DEPLOY_DIR)) { fs.mkdirSync(DEPLOY_DIR, { recursive: true }); } const getProjects = () => { if (!fs.existsSync(PROJECTS_FILE)) { fs.writeFileSync(PROJECTS_FILE, JSON.stringify([])); return []; } return JSON.parse(fs.readFileSync(PROJECTS_FILE, 'utf8')); }; const saveProjects = (projects) => { fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2)); }; const addLog = (projectId, message, type = 'info') => { const logFile = path.join(LOGS_DIR, `${projectId}.json`); const logs = fs.existsSync(logFile) ? JSON.parse(fs.readFileSync(logFile, 'utf8')) : []; logs.push({ timestamp: new Date().toISOString(), message, type }); fs.writeFileSync(logFile, JSON.stringify(logs, null, 2)); }; const calculateFileHash = (filePath) => { const content = fs.readFileSync(filePath); return crypto.createHash('md5').update(content).digest('hex'); }; const getAllProjects = () => { return getProjects(); }; const getProjectById = (id) => { const projects = getProjects(); return projects.find(p => p.id === id); }; const createProject = ({ name, description, files }) => { const projects = getProjects(); const id = Date.now().toString(); const projectDir = path.join(DEPLOY_DIR, id); fs.mkdirSync(projectDir, { recursive: true }); const fileInfos = []; files.forEach(file => { const destPath = path.join(projectDir, file.originalname); fs.copyFileSync(file.path, destPath); fs.unlinkSync(file.path); if (file.originalname.endsWith('.zip')) { try { const zip = new AdmZip(destPath); zip.extractAllTo(projectDir, true); fs.unlinkSync(destPath); addLog(id, `Extracted ${file.originalname}`, 'success'); } catch (e) { addLog(id, `Failed to extract ${file.originalname}: ${e.message}`, 'error'); } } else { fileInfos.push({ name: file.originalname, size: file.size, hash: calculateFileHash(destPath) }); } }); const allFiles = getAllFiles(projectDir); const project = { id, name, description, files: allFiles.map(f => ({ name: path.relative(projectDir, f), size: fs.statSync(f).size, hash: calculateFileHash(f) })), status: 'stopped', port: null, url: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), lastDeployed: null }; projects.push(project); saveProjects(projects); addLog(id, `Project "${name}" created with ${allFiles.length} file(s)`, 'success'); return project; }; const getAllFiles = (dir) => { const files = []; const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); if (fs.statSync(fullPath).isDirectory()) { files.push(...getAllFiles(fullPath)); } else { files.push(fullPath); } } return files; }; const updateProject = (id, updates) => { const projects = getProjects(); const index = projects.findIndex(p => p.id === id); if (index === -1) return null; projects[index] = { ...projects[index], ...updates, updatedAt: new Date().toISOString() }; saveProjects(projects); addLog(id, `Project updated: ${JSON.stringify(updates)}`); return projects[index]; }; const deleteProject = (id) => { const projects = getProjects(); const index = projects.findIndex(p => p.id === id); if (index === -1) return false; const project = projects[index]; if (project.status === 'running') { processManager.stopProject(project); } const projectDir = path.join(DEPLOY_DIR, id); if (fs.existsSync(projectDir)) { fs.rmSync(projectDir, { recursive: true, force: true }); } const logFile = path.join(LOGS_DIR, `${id}.json`); if (fs.existsSync(logFile)) { fs.unlinkSync(logFile); } projects.splice(index, 1); saveProjects(projects); return true; }; const addFilesToProject = (id, files) => { const projects = getProjects(); const project = projects.find(p => p.id === id); if (!project) return null; const projectDir = path.join(DEPLOY_DIR, id); const newFileInfos = files.map(file => { const destPath = path.join(projectDir, file.originalname); fs.copyFileSync(file.path, destPath); fs.unlinkSync(file.path); return { name: file.originalname, size: file.size, hash: calculateFileHash(destPath) }; }); project.files = [...project.files, ...newFileInfos]; project.updatedAt = new Date().toISOString(); saveProjects(projects); addLog(id, `Added ${files.length} file(s) to project`); return project; }; const updateProjectStatus = (id, status, port = null, url = null) => { const projects = getProjects(); const project = projects.find(p => p.id === id); if (!project) return null; project.status = status; project.port = port; project.url = url; if (status === 'running') { project.lastDeployed = new Date().toISOString(); addLog(id, `Project started on port ${port}`, 'success'); } else if (status === 'stopped') { addLog(id, 'Project stopped'); } saveProjects(projects); return project; }; const getProjectLogs = (id) => { const logFile = path.join(LOGS_DIR, `${id}.json`); if (!fs.existsSync(logFile)) { return []; } return JSON.parse(fs.readFileSync(logFile, 'utf8')); }; module.exports = { getAllProjects, getProjectById, createProject, updateProject, deleteProject, addFilesToProject, updateProjectStatus, getProjectLogs };