From 673d53f126c7dfdde09bed0d93031a08bef7760f Mon Sep 17 00:00:00 2001 From: szbk Date: Fri, 7 Nov 2025 00:53:00 +0300 Subject: [PATCH] fixed --- .serena/.gitignore | 1 + .serena/project.yml | 84 +++ docs/TEST_GUIDE.md | 4 +- package.json | 3 +- src/lib/components/GoodsManagerContent.svelte | 2 +- src/lib/server/socketClient.js | 2 +- src/production-server.js | 630 ++++++++++++++++++ src/server.js | 592 +++++++++++++++- vite.config.js | 2 +- 9 files changed, 1308 insertions(+), 12 deletions(-) create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml create mode 100644 src/production-server.js diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..14d86ad --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1 @@ +/cache diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..7a13ecf --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,84 @@ +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp csharp_omnisharp +# dart elixir elm erlang fortran go +# haskell java julia kotlin lua markdown +# nix perl php python python_jedi r +# rego ruby ruby_solargraph rust scala swift +# terraform typescript typescript_vts zig +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# Special requirements: +# - csharp: Requires the presence of a .sln file in the project folder. +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: "utf-8" + +# whether to use the project's gitignore file to ignore files +# Added on 2025-04-07 +ignore_all_files_in_gitignore: true + +# list of additional paths to ignore +# same syntax as gitignore, so you can use * and ** +# Was previously called `ignored_dirs`, please update your config if you are using that. +# Added (renamed) on 2025-04-07 +ignored_paths: [] + +# whether the project is in read-only mode +# If set to true, all editing tools will be disabled and attempts to use them will result in an error +# Added on 2025-04-18 +read_only: false + +# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +excluded_tools: [] + +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: "" + +project_name: "ytp-glm" +included_optional_tools: [] diff --git a/docs/TEST_GUIDE.md b/docs/TEST_GUIDE.md index fc83bfa..255738a 100644 --- a/docs/TEST_GUIDE.md +++ b/docs/TEST_GUIDE.md @@ -5,7 +5,7 @@ KNOWLEDGE_BASE.md'deki iş akışlarına göre implement edilen tam fonksiyonel yakıt takip sistemi. ### 🚀 **Sunucu Durumu** -- **Frontend**: `http://localhost:5174/` (SvelteKit) +- **Frontend**: `http://localhost:5173/` (SvelteKit) - **Backend**: `http://localhost:3000` (Express/Socket.IO) - **Status**: ✅ Çalışıyor @@ -23,7 +23,7 @@ KNOWLEDGE_BASE.md'deki iş akışlarına göre implement edilen tam fonksiyonel #### 1.1 Giriş Testi ```bash -✓ Adım 1: http://localhost:5174/ adresine git +✓ Adım 1: http://localhost:5173/ adresine git ✓ Adım 2: "admin" / "admin123" ile giriş yap ✓ Beklenen: /dashboard sayfasına yönlendirme ✓ Kontrol: Sol menüde "Yönetim Paneli" başlığı diff --git a/package.json b/package.json index 0ca7aa4..0cb241b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "client": "vite dev", "build": "vite build", "preview": "vite preview", - "start": "node src/server.js", + "start": "node src/production-server.js", + "prod": "npm run build && npm run start", "setup": "node -e \"import('./src/server.js').then(() => console.log('Database setup completed')).catch(console.error);\"", "init-db": "node -e \"import('./src/server.js').catch(console.error)\"" }, diff --git a/src/lib/components/GoodsManagerContent.svelte b/src/lib/components/GoodsManagerContent.svelte index a1eb409..60e3a60 100644 --- a/src/lib/components/GoodsManagerContent.svelte +++ b/src/lib/components/GoodsManagerContent.svelte @@ -29,7 +29,7 @@ console.log('🔌 Connecting to Socket.IO...'); // Socket.IO bağlantısı - socket = io('http://localhost:3000'); + socket = io('http://localhost:3001'); console.log('👤 Goods Manager ID:', user.id); diff --git a/src/lib/server/socketClient.js b/src/lib/server/socketClient.js index f0fa4c4..813c371 100644 --- a/src/lib/server/socketClient.js +++ b/src/lib/server/socketClient.js @@ -4,7 +4,7 @@ let socket = null; export function getSocketClient() { if (!socket) { - socket = io('http://localhost:3000', { + socket = io('http://localhost:3001', { transports: ['websocket', 'polling'] }); diff --git a/src/production-server.js b/src/production-server.js new file mode 100644 index 0000000..c189176 --- /dev/null +++ b/src/production-server.js @@ -0,0 +1,630 @@ +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'; + +// Import the handler from the built SvelteKit app +import { handler } from '../build/handler.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; + +const app = express(); +const server = createServer(app); +const io = new Server(server, { + cors: { + origin: process.env.NODE_ENV === 'production' ? false : ["http://localhost:5173"], + methods: ["GET", "POST"] + } +}); + +// Export io for use in other modules +export { io }; + +const PORT = process.env.PORT || 3001; + +// 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()); + +// Session middleware +app.use(session({ + secret: 'yakit-takip-modulu-secret-key-2023', + resave: false, + saveUninitialized: false, + cookie: { + secure: process.env.NODE_ENV === 'production', + 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); + }); + + // Ö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' } + ]; + + // 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); + }); + + // Her kullanıcıyı kontrol et ve yoksa ekle + users.forEach(async (user) => { + 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(); + }); + }); +} + +// 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 +app.get('/api/units', (req, res) => { + res.json({ units }); +}); + +app.post('/api/units', (req, res) => { + 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ı.' }); + } +}); + +app.put('/api/units', (req, res) => { + 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ı.' }); + } +}); + +app.delete('/api/units', (req, res) => { + 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 +app.get('/api/goods-managers', (req, res) => { + try { + const goodsManagers = getGoodsManagers(); + res.json({ goodsManagers }); + } catch (error) { + res.status(500).json({ message: 'Mal sorumluları alınırken hata oluştu.' }); + } +}); + +app.post('/api/goods-managers', async (req, res) => { + 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ı.' }); + } +}); + +app.put('/api/goods-managers', async (req, res) => { + 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ı.' }); + } +}); + +app.delete('/api/goods-managers', async (req, res) => { + 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ı.' }); + } +}); + +// 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); + }); +}); + +// Let SvelteKit handle everything else +app.use(handler); + +// 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(`🚀 Production server 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(); \ No newline at end of file diff --git a/src/server.js b/src/server.js index d3b6a26..e7ca422 100644 --- a/src/server.js +++ b/src/server.js @@ -65,6 +65,54 @@ let units = [ 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; + const app = express(); const server = createServer(app); const io = new Server(server, { @@ -77,7 +125,7 @@ const io = new Server(server, { // Export io for use in other modules export { io }; -const PORT = process.env.PORT || 3002; +const PORT = process.env.PORT || 3001; // ES Module equivalent of __dirname const __filename = fileURLToPath(import.meta.url); @@ -90,7 +138,7 @@ const dbPath = join(dbDir, 'yakit_takip.db'); // Middleware app.use(express.json()); -app.use(express.static('build')); +// Note: static file serving removed - SvelteKit handles frontend in development // Session middleware app.use(session({ @@ -602,6 +650,540 @@ app.delete('/api/goods-managers', async (req, res) => { } }); +// 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) + const 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() + } + ]; + res.json({ fuelPersonnel }); +}); + +// 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, + 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, + 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, + 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, + status: status.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) { + 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); @@ -622,10 +1204,8 @@ io.on('connection', (socket) => { }); }); -// SvelteKit için tüm route'ları handle et -app.use('*', (req, res) => { - res.sendFile('build/index.html', { root: '.' }); -}); +// 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() { diff --git a/vite.config.js b/vite.config.js index 154f5da..ee5d0c5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,7 +7,7 @@ export default defineConfig({ port: 5173, proxy: { '/api': { - target: 'http://localhost:3002', + target: 'http://localhost:3001', changeOrigin: true } }