Files

238 lines
8.0 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
import multer from 'multer';
import fs from 'fs';
// Load environment variables
dotenv.config();
// Import database and routes
import pool, { initDatabase } from './database.js';
import studentsRouter from './routes/students.js';
import violationsRouter from './routes/violations.js';
import achievementsRouter from './routes/achievements.js';
import settingsRouter from './routes/settings.js';
import usersRouter from './routes/users.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3007;
// Middleware
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Static files - serve from dist folder (built frontend)
app.use(express.static(path.join(__dirname, '../dist')));
// Also serve public folder for images
app.use('/images', express.static(path.join(__dirname, '../public/images')));
// File upload configuration
const uploadDir = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
// Create monthly folder
const date = new Date();
const monthNames = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
const monthFolder = `${monthNames[date.getMonth()]} ${date.getFullYear()}`;
const targetDir = path.join(uploadDir, monthFolder);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
cb(null, targetDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, `violation-${uniqueSuffix}${ext}`);
}
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limit
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed.'));
}
}
});
// Serve uploaded files
app.use('/uploads', express.static(uploadDir));
// API Routes
app.use('/api/students', studentsRouter);
app.use('/api/violations', violationsRouter);
app.use('/api/achievements', achievementsRouter);
app.use('/api/settings', settingsRouter);
app.use('/api/users', usersRouter);
// File upload endpoint
app.post('/api/upload', upload.single('photo'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
// Build the relative path for the uploaded file
const relativePath = req.file.path.replace(uploadDir, '').replace(/\\/g, '/');
const photoUrl = `/uploads${relativePath}`;
res.json({
status: 'success',
photoUrl,
filename: req.file.filename
});
} catch (error) {
console.error('Upload error:', error);
res.status(500).json({ error: 'Failed to upload file' });
}
});
// Base64 image upload (for compatibility with existing frontend)
app.post('/api/upload-base64', async (req, res) => {
try {
const { photoBase64, violationId } = req.body;
if (!photoBase64) {
return res.status(400).json({ error: 'No image data provided' });
}
// Extract base64 data
const matches = photoBase64.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
if (!matches || matches.length !== 3) {
return res.status(400).json({ error: 'Invalid base64 image format' });
}
const type = matches[1];
const data = matches[2];
const buffer = Buffer.from(data, 'base64');
// Determine file extension
let ext = '.jpg';
if (type.includes('png')) ext = '.png';
else if (type.includes('gif')) ext = '.gif';
else if (type.includes('webp')) ext = '.webp';
// Create monthly folder
const date = new Date();
const monthNames = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
const monthFolder = `${monthNames[date.getMonth()]} ${date.getFullYear()}`;
const targetDir = path.join(uploadDir, monthFolder);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Generate filename
const filename = `violation-${violationId || Date.now()}${ext}`;
const filePath = path.join(targetDir, filename);
// Write file
fs.writeFileSync(filePath, buffer);
// Build URL
const photoUrl = `/uploads/${monthFolder}/${filename}`;
res.json({
status: 'success',
photoUrl,
filename
});
} catch (error) {
console.error('Base64 upload error:', error);
res.status(500).json({ error: 'Failed to save image' });
}
});
// Health check endpoint with DB verification
app.get('/api/health', async (req, res) => {
try {
// Test database connection
const [rows] = await pool.execute('SELECT 1 as connection_test');
res.json({
status: 'ok',
server: 'SIPASI API Server',
version: '2.0.0',
database: {
status: 'connected',
type: 'MySQL',
ping: 'ok'
},
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({
status: 'error',
server: 'SIPASI API Server',
database: {
status: 'disconnected',
error: error.message
},
timestamp: new Date().toISOString()
});
}
});
// SPA fallback - serve index.html for all non-API routes
app.use((req, res, next) => {
// Skip API routes
if (req.path.startsWith('/api')) {
return next();
}
// Serve index.html for client-side routing
res.sendFile(path.join(__dirname, '../dist/index.html'));
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).json({ error: err.message || 'Internal server error' });
});
// Initialize database and start server
async function startServer() {
try {
await initDatabase();
app.listen(PORT, () => {
console.log(`
╔════════════════════════════════════════════════════╗
║ ║
║ 🏫 SIPASI - Sistem Informasi Pelanggaran Siswa ║
║ SMA Negeri 1 Abiansemal ║
║ ║
║ ✅ Server running on port ${PORT}
║ 📡 API: http://localhost:${PORT}/api ║
║ 🌐 Web: http://localhost:${PORT}
║ ║
╚════════════════════════════════════════════════════╝
`);
});
} catch (error) {
console.error('❌ Failed to start server:', error);
process.exit(1);
}
}
startServer();