const http = require('http'); const crypto = require('crypto'); const express = require('express'); const cors = require('cors'); const { Server } = require('socket.io'); const { db, initialize, getUserByUsername, createInventoryManager, listInventoryManagers, updateInventoryManager, deleteInventoryManager, createVehicle, listVehicles, updateVehicle, deleteVehicle, createUnit, listUnits, updateUnit, deleteUnit, createFuelPersonnel, listFuelPersonnel, updateFuelPersonnel, deleteFuelPersonnel, getVehicleById, getUnitById, getFuelPersonnelById, getInventoryManagerById, createFuelSlip, listFuelSlips, listFuelSlipsByInventoryManager, getFuelSlipById, updateFuelSlipStatus } = require('./db'); const PDFDocument = require('pdfkit'); const app = express(); const PORT = process.env.PORT || 5005; const sessions = new Map(); const httpServer = http.createServer(app); const io = new Server(httpServer, { cors: { origin: '*' } }); const FUEL_FORCES = ['MSB', 'K.K.K.', 'Dz.K.K.', 'Hv.K.K.', 'SGK', 'Gnkur. Bşk.', 'Hrt.Gn.K.']; const fuelManagerSockets = new Map(); const inventoryManagerSockets = new Map(); function addSocket(map, key, socket) { if (!map.has(key)) { map.set(key, new Set()); } map.get(key).add(socket); } function removeSocket(map, key, socket) { const set = map.get(key); if (!set) { return; } set.delete(socket); if (set.size === 0) { map.delete(key); } } function emitToInventoryManager(id, event, payload) { const set = inventoryManagerSockets.get(id); if (!set) { return; } for (const socket of set) { socket.emit(event, payload); } } function emitToFuelManager(id, event, payload) { const set = fuelManagerSockets.get(id); if (!set) { return; } for (const socket of set) { socket.emit(event, payload); } } io.use((socket, next) => { const token = socket.handshake.auth?.token || socket.handshake.query?.token; if (!token || !sessions.has(token)) { return next(new Error('Oturum bulunamadı.')); } socket.sessionToken = token; socket.user = sessions.get(token); return next(); }); io.on('connection', (socket) => { const { user } = socket; if (user.role === 'fuel_manager') { addSocket(fuelManagerSockets, user.id, socket); } else if (user.role === 'inventory_manager') { addSocket(inventoryManagerSockets, user.id, socket); } socket.on('disconnect', () => { if (user.role === 'fuel_manager') { removeSocket(fuelManagerSockets, user.id, socket); } else if (user.role === 'inventory_manager') { removeSocket(inventoryManagerSockets, user.id, socket); } }); }); app.use(cors()); app.use(express.json()); function createSession(user) { const token = crypto.randomBytes(24).toString('hex'); const session = { id: user.id, username: user.username, role: user.role, displayName: user.displayName }; sessions.set(token, session); return { token, session }; } function requireSession(req, res, next) { const token = req.header('x-session-token'); if (!token || !sessions.has(token)) { return res.status(401).json({ message: 'Oturum bulunamadi.' }); } req.session = sessions.get(token); req.sessionToken = token; return next(); } function requireAdmin(req, res, next) { if (req.session.role !== 'admin') { return res.status(403).json({ message: 'Bu islem icin yetki yok.' }); } return next(); } function requireFuelManager(req, res, next) { if (req.session.role !== 'fuel_manager') { return res.status(403).json({ message: 'Bu islem icin yetki yok.' }); } return next(); } function requireInventoryManager(req, res, next) { if (req.session.role !== 'inventory_manager') { return res.status(403).json({ message: 'Bu islem icin yetki yok.' }); } return next(); } app.post('/api/auth/login', async (req, res) => { const { username, password } = req.body || {}; if (!username || !password) { return res.status(400).json({ message: 'Kullanici adi ve sifre zorunlu.' }); } try { const user = await getUserByUsername(username); if (!user || user.password !== password) { return res.status(401).json({ message: 'Kullanici adi veya sifre hatali.' }); } const { token, session } = createSession(user); return res.json({ token, user: session }); } catch (err) { console.error('Login hatasi:', err); return res.status(500).json({ message: 'Beklenmeyen bir sorun olustu.' }); } }); app.post('/api/auth/logout', requireSession, (req, res) => { sessions.delete(req.sessionToken); return res.json({ message: 'Oturum kapatildi.' }); }); app.get('/api/session', requireSession, (req, res) => { return res.json({ user: req.session }); }); app.get('/api/inventory-managers', requireSession, requireAdmin, async (req, res) => { try { const managers = await listInventoryManagers(); return res.json({ managers }); } catch (err) { console.error('Listeleme hatasi:', err); return res.status(500).json({ message: 'Mal sorumlulari okunamadi.' }); } }); app.post('/api/inventory-managers', requireSession, requireAdmin, async (req, res) => { const { username, password, displayName } = req.body || {}; if (!username || !password || !displayName) { return res .status(400) .json({ message: 'Kullanici adi, sifre ve gorunen ad zorunlu alanlardir.' }); } try { const created = await createInventoryManager({ username, password, displayName }); return res.status(201).json({ manager: created }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu kullanici adi zaten mevcut.' }); } console.error('Ekleme hatasi:', err); return res.status(500).json({ message: 'Mal sorumlusu eklenemedi.' }); } }); app.put('/api/inventory-managers/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; const { displayName, password } = req.body || {}; if (!displayName && !password) { return res.status(400).json({ message: 'Guncelleme icin en az bir alan girilmeli.' }); } try { await updateInventoryManager({ id, displayName, password }); return res.json({ message: 'Kayit guncellendi.' }); } catch (err) { console.error('Mal sorumlusu guncelleme hatasi:', err); return res.status(500).json({ message: 'Guncelleme yapilamadi.' }); } }); app.delete('/api/inventory-managers/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; try { await deleteInventoryManager(id); return res.status(204).end(); } catch (err) { console.error('Mal sorumlusu silme hatasi:', err); return res.status(500).json({ message: 'Silme islemi tamamlanamadi.' }); } }); app.get('/api/vehicles', requireSession, requireAdmin, async (req, res) => { try { const vehicles = await listVehicles(); return res.json({ vehicles }); } catch (err) { console.error('Arac listeleme hatasi:', err); return res.status(500).json({ message: 'Araçlar okunamadı.' }); } }); app.post('/api/vehicles', requireSession, requireAdmin, async (req, res) => { const { brand, model, year, plate } = req.body || {}; if (!brand || !model || !year || !plate) { return res.status(400).json({ message: 'Marka, model, yıl ve plaka zorunludur.' }); } const parsedYear = Number(year); if (!Number.isInteger(parsedYear) || parsedYear < 1990 || parsedYear > new Date().getFullYear() + 1) { return res.status(400).json({ message: 'Lütfen geçerli bir model yılı girin.' }); } try { const vehicle = await createVehicle({ brand, model, year: parsedYear, plate: plate.trim().toUpperCase() }); return res.status(201).json({ vehicle }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu plaka ile kayıt zaten mevcut.' }); } console.error('Arac ekleme hatasi:', err); return res.status(500).json({ message: 'Araç kaydedilemedi.' }); } }); app.put('/api/vehicles/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; const { brand, model, year, plate } = req.body || {}; if (!brand || !model || !year || !plate) { return res.status(400).json({ message: 'Tum alanlar zorunlusudur.' }); } const parsedYear = Number(year); if (!Number.isInteger(parsedYear) || parsedYear < 1990 || parsedYear > new Date().getFullYear() + 1) { return res.status(400).json({ message: 'Gecerli bir model yılı girin.' }); } try { await updateVehicle({ id, brand, model, year: parsedYear, plate: plate.trim().toUpperCase() }); return res.json({ message: 'Araç kaydı güncellendi.' }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu plaka ile kayıt mevcut.' }); } console.error('Arac guncelleme hatasi:', err); return res.status(500).json({ message: 'Guncelleme yapilamadi.' }); } }); app.delete('/api/vehicles/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; try { await deleteVehicle(id); return res.status(204).end(); } catch (err) { console.error('Arac silme hatasi:', err); return res.status(500).json({ message: 'Silme islemi tamamlanamadi.' }); } }); app.get('/api/units', requireSession, requireAdmin, async (req, res) => { try { const units = await listUnits(); return res.json({ units }); } catch (err) { console.error('Birlik listeleme hatasi:', err); return res.status(500).json({ message: 'Birlikler okunamadı.' }); } }); app.post('/api/units', requireSession, requireAdmin, async (req, res) => { const { name, address, stk, btk, contactName, contactRank, contactRegistry, contactIdentity, contactPhone } = req.body || {}; if ( !name || !address || !stk || !btk || !contactName || !contactRank || !contactRegistry || !contactIdentity || !contactPhone ) { return res.status(400).json({ message: 'Birlik ve sorumlu bilgileri eksiksiz girilmeli.' }); } try { const unit = await createUnit({ name, address, stk, btk, contactName, contactRank, contactRegistry, contactIdentity, contactPhone }); return res.status(201).json({ unit }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu birlik adı ile kayıt mevcut.' }); } console.error('Birlik ekleme hatasi:', err); return res.status(500).json({ message: 'Birlik kaydedilemedi.' }); } }); app.put('/api/units/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; const { name, address, stk, btk, contactName, contactRank, contactRegistry, contactIdentity, contactPhone } = req.body || {}; if ( !name || !address || !stk || !btk || !contactName || !contactRank || !contactRegistry || !contactIdentity || !contactPhone ) { return res.status(400).json({ message: 'Tum alanlar zorunludur.' }); } try { await updateUnit({ id, name, address, stk, btk, contactName, contactRank, contactRegistry, contactIdentity, contactPhone }); return res.json({ message: 'Birlik kaydı güncellendi.' }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu birlik adı ile kayıt mevcut.' }); } console.error('Birlik guncelleme hatasi:', err); return res.status(500).json({ message: 'Birlik güncellenemedi.' }); } }); app.delete('/api/units/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; try { await deleteUnit(id); return res.status(204).end(); } catch (err) { console.error('Birlik silme hatasi:', err); return res.status(500).json({ message: 'Silme islemi tamamlanamadi.' }); } }); app.get('/api/fuel-personnel', requireSession, requireAdmin, async (req, res) => { try { const personnel = await listFuelPersonnel(); return res.json({ personnel }); } catch (err) { console.error('Personel listeleme hatasi:', err); return res.status(500).json({ message: 'Yakıt personeli okunamadı.' }); } }); app.post('/api/fuel-personnel', requireSession, requireAdmin, async (req, res) => { const { fullName, rank, registryNumber, identityNumber, phone } = req.body || {}; if (!fullName || !rank || !registryNumber || !identityNumber || !phone) { return res.status(400).json({ message: 'Personel bilgileri eksiksiz girilmeli.' }); } try { const created = await createFuelPersonnel({ fullName, rank, registryNumber, identityNumber, phone }); return res.status(201).json({ personnel: created }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu sicil numarası ile kayıt mevcut.' }); } console.error('Personel ekleme hatasi:', err); return res.status(500).json({ message: 'Personel kaydedilemedi.' }); } }); app.put('/api/fuel-personnel/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; const { fullName, rank, registryNumber, identityNumber, phone } = req.body || {}; if (!fullName || !rank || !registryNumber || !identityNumber || !phone) { return res.status(400).json({ message: 'Tum alanlar zorunludur.' }); } try { await updateFuelPersonnel({ id, fullName, rank, registryNumber, identityNumber, phone }); return res.json({ message: 'Personel kaydı güncellendi.' }); } catch (err) { if (err && err.code === 'SQLITE_CONSTRAINT') { return res.status(409).json({ message: 'Bu sicil numarası ile kayıt mevcut.' }); } console.error('Personel guncelleme hatasi:', err); return res.status(500).json({ message: 'Guncelleme yapilamadi.' }); } }); app.delete('/api/fuel-personnel/:id', requireSession, requireAdmin, async (req, res) => { const { id } = req.params; try { await deleteFuelPersonnel(id); return res.status(204).end(); } catch (err) { console.error('Personel silme hatasi:', err); return res.status(500).json({ message: 'Silme islemi tamamlanamadi.' }); } }); app.get('/api/fuel/resources', requireSession, requireFuelManager, async (req, res) => { try { const [vehicles, units, personnel, inventoryManagers] = await Promise.all([ listVehicles(), listUnits(), listFuelPersonnel(), listInventoryManagers() ]); return res.json({ forces: FUEL_FORCES, vehicles, units, personnel, inventoryManagers }); } catch (err) { console.error('Kaynaklar okunamadi:', err); return res.status(500).json({ message: 'Referans verileri yüklenemedi.' }); } }); app.get('/api/fuel-slips', requireSession, requireFuelManager, async (req, res) => { try { const slips = await listFuelSlips(); return res.json({ slips }); } catch (err) { console.error('Fisi listeleme hatasi:', err); return res.status(500).json({ message: 'Fişler okunamadı.' }); } }); app.get('/api/fuel-slips/assigned', requireSession, requireInventoryManager, async (req, res) => { try { const slips = await listFuelSlipsByInventoryManager(req.session.id); return res.json({ slips }); } catch (err) { console.error('Atanan fişleri listeleme hatasi:', err); return res.status(500).json({ message: 'Fişler okunamadı.' }); } }); app.post('/api/fuel-slips', requireSession, requireFuelManager, async (req, res) => { const { date, force, unitId, vehicleId, fuelAmountNumber, fuelAmountText, fuelType, receiverId, giverId, receiverPhone, giverPhone, notes, inventoryManagerId } = req.body || {}; if ( !date || !force || !unitId || !vehicleId || !fuelAmountNumber || !fuelAmountText || !fuelType || !receiverId || !giverId || !inventoryManagerId ) { return res.status(400).json({ message: 'Fiş oluşturmak için zorunlu alanları doldurun.' }); } try { const [unit, vehicle, receiver, giver, inventoryManager] = await Promise.all([ getUnitById(unitId), getVehicleById(vehicleId), getFuelPersonnelById(receiverId), getFuelPersonnelById(giverId), getInventoryManagerById(inventoryManagerId) ]); if (!unit || !vehicle || !receiver || !giver || !inventoryManager) { return res.status(404).json({ message: 'Referans kaydı bulunamadı.' }); } const payload = { date, force, unit: { id: unit.id, name: unit.name }, vehicle: { id: vehicle.id, description: `${vehicle.brand} ${vehicle.model}`.trim(), plate: vehicle.plate }, fuelAmountNumber: Number(fuelAmountNumber), fuelAmountText, fuelType, receiver: { id: receiver.id, fullName: receiver.fullName, rank: receiver.rank, registryNumber: receiver.registryNumber, phone: receiverPhone || receiver.phone }, giver: { id: giver.id, fullName: giver.fullName, rank: giver.rank, registryNumber: giver.registryNumber, phone: giverPhone || giver.phone }, inventoryManager: { id: inventoryManager.id, displayName: inventoryManager.displayName }, fuelManagerId: req.session.id, notes: notes || null }; const created = await createFuelSlip(payload); const slip = await getFuelSlipById(created.id); emitToInventoryManager(slip.inventoryManagerId, 'fuelSlip:new', slip); emitToFuelManager(req.session.id, 'fuelSlip:status', slip); return res.status(201).json({ slip }); } catch (err) { console.error('Fiş olusturma hatasi:', err); return res.status(500).json({ message: 'Fiş olusturulamadı.' }); } }); app.patch('/api/fuel-slips/:id/status', requireSession, requireInventoryManager, async (req, res) => { const { id } = req.params; const { status, reason } = req.body || {}; if (!['approved', 'rejected'].includes(status)) { return res.status(400).json({ message: 'Geçersiz durum değeri.' }); } if (status === 'rejected' && (!reason || !reason.trim())) { return res.status(400).json({ message: 'Red gerekçesi girilmeli.' }); } try { const slip = await getFuelSlipById(id); if (!slip) { return res.status(404).json({ message: 'Fiş bulunamadı.' }); } if (slip.inventoryManagerId !== req.session.id) { return res.status(403).json({ message: 'Bu fiş size atanmamış.' }); } if (slip.status !== 'pending') { return res.status(409).json({ message: 'Fiş durumu zaten güncellenmiş.' }); } const trimmedReason = status === 'rejected' ? reason.trim() : null; await updateFuelSlipStatus({ id, status, rejectionReason: trimmedReason }); const updated = await getFuelSlipById(id); emitToFuelManager(updated.fuelManagerId, 'fuelSlip:status', updated); emitToInventoryManager(updated.inventoryManagerId, 'fuelSlip:status', updated); return res.json({ slip: updated }); } catch (err) { console.error('Fiş durum güncelleme hatasi:', err); return res.status(500).json({ message: 'Durum güncellenemedi.' }); } }); app.get('/api/fuel-slips/:id/pdf', requireSession, requireFuelManager, async (req, res) => { const { id } = req.params; try { const slip = await getFuelSlipById(id); if (!slip) { return res.status(404).json({ message: 'Fiş bulunamadı.' }); } res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `inline; filename=akaryakit-senedi-${slip.slipNumber}.pdf`); const doc = new PDFDocument({ size: 'A4', margin: 36 }); doc.pipe(res); const title = 'TSK AKARYAKIT İKMAL SENEDİ'; doc.fontSize(18).text(title, { align: 'center', underline: true }); doc.moveDown(0.4); doc.fontSize(11).text(`Seri No: ${slip.slipNumber}`, { align: 'center' }); doc.moveDown(0.6); const startX = doc.page.margins.left; let y = doc.y; const tableWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right; const rowHeight = 28; const drawCell = (x, yPos, width, height) => { doc.rect(x, yPos, width, height).stroke(); }; const writeCell = (x, yPos, width, label, value) => { drawCell(x, yPos, width, rowHeight); doc.fontSize(9).font('Helvetica-Bold').text(label, x + 6, yPos + 6, { width: width - 12 }); if (value) { doc.fontSize(10).font('Helvetica').text(value, x + 6, yPos + 14, { width: width - 12 }); } }; const halfWidth = tableWidth / 2; writeCell(startX, y, halfWidth, 'Tarih', new Date(slip.slipDate).toLocaleDateString('tr-TR')); writeCell(startX + halfWidth, y, halfWidth, 'Fiş No', String(slip.slipNumber).padStart(4, '0')); y += rowHeight; writeCell(startX, y, tableWidth, 'Aracın Ait Olduğu Kuvvet', slip.force); y += rowHeight; writeCell(startX, y, tableWidth, 'Aracın Ait Olduğu Birlik', slip.unitName); y += rowHeight; writeCell(startX, y, tableWidth, 'Aracın Cinsi', slip.vehicleDescription); y += rowHeight; writeCell(startX, y, tableWidth, 'Aracın Plakası', slip.plate); y += rowHeight; writeCell(startX, y, halfWidth, 'Yakıt Miktarı (Rakam)', `${slip.fuelAmountNumber}`); writeCell(startX + halfWidth, y, halfWidth, 'Yakıt Miktarı (Yazı)', slip.fuelAmountText); y += rowHeight; writeCell(startX, y, halfWidth, 'Yakıt Cinsi', slip.fuelType); writeCell(startX + halfWidth, y, halfWidth, 'Not', slip.notes || '-'); y += rowHeight; writeCell(startX, y, halfWidth, 'Teslim Alan', `${slip.receiverName}\n${slip.receiverRank} / ${slip.receiverRegistry}`); writeCell(startX + halfWidth, y, halfWidth, 'Teslim Eden', `${slip.giverName}\n${slip.giverRank} / ${slip.giverRegistry}`); y += rowHeight; writeCell(startX, y, halfWidth, 'Teslim Alan Telefonu', slip.receiverPhone); writeCell(startX + halfWidth, y, halfWidth, 'Teslim Eden Telefonu', slip.giverPhone); y += rowHeight; doc.moveDown(2); doc.fontSize(9).text('Not: Seri numarası yıl + sıra şeklinde takip edilir. Bu fiş sistem üzerinden oluşturulmuştur.', { align: 'center' }); doc.end(); } catch (err) { console.error('PDF olusturma hatasi:', err); return res.status(500).json({ message: 'PDF oluşturulamadı.' }); } }); initialize() .then(() => { httpServer.listen(PORT, () => { console.log(`Sunucu ${PORT} portunda hazir.`); }); }) .catch((err) => { console.error('Sunucu baslatilamadi:', err); process.exit(1); }); process.on('SIGINT', () => { console.log('Sunucu kapatiliyor...'); io.close(); db.close(() => { httpServer.close(() => { process.exit(0); }); }); });