246 lines
7.8 KiB
JavaScript
246 lines
7.8 KiB
JavaScript
import http from 'http';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const PORT = process.env.PORT || 8080;
|
|
const DIST_DIR = path.join(__dirname, 'dist');
|
|
const CONFIG_DIR = path.join(__dirname, 'data');
|
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
|
|
// Ensure config dir exists
|
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
}
|
|
|
|
const MIME_TYPES = {
|
|
'.html': 'text/html',
|
|
'.css': 'text/css',
|
|
'.js': 'text/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
};
|
|
|
|
const server = http.createServer((req, res) => {
|
|
// CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(204);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
const pathname = parsedUrl.pathname;
|
|
|
|
// API Config GET
|
|
if (pathname === '/api/config' && req.method === 'GET') {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
try {
|
|
const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
res.end(data);
|
|
return;
|
|
} catch (err) {
|
|
// Fallback
|
|
}
|
|
}
|
|
res.end(JSON.stringify({}));
|
|
return;
|
|
}
|
|
|
|
// API Config POST
|
|
if (pathname === '/api/config' && req.method === 'POST') {
|
|
let body = '';
|
|
req.on('data', chunk => {
|
|
body += chunk.toString();
|
|
});
|
|
req.on('end', () => {
|
|
try {
|
|
const parsed = JSON.parse(body);
|
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(parsed, null, 2), 'utf-8');
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: true }));
|
|
} catch (err) {
|
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: 'Invalid JSON or write error' }));
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// --- CATEGORIES CRUD ---
|
|
const CATEGORIES_FILE = path.join(CONFIG_DIR, 'categories.json');
|
|
const readCategories = () => {
|
|
if (fs.existsSync(CATEGORIES_FILE)) {
|
|
try { return JSON.parse(fs.readFileSync(CATEGORIES_FILE, 'utf-8')); } catch (e) {}
|
|
}
|
|
return [];
|
|
};
|
|
|
|
if (pathname === '/api/categories') {
|
|
if (req.method === 'GET') {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(readCategories()));
|
|
return;
|
|
}
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT') {
|
|
let body = '';
|
|
req.on('data', chunk => body += chunk.toString());
|
|
req.on('end', () => {
|
|
try {
|
|
const category = JSON.parse(body);
|
|
let categories = readCategories();
|
|
|
|
if (req.method === 'POST') {
|
|
category.id = Date.now().toString();
|
|
categories.push(category);
|
|
} else {
|
|
const index = categories.findIndex(c => c.id === category.id);
|
|
if (index !== -1) categories[index] = { ...categories[index], ...category };
|
|
}
|
|
|
|
fs.writeFileSync(CATEGORIES_FILE, JSON.stringify(categories, null, 2));
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(category));
|
|
} catch (err) {
|
|
res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid payload' }));
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (req.method === 'DELETE') {
|
|
const id = parsedUrl.searchParams.get('id');
|
|
if (id) {
|
|
let categories = readCategories();
|
|
categories = categories.filter(c => c.id !== id);
|
|
fs.writeFileSync(CATEGORIES_FILE, JSON.stringify(categories, null, 2));
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: true }));
|
|
} else {
|
|
res.writeHead(400); res.end(JSON.stringify({ error: 'Missing id' }));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// --- END CATEGORIES CRUD ---
|
|
|
|
// --- STANDARDS CRUD ---
|
|
const STANDARDS_FILE = path.join(CONFIG_DIR, 'standards.json');
|
|
const readStandards = () => {
|
|
if (fs.existsSync(STANDARDS_FILE)) {
|
|
try { return JSON.parse(fs.readFileSync(STANDARDS_FILE, 'utf-8')); } catch (e) {}
|
|
}
|
|
return [];
|
|
};
|
|
|
|
if (pathname === '/api/standards') {
|
|
if (req.method === 'GET') {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
const allStandards = readStandards();
|
|
const catId = parsedUrl.searchParams.get('categoryId');
|
|
if (catId) {
|
|
res.end(JSON.stringify(allStandards.filter(s => s.categoria_id === catId)));
|
|
} else {
|
|
res.end(JSON.stringify(allStandards));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (req.method === 'POST' || req.method === 'PUT') {
|
|
let body = '';
|
|
req.on('data', chunk => body += chunk.toString());
|
|
req.on('end', () => {
|
|
try {
|
|
const standard = JSON.parse(body);
|
|
|
|
// Sanitização numérica para parametros_controle
|
|
if (standard.parametros_controle) {
|
|
for (const key of Object.keys(standard.parametros_controle)) {
|
|
const grupo = standard.parametros_controle[key];
|
|
if (typeof grupo === 'object' && grupo !== null) {
|
|
for (const prop of Object.keys(grupo)) {
|
|
const val = grupo[prop];
|
|
if (val !== null && val !== undefined && val !== '') {
|
|
const num = parseFloat(val);
|
|
if (!isNaN(num)) {
|
|
grupo[prop] = num;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let standards = readStandards();
|
|
if (req.method === 'POST') {
|
|
standard.id = Date.now().toString();
|
|
standards.push(standard);
|
|
} else {
|
|
const index = standards.findIndex(s => s.id === standard.id);
|
|
if (index !== -1) standards[index] = { ...standards[index], ...standard };
|
|
}
|
|
fs.writeFileSync(STANDARDS_FILE, JSON.stringify(standards, null, 2));
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(standard));
|
|
} catch (err) {
|
|
res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid payload' }));
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (req.method === 'DELETE') {
|
|
const id = parsedUrl.searchParams.get('id');
|
|
if (id) {
|
|
let standards = readStandards();
|
|
standards = standards.filter(s => s.id !== id);
|
|
fs.writeFileSync(STANDARDS_FILE, JSON.stringify(standards, null, 2));
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: true }));
|
|
} else {
|
|
res.writeHead(400); res.end(JSON.stringify({ error: 'Missing id' }));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// --- END STANDARDS CRUD ---
|
|
|
|
// Serve static files
|
|
let filePath = path.join(DIST_DIR, pathname === '/' ? 'index.html' : pathname);
|
|
|
|
// If path doesn't exist, fallback to index.html (SPA routing)
|
|
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
|
|
filePath = path.join(DIST_DIR, 'index.html');
|
|
}
|
|
|
|
const ext = path.extname(filePath);
|
|
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
|
|
fs.readFile(filePath, (err, content) => {
|
|
if (err) {
|
|
res.writeHead(500);
|
|
res.end('Server Error');
|
|
} else {
|
|
res.writeHead(200, { 'Content-Type': contentType });
|
|
res.end(content);
|
|
}
|
|
});
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
});
|