auto-deploy-demo/server/services/projectService.js

303 lines
7.5 KiB
JavaScript
Raw Normal View History

2026-02-23 06:31:59 +00:00
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const AdmZip = require('adm-zip');
const processManager = require('./processManager');
const nginxManager = require('./nginxManager');
const config = require('../config');
2026-02-23 06:31:59 +00:00
// Use absolute paths for container environment
const DATA_DIR = '/app/data';
2026-02-23 06:31:59 +00:00
const PROJECTS_FILE = path.join(DATA_DIR, 'projects.json');
const LOGS_DIR = path.join(DATA_DIR, 'logs');
const DEPLOY_DIR = '/app/projects';
2026-02-23 06:31:59 +00:00
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 = () => {
// Ensure directory exists before writing
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
2026-02-23 06:31:59 +00:00
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 fixHtmlPaths = (projectDir) => {
const htmlFiles = getAllFiles(projectDir).filter(f => f.endsWith('.html'));
for (const htmlFile of htmlFiles) {
let content = fs.readFileSync(htmlFile, 'utf8');
let modified = false;
// Fix absolute paths in HTML attributes
// src="/assets/..." -> src="./assets/..."
// href="/assets/..." -> href="./assets/..."
// Also fix other common static resource paths
const patterns = [
{ regex: /src="\/(?!\/|http)/g, replacement: 'src="./' },
{ regex: /href="\/(?!\/|http)/g, replacement: 'href="./' },
{ regex: /src='\/(?!\/|http)/g, replacement: "src='./" },
{ regex: /href='\/(?!\/|http)/g, replacement: "href='./" },
];
for (const { regex, replacement } of patterns) {
if (regex.test(content)) {
content = content.replace(regex, replacement);
modified = true;
}
}
if (modified) {
fs.writeFileSync(htmlFile, content);
console.log(`Fixed paths in ${htmlFile}`);
}
}
};
2026-02-23 06:31:59 +00:00
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);
// Fix absolute paths in HTML files to relative paths
fixHtmlPaths(projectDir);
2026-02-23 06:31:59 +00:00
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);
}
if (config.useNginx === true || config.useNginx === 'true') {
nginxManager.removeProjectLocation(id);
}
2026-02-23 06:31:59 +00:00
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 || config.getProjectUrl(id, port);
2026-02-23 06:31:59 +00:00
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
};