const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const multer = require('multer'); const path = require('path'); const fs = require('fs'); require('dotenv').config(); const app = express(); const PORT = process.env.PORT || 3001; const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-here'; // Middleware app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ extended: true, limit: '50mb' })); // Serve static files app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); // Ensure uploads directory exists const uploadsDir = path.join(__dirname, 'uploads'); if (!fs.existsSync(uploadsDir)) { fs.mkdirSync(uploadsDir, { recursive: true }); } // Multer configuration for file uploads const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, 'uploads/'); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Only image files are allowed')); } } }); // MongoDB Connection mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/pencere-hesaplama', { useNewUrlParser: true, useUnifiedTopology: true, }).then(() => { console.log('Connected to MongoDB'); }).catch(err => { console.error('MongoDB connection error:', err); }); // Schemas const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true }, email: { type: String, required: true, unique: true }, role: { type: String, enum: ['admin', 'user'], default: 'user' }, createdAt: { type: Date, default: Date.now } }); const companySchema = new mongoose.Schema({ name: { type: String, required: true }, logo: { type: String }, address: { type: String }, phone: { type: String }, email: { type: String }, website: { type: String }, description: { type: String }, createdAt: { type: Date, default: Date.now } }); const systemSchema = new mongoose.Schema({ name: { type: String, required: true }, image: { type: String }, parts: [{ name: { type: String, required: true }, type: { type: String, enum: ['yatay', 'dikey', 'cam'], required: true }, quantity: { type: Number, required: true, default: 1 }, reduction: { type: Number, default: 0 }, description: { type: String }, image: { type: String }, advancedFormula: { formula: { type: String }, type: { type: String } }, glassFormulas: { horizontal: { type: String }, horizontalType: { type: String }, vertical: { type: String }, verticalType: { type: String } }, createdAt: { type: Date, default: Date.now } }], createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); const customerSchema = new mongoose.Schema({ name: { type: String, required: true, unique: true }, phone: { type: String }, email: { type: String }, address: { type: String }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); const positionSchema = new mongoose.Schema({ customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: false }, projectId: { type: mongoose.Schema.Types.ObjectId, ref: 'Project' }, systemId: { type: mongoose.Schema.Types.ObjectId, ref: 'System', required: true }, projectName: { type: String }, width: { type: Number, required: true }, height: { type: Number, required: true }, quantity: { type: Number, required: true, default: 1 }, customerName: { type: String }, horizontalParts: [{ name: { type: String }, size: { type: Number }, quantity: { type: Number }, formulaUsed: { type: Boolean } }], verticalParts: [{ name: { type: String }, size: { type: Number }, quantity: { type: Number }, formulaUsed: { type: Boolean } }], glassParts: [{ name: { type: String }, quantity: { type: Number }, size: { type: String }, width: { type: Number }, height: { type: Number }, totalArea: { type: String } }], glassInfo: { width: { type: Number }, height: { type: Number }, totalArea: { type: String } }, pozNumber: { type: Number }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); const projectSchema = new mongoose.Schema({ name: { type: String, required: true }, customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer' }, description: { type: String }, startDate: { type: Date }, endDate: { type: Date }, status: { type: String, enum: ['active', 'completed', 'cancelled'], default: 'active' }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); const pdfSettingsSchema = new mongoose.Schema({ settings: { type: Object, required: true }, type: { type: String, enum: ['global', 'perpage'], required: true }, isActive: { type: Boolean, default: true }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); // Models const User = mongoose.model('User', userSchema); const Company = mongoose.model('Company', companySchema); const System = mongoose.model('System', systemSchema); const Customer = mongoose.model('Customer', customerSchema); const Position = mongoose.model('Position', positionSchema); const Project = mongoose.model('Project', projectSchema); const PDFSettings = mongoose.model('PDFSettings', pdfSettingsSchema); // Authentication Middleware const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Access token required' }); } jwt.verify(token, JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: 'Invalid token' }); } req.user = user; next(); }); }; // Helper Functions const generatePozNumber = async (customerId = null) => { const query = customerId ? { customerId } : { customerId: null }; const lastPosition = await Position.findOne(query).sort({ pozNumber: -1 }); return lastPosition ? lastPosition.pozNumber + 1 : 1; }; // Auth Routes app.post('/api/auth/register', async (req, res) => { try { const { username, email, password } = req.body; if (!username || !email || !password) { return res.status(400).json({ error: 'All fields are required' }); } const existingUser = await User.findOne({ $or: [{ username }, { email }] }); if (existingUser) { return res.status(400).json({ error: 'Username or email already exists' }); } const hashedPassword = await bcrypt.hash(password, 10); const user = new User({ username, email, password: hashedPassword }); await user.save(); const token = jwt.sign( { userId: user._id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '24h' } ); res.status(201).json({ message: 'User created successfully', token, user: { id: user._id, username: user.username, email: user.email, role: user.role } }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/auth/login', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username and password are required' }); } const user = await User.findOne({ username }); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ error: 'Invalid credentials' }); } const token = jwt.sign( { userId: user._id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '24h' } ); res.json({ message: 'Login successful', token, user: { id: user._id, username: user.username, email: user.email, role: user.role } }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Company Routes app.get('/api/company', async (req, res) => { try { let company = await Company.findOne(); if (!company) { company = new Company({ name: 'Firma Adı', createdAt: new Date() }); await company.save(); } res.json(company); } catch (error) { res.status(500).json({ error: error.message }); } }); app.put('/api/company', upload.single('logo'), async (req, res) => { try { const updateData = { ...req.body }; if (req.file) { updateData.logo = `/uploads/${req.file.filename}`; } const company = await Company.findOneAndUpdate( {}, { $set: updateData, updatedAt: new Date() }, { new: true, upsert: true } ); res.json(company); } catch (error) { res.status(500).json({ error: error.message }); } }); // System Routes app.get('/api/systems', async (req, res) => { try { const systems = await System.find().sort({ createdAt: -1 }); res.json(systems); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/systems', upload.single('image'), async (req, res) => { try { const systemData = { ...req.body }; if (req.file) { systemData.image = `/uploads/${req.file.filename}`; } const system = new System(systemData); await system.save(); res.status(201).json(system); } catch (error) { res.status(500).json({ error: error.message }); } }); app.put('/api/systems/:id', upload.single('image'), async (req, res) => { try { const updateData = { ...req.body, updatedAt: new Date() }; if (req.file) { updateData.image = `/uploads/${req.file.filename}`; } const system = await System.findByIdAndUpdate( req.params.id, updateData, { new: true } ); if (!system) { return res.status(404).json({ error: 'System not found' }); } res.json(system); } catch (error) { res.status(500).json({ error: error.message }); } }); app.delete('/api/systems/:id', async (req, res) => { try { const system = await System.findByIdAndDelete(req.params.id); if (!system) { return res.status(404).json({ error: 'System not found' }); } // Delete associated image file if (system.image && fs.existsSync(path.join(__dirname, system.image))) { fs.unlinkSync(path.join(__dirname, system.image)); } res.json({ message: 'System deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Customer Routes app.get('/api/customers', async (req, res) => { try { const customers = await Customer.find().sort({ name: 1 }); res.json(customers); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/customers', async (req, res) => { try { const customer = new Customer(req.body); await customer.save(); res.status(201).json(customer); } catch (error) { if (error.code === 11000) { res.status(400).json({ error: 'Customer name already exists' }); } else { res.status(500).json({ error: error.message }); } } }); app.put('/api/customers/:id', async (req, res) => { try { const customer = await Customer.findByIdAndUpdate( req.params.id, { $set: req.body, updatedAt: new Date() }, { new: true } ); if (!customer) { return res.status(404).json({ error: 'Customer not found' }); } res.json(customer); } catch (error) { if (error.code === 11000) { res.status(400).json({ error: 'Customer name already exists' }); } else { res.status(500).json({ error: error.message }); } } }); app.delete('/api/customers/:id', async (req, res) => { try { const customer = await Customer.findByIdAndDelete(req.params.id); if (!customer) { return res.status(404).json({ error: 'Customer not found' }); } // Delete associated positions await Position.deleteMany({ customerId: req.params.id }); res.json({ message: 'Customer deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Position Routes app.get('/api/positions', async (req, res) => { try { const { customerId } = req.query; const query = customerId ? { customerId } : { customerId: null }; const positions = await Position.find(query) .populate('systemId', 'name image parts') .sort({ createdAt: -1 }); res.json(positions); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/positions', async (req, res) => { try { const positionData = { ...req.body }; if (!positionData.pozNumber) { positionData.pozNumber = await generatePozNumber(positionData.customerId); } const position = new Position(positionData); await position.save(); await position.populate('systemId', 'name image parts'); res.status(201).json(position); } catch (error) { res.status(500).json({ error: error.message }); } }); app.put('/api/positions/:id', async (req, res) => { try { const position = await Position.findByIdAndUpdate( req.params.id, { $set: req.body, updatedAt: new Date() }, { new: true } ).populate('systemId', 'name image parts'); if (!position) { return res.status(404).json({ error: 'Position not found' }); } res.json(position); } catch (error) { res.status(500).json({ error: error.message }); } }); app.delete('/api/positions/:id', async (req, res) => { try { const position = await Position.findByIdAndDelete(req.params.id); if (!position) { return res.status(404).json({ error: 'Position not found' }); } res.json({ message: 'Position deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // PDF Settings Routes app.get('/api/pdf-settings', async (req, res) => { try { const { type } = req.query; const query = type ? { type, isActive: true } : { isActive: true }; const settings = await PDFSettings.findOne(query).sort({ createdAt: -1 }); res.json(settings || { settings: {} }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.put('/api/pdf-settings', async (req, res) => { try { const { type = 'global', settings } = req.body; // Deactivate other settings of the same type await PDFSettings.updateMany({ type }, { isActive: false }); const pdfSettings = new PDFSettings({ type, settings, isActive: true }); await pdfSettings.save(); res.status(201).json(pdfSettings); } catch (error) { res.status(500).json({ error: error.message }); } }); // Backup Routes app.get('/api/backup', async (req, res) => { try { const backup = { timestamp: new Date().toISOString(), systems: await System.find(), customers: await Customer.find(), positions: await Position.find(), company: await Company.findOne(), pdfSettings: await PDFSettings.find(), projects: await Project.find() }; res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', `attachment; filename="backup-${new Date().toISOString().split('T')[0]}.json"`); res.json(backup); } catch (error) { res.status(500).json({ error: error.message }); } }); app.post('/api/restore', async (req, res) => { try { const backup = req.body; // Clear existing data await Promise.all([ System.deleteMany({}), Customer.deleteMany({}), Position.deleteMany({}), Company.deleteMany({}), PDFSettings.deleteMany({}), Project.deleteMany({}) ]); // Restore data if (backup.systems && backup.systems.length > 0) { await System.insertMany(backup.systems); } if (backup.customers && backup.customers.length > 0) { await Customer.insertMany(backup.customers); } if (backup.positions && backup.positions.length > 0) { await Position.insertMany(backup.positions); } if (backup.company) { await Company.create(backup.company); } if (backup.pdfSettings && backup.pdfSettings.length > 0) { await PDFSettings.insertMany(backup.pdfSettings); } if (backup.projects && backup.projects.length > 0) { await Project.insertMany(backup.projects); } res.json({ message: 'Data restored successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Something went wrong!' }); }); // Start server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); console.log(`API documentation: http://localhost:${PORT}/api`); });