const express = require('express'); const db = require('../db'); const jwt = require('jsonwebtoken'); module.exports = function(io, onlineUsers) { const router = express.Router(); function authenticate(req, res, next) { const auth = req.headers.authorization?.split(' '); try { if (!auth || auth[0] !== 'Bearer') return res.status(401).send('No token'); const payload = jwt.verify(auth[1], process.env.JWT_SECRET); req.user = payload; next(); } catch { res.status(401).send('Invalid token'); } } async function initTables() { await db.query(`CREATE TABLE IF NOT EXISTS organizations ( id SERIAL PRIMARY KEY, object_id INTEGER, name TEXT NOT NULL, type TEXT NOT NULL, city TEXT, owner TEXT, budget NUMERIC DEFAULT 0, rating NUMERIC DEFAULT 0 )`); await db.query(`ALTER TABLE organizations ADD COLUMN IF NOT EXISTS object_id INTEGER`); // NEW: владелец числовой await db.query(`ALTER TABLE organizations ADD COLUMN IF NOT EXISTS owner_id INTEGER`); await db.query(`CREATE TABLE IF NOT EXISTS organization_settings ( organization_id INTEGER PRIMARY KEY REFERENCES organizations(id) ON DELETE CASCADE, menu JSONB, work_hours TEXT )`); await db.query(`CREATE TABLE IF NOT EXISTS organization_orders ( id SERIAL PRIMARY KEY, organization_id INTEGER REFERENCES organizations(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, item TEXT NOT NULL, price NUMERIC NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )`); await db.query(`CREATE TABLE IF NOT EXISTS organization_inventory ( id SERIAL PRIMARY KEY, organization_id INTEGER REFERENCES organizations(id) ON DELETE CASCADE, item TEXT NOT NULL, quantity INTEGER DEFAULT 0, price NUMERIC DEFAULT 0 )`); await db.query(`CREATE TABLE IF NOT EXISTS organization_employees ( organization_id INTEGER REFERENCES organizations(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, wage NUMERIC DEFAULT 0, PRIMARY KEY(organization_id, user_id) )`); await db.query(`CREATE TABLE IF NOT EXISTS hotel_rooms ( id SERIAL PRIMARY KEY, organization_id INTEGER REFERENCES organizations(id) ON DELETE CASCADE, room_number TEXT, class TEXT, price NUMERIC, is_available BOOLEAN DEFAULT true, occupant INTEGER REFERENCES users(id), lease_end TIMESTAMPTZ )`); } initTables().catch(err => console.error('Failed to init organization tables', err)); router.post('/', async (req, res) => { const { name, type, city, owner, ownerId, objectId, budget = 0, rating = 0 } = req.body; try { const { rows } = await db.query( `INSERT INTO organizations(name, type, city, owner, owner_id, object_id, budget, rating) VALUES($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`, [name, type, city, owner ?? null, ownerId ?? null, objectId, budget, rating] ); res.json(rows[0]); } catch (err) { console.error(err); res.status(500).json({ error: 'db error' }); } }); router.get('/:id', async (req, res) => { const { id } = req.params; const { rows } = await db.query('SELECT * FROM organizations WHERE id=$1', [id]); if (!rows.length) return res.status(404).json({ error: 'not found' }); res.json(rows[0]); }); router.get('/object/:objectId', async (req, res) => { const { objectId } = req.params; const { rows } = await db.query('SELECT * FROM organizations WHERE object_id=$1', [objectId]); if (!rows.length) return res.status(404).json({ error: 'not found' }); res.json(rows[0]); }); // >>> НОРМАЛИЗУЕМ МЕНЮ В МАССИВ router.get('/:id/settings', async (req, res) => { const { id } = req.params; const { rows } = await db.query( 'SELECT menu, work_hours FROM organization_settings WHERE organization_id=$1', [id] ); const raw = rows[0] || {}; const menu = raw.menu || {}; const menuArray = Array.isArray(menu) ? menu : Object.entries(menu).map(([key, val]) => ({ key, ...(val || {}) })); res.json({ menu: menuArray, work_hours: raw.work_hours || '' }); }); router.put('/:id/settings', async (req, res) => { const { id } = req.params; let { menu, workHours } = req.body; // принимаем и массив, и объект — храним как есть try { const { rows } = await db.query( `INSERT INTO organization_settings(organization_id, menu, work_hours) VALUES($1, $2, $3) ON CONFLICT (organization_id) DO UPDATE SET menu = EXCLUDED.menu, work_hours = EXCLUDED.work_hours RETURNING menu, work_hours`, [id, menu, workHours] ); res.json(rows[0]); } catch (err) { console.error(err); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/purchase', authenticate, async (req, res) => { const { id } = req.params; const { itemKey } = req.body; try { const menuRes = await db.query('SELECT menu FROM organization_settings WHERE organization_id=$1', [id]); const menu = menuRes.rows[0]?.menu || {}; const entry = Array.isArray(menu) ? (menu.find(it => it.key === itemKey) || null) : menu[itemKey]; if (!entry) return res.status(400).json({ error: 'invalid item' }); let itemDef; if (entry.itemId) { const r = await db.query('SELECT * FROM items WHERE id=$1', [entry.itemId]); itemDef = r.rows[0]; } else { const r = await db.query('SELECT * FROM items WHERE key=$1', [itemKey]); itemDef = r.rows[0]; } if (!itemDef) return res.status(400).json({ error: 'item not found' }); const price = parseFloat(entry.price); const { rows } = await db.query('SELECT balance, satiety, thirst FROM users WHERE id=$1', [req.user.id]); let balance = parseFloat(rows[0].balance); let satiety = parseFloat(rows[0].satiety ?? 100); let thirst = parseFloat(rows[0].thirst ?? 100); if (balance < price) { const sock = onlineUsers[req.user.id]; if (sock) io.to(sock).emit('chatMessage', { playerId: 0, name: 'Система', message: `Вам недостаточно средств для покупки ${itemDef.name}` }); return res.status(400).json({ error: 'insufficient funds' }); } await db.query('BEGIN'); await db.query( 'UPDATE users SET balance = balance - $1, satiety = LEAST(100, COALESCE(satiety,100)+$2), thirst = LEAST(100, COALESCE(thirst,100)+$3) WHERE id=$4', [price, itemDef.hunger_gain, itemDef.thirst_gain, req.user.id] ); await db.query( `INSERT INTO organization_orders(organization_id, user_id, item, price) VALUES($1,$2,$3,$4)`, [id, req.user.id, itemDef.name, price] ); await db.query( `INSERT INTO inventory(user_id, item_id, name, quantity, stackable, weight) VALUES($1,$2,$3,1,$4,$5) ON CONFLICT (user_id, item_id) DO UPDATE SET quantity = inventory.quantity + 1`, [req.user.id, itemDef.id, itemDef.name, itemDef.stackable, itemDef.weight] ); await db.query('COMMIT'); balance -= price; satiety = Math.min(100, satiety + parseFloat(itemDef.hunger_gain)); thirst = Math.min(100, thirst + parseFloat(itemDef.thirst_gain)); const sock = onlineUsers[req.user.id]; if (sock) io.to(sock).emit('chatMessage', { playerId: 0, name: 'Система', message: `Вы купили ${itemDef.name}` }); res.json({ success: true, balance, satiety, thirst }); } catch (err) { await db.query('ROLLBACK'); console.error(err); res.status(500).json({ error: 'db error' }); } }); // ----- управление бизнесом ----- // Ставим owner_id (и дублируем в owner для совместимости) router.put('/:id/owner', authenticate, async (req, res) => { const { id } = req.params; const { ownerId } = req.body; // число try { await db.query('UPDATE organizations SET owner_id=$1, owner=$1::text WHERE id=$2', [ownerId, id]); res.json({ success: true }); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.delete('/:id', authenticate, async (req, res) => { const { id } = req.params; try { await db.query('DELETE FROM organizations WHERE id=$1', [id]); res.json({ success: true }); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/inventory', authenticate, async (req, res) => { const { id } = req.params; const { item, quantity = 0, cost = 0, price = 0 } = req.body; try { await db.query('BEGIN'); await db.query( `INSERT INTO organization_inventory(organization_id, item, quantity, price) VALUES($1,$2,$3,$4) ON CONFLICT (organization_id, item) DO UPDATE SET quantity = organization_inventory.quantity + EXCLUDED.quantity, price = EXCLUDED.price`, [id, item, quantity, price] ); await db.query('UPDATE organizations SET budget = budget - $1 WHERE id=$2', [cost, id]); await db.query('COMMIT'); res.json({ success: true }); } catch (e) { await db.query('ROLLBACK'); console.error(e); res.status(500).json({ error: 'db error' }); } }); router.get('/:id/inventory', async (req, res) => { const { id } = req.params; try { const { rows } = await db.query('SELECT * FROM organization_inventory WHERE organization_id=$1', [id]); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/pay-wages', authenticate, async (req, res) => { const { id } = req.params; try { const { rows } = await db.query('SELECT user_id, wage FROM organization_employees WHERE organization_id=$1', [id]); let total = rows.reduce((s, r) => s + parseFloat(r.wage), 0); const org = await db.query('SELECT budget FROM organizations WHERE id=$1', [id]); if (parseFloat(org.rows[0].budget) < total) return res.status(400).json({ error: 'insufficient funds' }); await db.query('BEGIN'); await db.query('UPDATE organizations SET budget = budget - $1 WHERE id=$2', [total, id]); for (const r of rows) { await db.query('UPDATE users SET balance = balance + $1 WHERE id=$2', [r.wage, r.user_id]); } await db.query('COMMIT'); res.json({ success: true }); } catch (e) { await db.query('ROLLBACK'); console.error(e); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/pay-taxes', authenticate, async (req, res) => { const { id } = req.params; const { amount } = req.body; try { await db.query('UPDATE organizations SET budget = budget - $1 WHERE id=$2', [amount, id]); res.json({ success: true }); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/pay-utilities', authenticate, async (req, res) => { const { id } = req.params; const { amount } = req.body; try { await db.query('UPDATE organizations SET budget = budget - $1 WHERE id=$2', [amount, id]); res.json({ success: true }); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); // ----- управление отелями ----- router.post('/:id/rooms', authenticate, async (req, res) => { const { id } = req.params; const { roomNumber, class: roomClass, price } = req.body; try { const { rows } = await db.query( `INSERT INTO hotel_rooms(organization_id, room_number, class, price) VALUES($1,$2,$3,$4) RETURNING *`, [id, roomNumber, roomClass, price] ); res.json(rows[0]); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.put('/:id/rooms/:roomId', authenticate, async (req, res) => { const { id, roomId } = req.params; const { class: roomClass, price } = req.body; try { await db.query( `UPDATE hotel_rooms SET class=COALESCE($1,class), price=COALESCE($2,price) WHERE id=$3 AND organization_id=$4`, [roomClass, price, roomId, id] ); res.json({ success: true }); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.get('/:id/rooms', async (req, res) => { const { id } = req.params; try { const { rows } = await db.query('SELECT * FROM hotel_rooms WHERE organization_id=$1', [id]); res.json(rows); } catch (e) { console.error(e); res.status(500).json({ error: 'db error' }); } }); router.post('/:id/rooms/:roomId/rent', authenticate, async (req, res) => { const { id, roomId } = req.params; const { days = 1 } = req.body; try { const { rows } = await db.query('SELECT price, is_available FROM hotel_rooms WHERE id=$1 AND organization_id=$2', [roomId, id]); if (!rows.length || !rows[0].is_available) return res.status(400).json({ error: 'room unavailable' }); const price = parseFloat(rows[0].price) * days; const user = await db.query('SELECT balance FROM users WHERE id=$1', [req.user.id]); if (parseFloat(user.rows[0].balance) < price) return res.status(400).json({ error: 'insufficient funds' }); await db.query('BEGIN'); await db.query('UPDATE users SET balance = balance - $1 WHERE id=$2', [price, req.user.id]); await db.query('UPDATE organizations SET budget = budget + $1 WHERE id=$2', [price, id]); await db.query( 'UPDATE hotel_rooms SET is_available=false, occupant=$1, lease_end = NOW() + ($2 || " days")::interval WHERE id=$3', [req.user.id, days, roomId] ); await db.query('COMMIT'); res.json({ success: true }); } catch (e) { await db.query('ROLLBACK'); console.error(e); res.status(500).json({ error: 'db error' }); } }); return router; };