385 lines
14 KiB
JavaScript
385 lines
14 KiB
JavaScript
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.get(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.get(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;
|
||
};
|