import express from 'express'; import session from 'express-session'; import { createServer } from 'http'; import { Server } from 'socket.io'; import sqlite3 from 'sqlite3'; import bcrypt from 'bcrypt'; import { promises as fs } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { getGoodsManagers, addGoodsManager, updateGoodsManager, deleteGoodsManager } from './lib/data/goodsManagers.js'; // Units veritabanı let units = [ { id: 1, name: '1. Motorlu Piyade Tugayı', address: 'Mecidiyeköy, Şişli/İstanbul', stk: 'STK-12345', btk: 'BTK-67890', commander: { full_name: 'Mehmet Yılmaz', rank: 'Yüzbaşı', registration_number: '123456', tc_kimlik: '12345678901', phone: '05321234567' }, created_at: new Date().toISOString() }, { id: 2, name: '2. Zırhlı Tabur', address: 'Havran, Balıkesir', stk: 'STK-54321', btk: 'BTK-09876', commander: { full_name: 'Ali Kaya', rank: 'Binbaşı', registration_number: '654321', tc_kimlik: '98765432109', phone: '05337654321' }, created_at: new Date().toISOString() }, { id: 3, name: '3. Komutanlık', address: 'Çankaya, Ankara', stk: 'STK-11111', btk: 'BTK-22222', commander: { full_name: 'Hasan Demir', rank: 'Üsteğmen', registration_number: '111111', tc_kimlik: '11111111111', phone: '05321111111' }, created_at: new Date().toISOString() } ]; let nextUnitId = 4; // Vehicles veritabanı let vehicles = [ { id: 1, brand: 'Mercedes-Benz', model: 'Actros 1851', year: 2020, plate: '34 ABC 123', fuel_type: 'Dizel', fuel_capacity: 500, current_fuel: 300, status: 'Aktif', driver_id: 3, driver_name: 'Ali Veli', created_at: new Date().toISOString() }, { id: 2, brand: 'Volvo', model: 'FH16 750', year: 2021, plate: '34 XYZ 789', fuel_type: 'Dizel', fuel_capacity: 600, current_fuel: 450, status: 'Aktif', driver_id: 4, driver_name: 'İbrahim Kara', created_at: new Date().toISOString() }, { id: 3, brand: 'Scania', model: 'R730', year: 2019, plate: '34 DEF 456', fuel_type: 'Dizel', fuel_capacity: 550, current_fuel: 200, status: 'Bakımda', driver_id: null, driver_name: null, created_at: new Date().toISOString() } ]; let nextVehicleId = 4; // Fuel Personnel veritabanı let fuelPersonnel = [ { id: 1, full_name: 'Ahmet Demir', rank: 'Üsteğmen', registration_number: '111222', tc_kimlik: '11111111111', phone: '05321112233', is_active: true, created_at: new Date().toISOString() }, { id: 2, full_name: 'Mustafa Çelik', rank: 'Astsubay', registration_number: '333444', tc_kimlik: '22222222222', phone: '05334455566', is_active: true, created_at: new Date().toISOString() } ]; let nextFuelPersonnelId = 3; const app = express(); const server = createServer(app); const io = new Server(server, { cors: { origin: "http://localhost:5173", methods: ["GET", "POST"] } }); // Export io for use in other modules // export { io }; // Commented out to avoid circular dependency const PORT = process.env.PORT || 3000; // ES Module equivalent of __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Database path setup - ensure /db directory exists and use it const projectRoot = dirname(__dirname); // Go up from src/ to project root const dbDir = join(projectRoot, 'db'); const dbPath = join(dbDir, 'yakit_takip.db'); // Middleware app.use(express.json()); // Note: static file serving removed - SvelteKit handles frontend in development // Session middleware app.use(session({ secret: 'yakit-takip-modulu-secret-key-2023', resave: false, saveUninitialized: false, cookie: { secure: false, // development için false maxAge: 24 * 60 * 60 * 1000 // 24 saat } })); // Ensure /db directory exists async function ensureDbDirectory() { try { await fs.mkdir(dbDir, { recursive: true }); console.log(`📁 Database directory ensured: ${dbDir}`); } catch (error) { console.error('❌ Error creating database directory:', error); throw error; } } // Veritabanı değişkeni (başlangıçta null) let db; // Veritabanı bağlantısını oluştur async function createDatabaseConnection() { return new Promise((resolve, reject) => { db = new sqlite3.Database(dbPath, (err) => { if (err) { console.error('❌ Database connection error:', err); reject(err); } else { console.log('✅ Database connection established'); resolve(); } }); }); } // Veritabanı tablolarını oluştur async function initializeDatabase() { return new Promise((resolve, reject) => { db.serialize(() => { // Kullanıcılar tablosu db.run(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, role TEXT NOT NULL, full_name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1 )`, (err) => { if (err) reject(err); }); // Yakıt fişleri tablosu db.run(`CREATE TABLE IF NOT EXISTS fuel_slips ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, force_command TEXT NOT NULL, unit_id INTEGER, unit_name TEXT, vehicle_id INTEGER, vehicle_info TEXT, fuel_type TEXT NOT NULL, liters REAL NOT NULL, km INTEGER, personnel_id INTEGER, personnel_info TEXT, goods_manager_id INTEGER, goods_manager_info TEXT, fuel_manager_id INTEGER, fuel_manager_info TEXT, status TEXT DEFAULT 'pending', notes TEXT, approval_date TEXT, approval_notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, (err) => { if (err) reject(err); }); // Tablolar oluşturulduktan sonra kullanıcıları ekle setTimeout(async () => { // Örnek kullanıcıları ekle const users = [ { username: 'admin', password: 'admin123', role: 'admin', full_name: 'Sistem Yöneticisi' }, { username: 'fuel', password: 'fuel123', role: 'fuel_manager', full_name: 'Yakıt Sorumlusu' }, { username: 'goods', password: 'goods123', role: 'goods_manager', full_name: 'Mal Sorumlusu' }, { username: 'ibrahim_kara', password: 'kara123', role: 'goods_manager', full_name: 'İbrahim Kara' } ]; // Her kullanıcıyı kontrol et ve yoksa ekle for (const user of users) { const hashedPassword = await bcrypt.hash(user.password, 10); db.get('SELECT id FROM users WHERE username = ?', [user.username], (err, row) => { if (!row) { db.run('INSERT INTO users (username, password, role, full_name) VALUES (?, ?, ?, ?)', [user.username, hashedPassword, user.role, user.full_name]); } }); } resolve(); }, 100); }); }); } // API Routes // Login endpoint app.post('/api/login', async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ message: 'Kullanıcı adı ve şifre gerekli.' }); } try { db.get('SELECT * FROM users WHERE username = ? AND is_active = 1', [username], async (err, user) => { if (err) { return res.status(500).json({ message: 'Veritabanı hatası.' }); } if (!user) { return res.status(401).json({ message: 'Kullanıcı bulunamadı.' }); } const passwordMatch = await bcrypt.compare(password, user.password); if (!passwordMatch) { return res.status(401).json({ message: 'Şifre hatalı.' }); } // Session oluştur let sessionUser = { id: user.id, username: user.username, role: user.role, full_name: user.full_name }; // Eğer goods_manager ise, goods_managers API'den gerçek ID'yi al if (user.role === 'goods_manager') { try { const goodsManagersRes = await fetch('http://localhost:3000/api/goods-managers'); if (goodsManagersRes.ok) { const goodsData = await goodsManagersRes.json(); const goodsManager = goodsData.goodsManagers?.find(gm => gm.username === user.username); if (goodsManager) { sessionUser.id = goodsManager.id; // goods_manager ID'sini kullan console.log(`✅ Goods manager logged in: ${user.full_name} (ID: ${goodsManager.id})`); } } } catch (fetchError) { console.warn('⚠️ Could not fetch goods manager ID:', fetchError); } } req.session.user = sessionUser; res.json({ message: 'Giriş başarılı.', user: sessionUser }); }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Logout endpoint app.post('/api/logout', (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json({ message: 'Çıkış yapılamadı.' }); } res.json({ message: 'Çıkış başarılı.' }); }); }); // Mevcut kullanıcı bilgisi app.get('/api/user', (req, res) => { if (!req.session.user) { return res.status(401).json({ message: 'Oturum bulunamadı.' }); } res.json({ user: req.session.user }); }); // Socket.IO bildirim endpoint'i app.post('/api/socket-notify', (req, res) => { const { event, data } = req.body; if (!event || !data) { return res.status(400).json({ message: 'Event ve data zorunludur.' }); } // Socket.IO ile olay yayınla console.log(`📢 Socket.IO event emitted: ${event}`, data); io.emit(event, data); res.json({ message: 'Bildirim gönderildi.' }); }); // Tüm kullanıcıları getir (sadece admin) app.get('/api/users', (req, res) => { if (!req.session.user || req.session.user.role !== 'admin') { return res.status(403).json({ message: 'Yetkisiz erişim.' }); } db.all('SELECT id, username, role, full_name, created_at, is_active FROM users', (err, users) => { if (err) { return res.status(500).json({ message: 'Veritabanı hatası.' }); } res.json({ users }); }); }); // Units API endpoint'leri // GET - Tüm birlikleri listele app.get('/api/units', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) res.json({ units }); }); // POST - Yeni birlik ekle app.post('/api/units', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { name, address, stk, btk, commander } = req.body; // Validasyon if (!name || !address || !stk || !btk || !commander) { return res.status(400).json({ message: 'Tüm alanlar zorunludur.' }); } // Komutan validasyonu const { full_name, rank, registration_number, tc_kimlik, phone } = commander; if (!full_name || !rank || !registration_number || !tc_kimlik || !phone) { return res.status(400).json({ message: 'Birlik sorumlusunun tüm bilgileri zorunludur.' }); } // TC Kimlik numarası validasyonu if (!/^[0-9]{11}$/.test(tc_kimlik)) { return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' }); } // Yeni birlik oluştur const newUnit = { id: nextUnitId++, name: name.trim(), address: address.trim(), stk: stk.trim().toUpperCase(), btk: btk.trim().toUpperCase(), commander: { full_name: full_name.trim(), rank: rank.trim(), registration_number: registration_number.trim(), tc_kimlik: tc_kimlik.trim(), phone: phone.trim() }, created_at: new Date().toISOString() }; units.push(newUnit); res.json({ message: 'Birlik başarıyla eklendi.', unit: newUnit }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // PUT - Birlik güncelle app.put('/api/units', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id, name, address, stk, btk, commander } = req.body; // Validasyon if (!id || !name || !address || !stk || !btk || !commander) { return res.status(400).json({ message: 'Tüm alanlar zorunludur.' }); } // Komutan validasyonu const { full_name, rank, registration_number, tc_kimlik, phone } = commander; if (!full_name || !rank || !registration_number || !tc_kimlik || !phone) { return res.status(400).json({ message: 'Birlik sorumlusunun tüm bilgileri zorunludur.' }); } // TC Kimlik numarası validasyonu if (!/^[0-9]{11}$/.test(tc_kimlik)) { return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' }); } // Birlik bul const unitIndex = units.findIndex(u => u.id === parseInt(id)); if (unitIndex === -1) { return res.status(404).json({ message: 'Birlik bulunamadı.' }); } // Birlik güncelle units[unitIndex] = { ...units[unitIndex], name: name.trim(), address: address.trim(), stk: stk.trim().toUpperCase(), btk: btk.trim().toUpperCase(), commander: { full_name: full_name.trim(), rank: rank.trim(), registration_number: registration_number.trim(), tc_kimlik: tc_kimlik.trim(), phone: phone.trim() } }; res.json({ message: 'Birlik başarıyla güncellendi.', unit: units[unitIndex] }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // DELETE - Birlik sil app.delete('/api/units', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id } = req.body; if (!id) { return res.status(400).json({ message: 'Birlik ID zorunludur.' }); } // Birlik bul const unitIndex = units.findIndex(u => u.id === parseInt(id)); if (unitIndex === -1) { return res.status(404).json({ message: 'Birlik bulunamadı.' }); } // Birlik sil const deletedUnit = units.splice(unitIndex, 1)[0]; res.json({ message: 'Birlik başarıyla silindi.', unit: deletedUnit }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Goods Managers API endpoint'leri // GET - Tüm mal sorumlularını listele app.get('/api/goods-managers', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const goodsManagers = getGoodsManagers(); res.json({ goodsManagers }); } catch (error) { res.status(500).json({ message: 'Mal sorumluları alınırken hata oluştu.' }); } }); // POST - Yeni mal sorumlusu ekle app.post('/api/goods-managers', async (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { full_name, rank, registration_number, tc_kimlik, phone, unit_id, username, password, is_active = true } = req.body; const newManager = addGoodsManager({ full_name, rank, registration_number, tc_kimlik, phone, unit_id, unit_name: units.find(u => u.id === parseInt(unit_id))?.name || 'Bilinmeyen Birlik', username, password, is_active }); res.json({ message: 'Personel başarıyla eklendi.', goodsManager: newManager }); } catch (error) { if (error.message.includes('zorunludur') || error.message.includes('zaten kayıtlı') || error.message.includes('Geçersiz') || error.message.includes('karakter')) { return res.status(400).json({ message: error.message }); } res.status(500).json({ message: 'Sunucu hatası.' }); } }); // PUT - Mal sorumlusu güncelle app.put('/api/goods-managers', async (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id, full_name, rank, registration_number, tc_kimlik, phone, unit_id, username, password, is_active } = req.body; const updatedManager = updateGoodsManager(id, { full_name, rank, registration_number, tc_kimlik, phone, unit_id, unit_name: units.find(u => u.id === parseInt(unit_id))?.name || 'Bilinmeyen Birlik', username, password, is_active }); res.json({ message: 'Personel başarıyla güncellendi.', goodsManager: updatedManager }); } catch (error) { if (error.message.includes('zorunludur') || error.message.includes('bulunamadı') || error.message.includes('zaten kayıtlı') || error.message.includes('Geçersiz') || error.message.includes('karakter')) { return res.status(error.message.includes('bulunamadı') ? 404 : 400).json({ message: error.message }); } res.status(500).json({ message: 'Sunucu hatası.' }); } }); // DELETE - Mal sorumlusu sil app.delete('/api/goods-managers', async (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id } = req.body; if (!id) { return res.status(400).json({ message: 'Mal sorumlusu ID zorunludur.' }); } const deletedManager = deleteGoodsManager(id); res.json({ message: 'Mal sorumlusu başarıyla silindi.', goodsManager: deletedManager }); } catch (error) { if (error.message.includes('bulunamadı')) { return res.status(404).json({ message: error.message }); } res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Fuel Personnel API endpoint'leri // GET - Tüm yakıt personelini listele app.get('/api/fuel-personnel', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) res.json({ fuelPersonnel }); }); // POST - Yeni yakıt personeli ekle app.post('/api/fuel-personnel', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { full_name, rank, registration_number, tc_kimlik, phone, is_active = true } = req.body; // Validasyon if (!full_name || !rank || !registration_number || !tc_kimlik || !phone) { return res.status(400).json({ message: 'Tüm alanlar zorunludur.' }); } // TC Kimlik numarası validasyonu if (!/^[0-9]{11}$/.test(tc_kimlik)) { return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' }); } // Sicil numarası tekrar kontrolü const existingPersonnel = fuelPersonnel.find(p => p.registration_number === registration_number ); if (existingPersonnel) { return res.status(400).json({ message: 'Bu sicil numarası zaten kayıtlı.' }); } // TC Kimlik numarası tekrar kontrolü const existingTc = fuelPersonnel.find(p => p.tc_kimlik === tc_kimlik ); if (existingTc) { return res.status(400).json({ message: 'Bu TC Kimlik numarası zaten kayıtlı.' }); } // Yeni personel oluştur const newPersonnel = { id: nextFuelPersonnelId++, full_name: full_name.trim(), rank: rank.trim(), registration_number: registration_number.trim(), tc_kimlik: tc_kimlik.trim(), phone: phone.trim(), is_active: Boolean(is_active), created_at: new Date().toISOString() }; fuelPersonnel.push(newPersonnel); res.json({ message: 'Personel başarıyla eklendi.', fuelPersonnel: newPersonnel }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // PUT - Yakıt personeli güncelle app.put('/api/fuel-personnel', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id, full_name, rank, registration_number, tc_kimlik, phone, is_active } = req.body; // Validasyon if (!id || !full_name || !rank || !registration_number || !tc_kimlik || !phone) { return res.status(400).json({ message: 'Tüm alanlar zorunludur.' }); } // TC Kimlik numarası validasyonu if (!/^[0-9]{11}$/.test(tc_kimlik)) { return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' }); } // Personel bul const personnelIndex = fuelPersonnel.findIndex(p => p.id === parseInt(id)); if (personnelIndex === -1) { return res.status(404).json({ message: 'Personel bulunamadı.' }); } // Sicil numarası tekrar kontrolü (başka personel için) const existingRegNumber = fuelPersonnel.find(p => p.id !== parseInt(id) && p.registration_number === registration_number ); if (existingRegNumber) { return res.status(400).json({ message: 'Bu sicil numarası başka bir personelde kayıtlı.' }); } // TC Kimlik numarası tekrar kontrolü (başka personel için) const existingTc = fuelPersonnel.find(p => p.id !== parseInt(id) && p.tc_kimlik === tc_kimlik ); if (existingTc) { return res.status(400).json({ message: 'Bu TC Kimlik numarası başka bir personelde kayıtlı.' }); } // Personeli güncelle fuelPersonnel[personnelIndex] = { ...fuelPersonnel[personnelIndex], full_name: full_name.trim(), rank: rank.trim(), registration_number: registration_number.trim(), tc_kimlik: tc_kimlik.trim(), phone: phone.trim(), is_active: Boolean(is_active) }; res.json({ message: 'Personel başarıyla güncellendi.', fuelPersonnel: fuelPersonnel[personnelIndex] }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // DELETE - Yakıt personeli sil app.delete('/api/fuel-personnel', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id } = req.body; if (!id) { return res.status(400).json({ message: 'Personel ID zorunludur.' }); } // Personel bul const personnelIndex = fuelPersonnel.findIndex(p => p.id === parseInt(id)); if (personnelIndex === -1) { return res.status(404).json({ message: 'Personel bulunamadı.' }); } // Personel sil const deletedPersonnel = fuelPersonnel.splice(personnelIndex, 1)[0]; res.json({ message: 'Personel başarıyla silindi.', fuelPersonnel: deletedPersonnel }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Fuel Slips API endpoint'leri // GET - Yakıt fişlerini listele app.get('/api/fuel-slips', (req, res) => { const searchParams = req.query; const status = searchParams.status; const manager_id = searchParams.manager_id; const fuel_manager_id = searchParams.fuel_manager_id; // Veritabanı sorgusu let query = 'SELECT * FROM fuel_slips WHERE 1=1'; const params = []; // Status filtreleme if (status) { query += ' AND status = ?'; params.push(status); } // Mal sorumlusu filtreleme if (manager_id) { query += ' AND goods_manager_id = ?'; params.push(manager_id); } // Yakıt sorumlusu filtreleme if (fuel_manager_id) { query += ' AND fuel_manager_id = ?'; params.push(fuel_manager_id); } // Tarihe göre ters sırala query += ' ORDER BY created_at DESC'; db.all(query, params, (err, rows) => { if (err) { console.error('GET fuel slips error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } // JSON string olarak saklanan alanları parse et const fuelSlips = rows.map(row => ({ ...row, vehicle_info: row.vehicle_info ? JSON.parse(row.vehicle_info) : null, personnel_info: row.personnel_info ? JSON.parse(row.personnel_info) : null, goods_manager_info: row.goods_manager_info ? JSON.parse(row.goods_manager_info) : null, fuel_manager_info: row.fuel_manager_info ? JSON.parse(row.fuel_manager_info) : null })); res.json({ fuelSlips }); }); }); // POST - Yeni yakıt fişi oluştur app.post('/api/fuel-slips', async (req, res) => { try { const slipData = req.body; console.log('📝 Creating new fuel slip with data:', slipData); // Validasyon const requiredFields = [ 'date', 'force_command', 'unit_id', 'vehicle_id', 'fuel_type', 'liters', 'km', 'personnel_id', 'goods_manager_id', 'fuel_manager_id' ]; for (const field of requiredFields) { if (!slipData[field]) { return res.status(400).json({ message: `${field} alanı zorunludur.` }); } } // Litre ve KM validasyonu if (slipData.liters <= 0 || slipData.km < 0) { return res.status(400).json({ message: 'Litre ve KM değerleri geçersiz.' }); } // Araç, personel ve mal sorumlusu bilgilerini getir const [vehicle, unit, person, goodsManager] = await Promise.all([ vehicles.find(v => v.id === parseInt(slipData.vehicle_id)), units.find(u => u.id === parseInt(slipData.unit_id)), Promise.resolve({ // Temporary fuel personnel lookup id: 1, full_name: 'Ahmet Demir', rank: 'Üsteğmen' }), Promise.resolve({ // Temporary goods manager lookup id: slipData.goods_manager_id, full_name: 'Mal Sorumlusu', rank: 'Yüzbaşı' }) ]); const vehicleInfo = vehicle ? { brand: vehicle.brand, model: vehicle.model, plate: vehicle.plate, year: vehicle.year } : { brand: 'Bilinmeyen', model: 'Araç', plate: 'Bilinmiyor', year: 0 }; const personnelInfo = person ? { full_name: person.full_name, rank: person.rank } : { full_name: 'Bilinmeyen Personel', rank: '' }; const goodsManagerInfo = goodsManager ? { full_name: goodsManager.full_name, rank: goodsManager.rank } : { full_name: 'Bilinmeyen Mal Sorumlusu', rank: '' }; const fuelManagerInfo = { full_name: 'Yakıt Sorumlusu', rank: 'Yüzbaşı' }; const query = ` INSERT INTO fuel_slips ( date, force_command, unit_id, unit_name, vehicle_id, vehicle_info, fuel_type, liters, km, personnel_id, personnel_info, goods_manager_id, goods_manager_info, fuel_manager_id, fuel_manager_info, status, notes ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ slipData.date, slipData.force_command, slipData.unit_id, unit?.name || `Birim ${slipData.unit_id}`, slipData.vehicle_id, JSON.stringify(vehicleInfo), slipData.fuel_type, parseFloat(slipData.liters), parseInt(slipData.km), slipData.personnel_id, JSON.stringify(personnelInfo), slipData.goods_manager_id, JSON.stringify(goodsManagerInfo), slipData.fuel_manager_id, JSON.stringify(fuelManagerInfo), 'pending', slipData.notes || '' ]; db.run(query, params, function(err) { if (err) { console.error('Create fuel slip error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } const newSlip = { id: this.lastID, date: slipData.date, force_command: slipData.force_command, unit_id: slipData.unit_id, unit_name: unit?.name || `Birim ${slipData.unit_id}`, vehicle_id: slipData.vehicle_id, vehicle_info: vehicleInfo, fuel_type: slipData.fuel_type, liters: parseFloat(slipData.liters), km: parseInt(slipData.km), personnel_id: slipData.personnel_id, personnel_info: personnelInfo, goods_manager_id: slipData.goods_manager_id, goods_manager_info: goodsManagerInfo, fuel_manager_id: slipData.fuel_manager_id, fuel_manager_info: fuelManagerInfo, status: 'pending', notes: slipData.notes || '', created_at: new Date().toISOString() }; console.log('✅ Fuel slip created:', newSlip); // Socket.IO ile mal sorumlusuna bildirim gönder const socketData = { goods_manager_id: newSlip.goods_manager_id, fuel_slip_id: newSlip.id, message: `${newSlip.vehicle_info.plate} plakalı araç için yeni yakıt fişi` }; console.log('📢 Sending socket notification: fuel-slip-assigned', socketData); io.emit('fuel-slip-assigned', socketData); res.json({ message: 'Yakıt fişi başarıyla oluşturuldu.', fuelSlip: newSlip }); }); } catch (error) { console.error('Create fuel slip error:', error); res.status(500).json({ message: 'Sunucu hatası.' }); } }); // PUT - Fiş durumunu güncelle (onay/reddet) app.put('/api/fuel-slips', async (req, res) => { try { const { id, status, approval_notes } = req.body; if (!id || !status) { return res.status(400).json({ message: 'ID ve durum zorunludur.' }); } if (!['approved', 'rejected'].includes(status)) { return res.status(400).json({ message: 'Geçersiz durum.' }); } const query = ` UPDATE fuel_slips SET status = ?, approval_date = ?, approval_notes = ? WHERE id = ? `; const params = [ status, new Date().toISOString(), approval_notes || '', parseInt(id) ]; db.run(query, params, function(err) { if (err) { console.error('Update fuel slip error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } if (this.changes === 0) { return res.status(404).json({ message: 'Fiş bulunamadı.' }); } // Güncellenmiş fişi getir db.get('SELECT * FROM fuel_slips WHERE id = ?', [id], (err, row) => { if (err) { console.error('Get updated fuel slip error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } const updatedSlip = { ...row, vehicle_info: row.vehicle_info ? JSON.parse(row.vehicle_info) : null, personnel_info: row.personnel_info ? JSON.parse(row.personnel_info) : null, goods_manager_info: row.goods_manager_info ? JSON.parse(row.goods_manager_info) : null, fuel_manager_info: row.fuel_manager_info ? JSON.parse(row.fuel_manager_info) : null }; console.log('✅ Fuel slip updated:', updatedSlip); // Socket.IO ile yakıt sorumlusuna bildirim gönder const socketData = { goods_manager_id: updatedSlip.goods_manager_id, fuel_manager_id: updatedSlip.fuel_manager_id, fuel_slip_id: updatedSlip.id, status: updatedSlip.status, approval_notes: updatedSlip.approval_notes }; console.log('📢 Sending socket notification: fuel-slip-updated', socketData); io.emit('fuel-slip-updated', socketData); res.json({ message: `Fiş başarıyla ${status === 'approved' ? 'onaylandı' : 'reddedildi'}.`, fuelSlip: updatedSlip }); }); }); } catch (error) { console.error('Update fuel slip error:', error); res.status(500).json({ message: 'Sunucu hatası.' }); } }); // DELETE - Fiş sil app.delete('/api/fuel-slips', async (req, res) => { try { const { id } = req.body; if (!id) { return res.status(400).json({ message: 'Fiş ID zorunludur.' }); } // Önce fişin durumunu kontrol et db.get('SELECT status FROM fuel_slips WHERE id = ?', [id], (err, row) => { if (err) { console.error('Check fuel slip error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } if (!row) { return res.status(404).json({ message: 'Fiş bulunamadı.' }); } // Sadece pending olan fişler silinebilir if (row.status !== 'pending') { return res.status(400).json({ message: 'Sadece bekleyen fişler silinebilir.' }); } // Fiş sil db.run('DELETE FROM fuel_slips WHERE id = ?', [id], function(err) { if (err) { console.error('Delete fuel slip error:', err); return res.status(500).json({ message: 'Veritabanı hatası.' }); } if (this.changes === 0) { return res.status(404).json({ message: 'Fiş bulunamadı.' }); } res.json({ message: 'Fiş başarıyla silindi.', fuelSlipId: parseInt(id) }); }); }); } catch (error) { console.error('Delete fuel slip error:', error); res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Vehicles API endpoint'leri // GET - Tüm araçları listele app.get('/api/vehicles', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) res.json({ vehicles }); }); // POST - Yeni araç ekle app.post('/api/vehicles', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { brand, model, year, plate, fuel_type, fuel_capacity, current_fuel, yazlik_mukannen = 0, kislik_mukannen = 0, status = 'Aktif', driver_id, driver_name } = req.body; // Validasyon if (!brand || !model || !year || !plate || !fuel_type || !fuel_capacity) { return res.status(400).json({ message: 'Tüm zorunlu alanları doldurun.' }); } if (year < 1990 || year > new Date().getFullYear() + 1) { return res.status(400).json({ message: 'Geçerli bir yıl girin.' }); } if (current_fuel < 0 || current_fuel > fuel_capacity) { return res.status(400).json({ message: 'Mevcut yakıt değeri geçersiz.' }); } // Plaka tekrar kontrolü const existingVehicle = vehicles.find(v => v.plate.toLowerCase() === plate.toLowerCase() ); if (existingVehicle) { return res.status(400).json({ message: 'Bu plaka zaten kayıtlı.' }); } // Yeni araç oluştur const newVehicle = { id: nextVehicleId++, brand: brand.trim(), model: model.trim(), year: parseInt(year), plate: plate.trim().toUpperCase(), fuel_type: fuel_type.trim(), fuel_capacity: parseInt(fuel_capacity), current_fuel: parseInt(current_fuel) || 0, yazlik_mukannen: parseInt(yazlik_mukannen) || 0, kislik_mukannen: parseInt(kislik_mukannen) || 0, status: status.trim(), driver_id: driver_id ? parseInt(driver_id) : null, driver_name: driver_name ? driver_name.trim() : null, created_at: new Date().toISOString() }; vehicles.push(newVehicle); res.json({ message: 'Araç başarıyla eklendi.', vehicle: newVehicle }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // PUT - Araç güncelle app.put('/api/vehicles', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id, brand, model, year, plate, fuel_type, fuel_capacity, current_fuel, yazlik_mukannen, kislik_mukannen, status, driver_id, driver_name } = req.body; // Validasyon if (!id || !brand || !model || !year || !plate || !fuel_type || !fuel_capacity) { return res.status(400).json({ message: 'Tüm zorunlu alanları doldurun.' }); } if (year < 1990 || year > new Date().getFullYear() + 1) { return res.status(400).json({ message: 'Geçerli bir yıl girin.' }); } if (current_fuel < 0 || current_fuel > fuel_capacity) { return res.status(400).json({ message: 'Mevcut yakıt değeri geçersiz.' }); } // Araç bul const vehicleIndex = vehicles.findIndex(v => v.id === parseInt(id)); if (vehicleIndex === -1) { return res.status(404).json({ message: 'Araç bulunamadı.' }); } // Plaka tekrar kontrolü (diğer araçlar için) const existingVehicle = vehicles.find(v => v.id !== parseInt(id) && v.plate.toLowerCase() === plate.toLowerCase() ); if (existingVehicle) { return res.status(400).json({ message: 'Bu plaka başka bir araçta kullanılıyor.' }); } // Araç güncelle vehicles[vehicleIndex] = { ...vehicles[vehicleIndex], brand: brand.trim(), model: model.trim(), year: parseInt(year), plate: plate.trim().toUpperCase(), fuel_type: fuel_type.trim(), fuel_capacity: parseInt(fuel_capacity), current_fuel: parseInt(current_fuel) || 0, yazlik_mukannen: parseInt(yazlik_mukannen) || 0, kislik_mukannen: parseInt(kislik_mukannen) || 0, status: (status || 'Aktif').trim(), driver_id: driver_id ? parseInt(driver_id) : null, driver_name: driver_name ? driver_name.trim() : null }; res.json({ message: 'Araç başarıyla güncellendi.', vehicle: vehicles[vehicleIndex] }); } catch (error) { console.error('❌ PUT /api/vehicles error:', error); console.error('Error details:', { message: error.message, stack: error.stack, body: req.body }); res.status(500).json({ message: 'Sunucu hatası.' }); } }); // DELETE - Araç sil app.delete('/api/vehicles', (req, res) => { // Yetki kontrolü (temporary - will be implemented with proper session) try { const { id } = req.body; if (!id) { return res.status(400).json({ message: 'Araç ID zorunludur.' }); } // Araç bul const vehicleIndex = vehicles.findIndex(v => v.id === parseInt(id)); if (vehicleIndex === -1) { return res.status(404).json({ message: 'Araç bulunamadı.' }); } // Araç sil const deletedVehicle = vehicles.splice(vehicleIndex, 1)[0]; res.json({ message: 'Araç başarıyla silindi.', vehicle: deletedVehicle }); } catch (error) { res.status(500).json({ message: 'Sunucu hatası.' }); } }); // Socket.IO bağlantıları io.on('connection', (socket) => { console.log('✅ Bir kullanıcı bağlandı:', socket.id); // API'den gelen event'ları dinle ve broadcast et socket.on('api-fuel-slip-assigned', (data) => { console.log('📢 API event received: api-fuel-slip-assigned, broadcasting to all clients', data); io.emit('fuel-slip-assigned', data); }); socket.on('api-fuel-slip-updated', (data) => { console.log('📢 API event received: api-fuel-slip-updated, broadcasting to all clients', data); io.emit('fuel-slip-updated', data); }); socket.on('disconnect', () => { console.log('❌ Bir kullanıcı ayrıldı:', socket.id); }); }); // API server only - frontend handled by SvelteKit dev server in development // In production, the build will be served differently // Sunucuyu başlat async function startServer() { try { // Ensure database directory exists first await ensureDbDirectory(); console.log(`📄 Database file path: ${dbPath}`); // Create database connection await createDatabaseConnection(); // Initialize database and tables await initializeDatabase(); server.listen(PORT, () => { console.log(`🚀 Sunucu http://localhost:${PORT} adresinde çalışıyor`); console.log(`📱 Socket.IO sunucusu aktif`); console.log(`💾 Veritabanı: ${dbPath}`); }); } catch (error) { console.error('❌ Sunucu başlatılamadı:', error); process.exit(1); } } startServer();