Переработаны роутеры приложения
Переписано APi WebSocket для работы с новыми роутерами
This commit is contained in:
21
ws/config/db.js
Normal file
21
ws/config/db.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const sqlite3 = require("sqlite3");
|
||||
const path = require("path");
|
||||
require("dotenv").config();
|
||||
|
||||
const dbPath = process.env.DATABASE_PATH || path.join(__dirname, ".."); // если в .env относительный путь
|
||||
const fileName = process.env.DATABASE_FILE || "database.sqlite";
|
||||
|
||||
const fullPath = path.isAbsolute(dbPath) ? path.join(dbPath, fileName) : path.join(dbPath, fileName);
|
||||
|
||||
const db = new sqlite3.Database(fullPath, (err) => {
|
||||
if (err) {
|
||||
console.error("❌ Failed to open sqlite database:", err);
|
||||
} else {
|
||||
console.log("✅ SQLite DB opened at", fullPath);
|
||||
}
|
||||
});
|
||||
|
||||
// optional: enable foreign keys
|
||||
db.exec("PRAGMA foreign_keys = ON;");
|
||||
|
||||
module.exports = db;
|
||||
50
ws/middleware/auth.js
Normal file
50
ws/middleware/auth.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const db = require("../config/db");
|
||||
|
||||
async function auth(uuid) {
|
||||
return new Promise((res, rej) => {
|
||||
db.get(`
|
||||
SELECT
|
||||
sheeps.*,
|
||||
possibilities.can_add_sheeps,
|
||||
possibilities.can_view_sheeps,
|
||||
possibilities.can_add_territory,
|
||||
possibilities.can_view_territory,
|
||||
possibilities.can_manager_territory,
|
||||
possibilities.can_add_stand,
|
||||
possibilities.can_view_stand,
|
||||
possibilities.can_manager_stand,
|
||||
possibilities.can_add_schedule,
|
||||
possibilities.can_view_schedule
|
||||
FROM sheeps
|
||||
LEFT JOIN possibilities ON possibilities.sheep_id = sheeps.id
|
||||
WHERE sheeps.uuid_manager = ? OR sheeps.uuid = ?
|
||||
`, [uuid, uuid], (err, row) => {
|
||||
if (err) return rej(err);
|
||||
if (!row) return res(false);
|
||||
|
||||
res({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
uuid: row.uuid,
|
||||
group_id: row.group_id,
|
||||
icon: row.icon,
|
||||
appointment: row.appointment,
|
||||
sheepRole: row.mode_title,
|
||||
possibilities: {
|
||||
can_add_sheeps: !!row.can_add_sheeps,
|
||||
can_view_sheeps: !!row.can_view_sheeps,
|
||||
can_add_territory: !!row.can_add_territory,
|
||||
can_view_territory: !!row.can_view_territory,
|
||||
can_manager_territory: !!row.can_manager_territory,
|
||||
can_add_stand: !!row.can_add_stand,
|
||||
can_view_stand: !!row.can_view_stand,
|
||||
can_manager_stand: !!row.can_manager_stand,
|
||||
can_add_schedule: !!row.can_add_schedule,
|
||||
can_view_schedule: !!row.can_view_schedule,
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { auth };
|
||||
11
ws/middleware/requirePermission.js
Normal file
11
ws/middleware/requirePermission.js
Normal file
@@ -0,0 +1,11 @@
|
||||
function requirePermission(permission, ws, next) {
|
||||
if (!ws.user) {
|
||||
return ws.send(JSON.stringify({ error: "Unauthorized" }));
|
||||
}
|
||||
if (!ws.user.possibilities[permission]) {
|
||||
return ws.send(JSON.stringify({ error: `Forbidden: missing ${permission}` }));
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { requirePermission };
|
||||
6
ws/routes/connection.js
Normal file
6
ws/routes/connection.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { broadcast } = require("../utils/broadcaster");
|
||||
|
||||
module.exports = (wss, ws, message) => {
|
||||
console.log(`🔗 Connection event from ${ws.user.name}`);
|
||||
broadcast(wss, { event: "user_connected", user: {name: ws.user.name, id: ws.user.id } });
|
||||
};
|
||||
18
ws/routes/index.js
Normal file
18
ws/routes/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const connectionRoute = require("./connection");
|
||||
const messageRoute = require("./message");
|
||||
|
||||
const routes = {
|
||||
connection: connectionRoute,
|
||||
message: messageRoute,
|
||||
};
|
||||
|
||||
function routeMessage(wss, ws, message) {
|
||||
const handler = routes[message.event];
|
||||
if (handler) {
|
||||
handler(wss, ws, message);
|
||||
} else {
|
||||
ws.send(JSON.stringify({ error: `Unknown event: ${message.event}` }));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { routeMessage };
|
||||
25
ws/routes/message.js
Normal file
25
ws/routes/message.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const { updateApartment } = require("../services/apartments.service");
|
||||
const { updateBuilding } = require("../services/buildings.service");
|
||||
const { updateStand } = require("../services/stand.service");
|
||||
const { broadcast } = require("../utils/broadcaster");
|
||||
|
||||
module.exports = async (wss, ws, message) => {
|
||||
try {
|
||||
switch (message.type) {
|
||||
case "apartment":
|
||||
await updateApartment(ws.user, message.data);
|
||||
break;
|
||||
case "building":
|
||||
await updateBuilding(ws.user, message.data);
|
||||
break;
|
||||
case "stand":
|
||||
await updateStand(ws.user, message.data);
|
||||
break;
|
||||
default:
|
||||
return ws.send(JSON.stringify({ error: `Unknown message type: ${message.type}` }));
|
||||
}
|
||||
broadcast(wss, message);
|
||||
} catch (err) {
|
||||
ws.send(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
};
|
||||
31
ws/services/apartments.service.js
Normal file
31
ws/services/apartments.service.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const db = require("../config/db");
|
||||
|
||||
function updateApartment(user, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!user.possibilities.can_manager_territory) {
|
||||
return reject(new Error("Forbidden: no rights to manage territory"));
|
||||
}
|
||||
|
||||
const sql = `
|
||||
UPDATE apartments
|
||||
SET status = ?, description = ?, sheep_id = ?, updated_at = ?
|
||||
WHERE id = ?`;
|
||||
|
||||
db.run(sql, [Number(data.status), data.description, data.sheep_id, data.updated_at, data.id], function (err) {
|
||||
if (err) return reject(err);
|
||||
if (this.changes === 0) return reject(new Error("Apartment not found"));
|
||||
|
||||
const insertSql = `
|
||||
INSERT INTO apartments_history
|
||||
(apartments_id, status, description, sheep_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(insertSql, [Number(data.id), Number(data.status), data.description, data.sheep_id, Date.now()], function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve({ update: "ok", id: data.id, historyId: this.lastID });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { updateApartment };
|
||||
31
ws/services/buildings.service.js
Normal file
31
ws/services/buildings.service.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const db = require("../config/db");
|
||||
|
||||
function updateBuilding(user, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!user.possibilities.can_manager_territory) {
|
||||
return reject(new Error("Forbidden: no rights to manage territory"));
|
||||
}
|
||||
|
||||
const sql = `
|
||||
UPDATE buildings
|
||||
SET status = ?, description = ?, sheep_id = ?, updated_at = ?
|
||||
WHERE id = ?`;
|
||||
|
||||
db.run(sql, [Number(data.status), data.description, data.sheep_id, data.updated_at, data.id], function (err) {
|
||||
if (err) return reject(err);
|
||||
if (this.changes === 0) return reject(new Error("Building not found"));
|
||||
|
||||
const insertSql = `
|
||||
INSERT INTO buildings_history
|
||||
(buildings_id, status, description, sheep_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(insertSql, [Number(data.id), Number(data.status), data.description, data.sheep_id, Date.now()], function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve({ update: "ok", id: data.id, historyId: this.lastID });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { updateBuilding };
|
||||
31
ws/services/stand.service.js
Normal file
31
ws/services/stand.service.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const db = require("../config/db");
|
||||
|
||||
function updateStand(user, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!user.possibilities.can_manager_territory) {
|
||||
return reject(new Error("Forbidden: no rights to manage territory"));
|
||||
}
|
||||
|
||||
const sql = `
|
||||
UPDATE buildings
|
||||
SET status = ?, description = ?, sheep_id = ?, updated_at = ?
|
||||
WHERE id = ?`;
|
||||
|
||||
db.run(sql, [Number(data.status), data.description, data.sheep_id, data.updated_at, data.id], function (err) {
|
||||
if (err) return reject(err);
|
||||
if (this.changes === 0) return reject(new Error("Building not found"));
|
||||
|
||||
const insertSql = `
|
||||
INSERT INTO buildings_history
|
||||
(buildings_id, status, description, sheep_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(insertSql, [Number(data.id), Number(data.status), data.description, data.sheep_id, Date.now()], function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve({ update: "ok", id: data.id, historyId: this.lastID });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { updateStand };
|
||||
8
ws/utils/broadcaster.js
Normal file
8
ws/utils/broadcaster.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function broadcast(wss, message) {
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === 1) {
|
||||
client.send(JSON.stringify(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
module.exports = { broadcast };
|
||||
14
ws/utils/ping.js
Normal file
14
ws/utils/ping.js
Normal file
@@ -0,0 +1,14 @@
|
||||
function setupPing(ws) {
|
||||
const interval = setInterval(() => {
|
||||
if (ws.readyState === 1) {
|
||||
ws.ping("ping");
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
ws.on("pong", (data) => {
|
||||
console.log("📡 PONG from client:", data.toString());
|
||||
});
|
||||
}
|
||||
module.exports = { setupPing };
|
||||
240
ws/ws.js
240
ws/ws.js
@@ -1,226 +1,50 @@
|
||||
const WebSocket = require("ws");
|
||||
const { URL } = require('url');
|
||||
const sqlite3 = require('sqlite3');
|
||||
const path = require('path');
|
||||
const { routeMessage } = require("./routes");
|
||||
const { auth } = require("./middleware/auth");
|
||||
const { setupPing } = require("./utils/ping");
|
||||
require("dotenv").config();
|
||||
|
||||
const dbPath = process.env.DATABASE_PATH || '../';
|
||||
const db = new sqlite3.Database(path.join(dbPath, 'database.sqlite'));
|
||||
const api_version = '1.1.0';
|
||||
|
||||
const port = process.env.WS_PORT || 4001;
|
||||
const api_version = '1.0.0';
|
||||
|
||||
const wss = new WebSocket.Server({
|
||||
port: port
|
||||
}, () => console.log(`Server started on port ${port}`));
|
||||
const wss = new WebSocket.Server({ port: 4004 });
|
||||
console.log("WebSocket сервер запущен на порту 4004");
|
||||
|
||||
|
||||
wss.on('connection', async (ws, request) => {
|
||||
const url = new URL(request.url, `http://${request.headers.host}`)
|
||||
wss.on("connection", async (ws, request) => {
|
||||
const url = new URL(request.url, `http://${request.headers.host}`);
|
||||
const params = Object.fromEntries(url.searchParams.entries());
|
||||
const uuid = params.uuid;
|
||||
const uuid = params.uuid ?? ws.protocol;
|
||||
|
||||
if (!uuid) return
|
||||
if (!uuid) return ws.close();
|
||||
|
||||
let check = await checkUUID(uuid);
|
||||
const user = await auth(uuid);
|
||||
if (!user) return ws.close();
|
||||
|
||||
console.log(check);
|
||||
try {
|
||||
const user = await auth(uuid);
|
||||
if (!user) return ws.close();
|
||||
|
||||
ws.user = user;
|
||||
ws.send(JSON.stringify({ connection: "success", api_version, user: {name: ws.user.name, id: ws.user.id } }));
|
||||
|
||||
if (!check && check.possibilities.can_view_territory) return
|
||||
// Periodic ping to maintain a connection
|
||||
setupPing(ws);
|
||||
|
||||
// Periodic ping to maintain a connection
|
||||
const pingInterval = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.ping('ping');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
|
||||
ws.send(JSON.stringify({ "connection": "success", "api_version": api_version }));
|
||||
|
||||
ws.on('message', (message) => {
|
||||
message = JSON.parse(message);
|
||||
|
||||
console.log(message.username, check.name);
|
||||
|
||||
switch (message.event) {
|
||||
case "connection":
|
||||
broadcastMessage(message);
|
||||
break;
|
||||
|
||||
case "message":
|
||||
updateDatabase(message);
|
||||
broadcastMessage(message);
|
||||
break;
|
||||
};
|
||||
});
|
||||
|
||||
ws.on('pong', (data) => {
|
||||
console.log('PONG received from the client:', data.toString());
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('Client close');
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.error('ERROR WebSocket:', err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function broadcastMessage(message) {
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateDatabase(message) {
|
||||
|
||||
console.log(message.type);
|
||||
|
||||
|
||||
if (message.type === "apartment") {
|
||||
|
||||
let sql = 'UPDATE apartments SET status = ?, description = ?, sheep_id = ?, updated_at = ? WHERE id = ?';
|
||||
|
||||
db.run(sql, [
|
||||
Number(message.data.status),
|
||||
message.data.description,
|
||||
message.data.sheep_id,
|
||||
message.data.updated_at,
|
||||
message.data.id
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
} else if (this.changes === 0) {
|
||||
console.error('Product not found');
|
||||
} else {
|
||||
console.log({ "update": "ok", "id": message.data.id });
|
||||
|
||||
let sql = `INSERT INTO apartments_history (apartments_id, status, description, sheep_id, created_at) VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(sql, [
|
||||
Number(message.data.id),
|
||||
Number(message.data.status),
|
||||
message.data.description,
|
||||
message.data.sheep_id,
|
||||
Math.floor(Date.now())
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
} else if (this.changes === 0) {
|
||||
console.error('Apartments not found');
|
||||
} else {
|
||||
console.log({ "insert": "ok", "id": this.lastID });
|
||||
}
|
||||
});
|
||||
ws.on("message", (raw) => {
|
||||
try {
|
||||
const message = JSON.parse(raw);
|
||||
routeMessage(wss, ws, message);
|
||||
} catch (e) {
|
||||
console.error("❌ Invalid message:", raw);
|
||||
ws.send(JSON.stringify({ error: "Invalid message format" }));
|
||||
}
|
||||
});
|
||||
} else if(message.type === "building") {
|
||||
let sql = 'UPDATE buildings SET status = ?, description = ?, sheep_id = ?, updated_at = ? WHERE id = ?';
|
||||
|
||||
db.run(sql, [
|
||||
Number(message.data.status),
|
||||
message.data.description,
|
||||
message.data.sheep_id,
|
||||
message.data.updated_at,
|
||||
message.data.id
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
} else if (this.changes === 0) {
|
||||
console.error('Product not found');
|
||||
} else {
|
||||
console.log({ "update": "ok", "id": message.data.id });
|
||||
|
||||
let sql = `INSERT INTO buildings_history (buildings_id, status, description, sheep_id, created_at) VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(sql, [
|
||||
Number(message.data.id),
|
||||
Number(message.data.status),
|
||||
message.data.description,
|
||||
message.data.sheep_id,
|
||||
Math.floor(Date.now())
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
} else if (this.changes === 0) {
|
||||
console.error('Apartments not found');
|
||||
} else {
|
||||
console.log({ "insert": "ok", "id": this.lastID });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
ws.on("close", () => console.log("🔌 Client disconnected"));
|
||||
ws.on("error", (err) => console.error("❌ WS error:", err));
|
||||
} catch (err) {
|
||||
console.error("❌ Auth error:", err);
|
||||
ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUUID(uuid) {
|
||||
return new Promise((res, rej) => {
|
||||
db.get(`
|
||||
SELECT
|
||||
sheeps.*,
|
||||
possibilities.can_view_territory AS can_view_territory
|
||||
FROM
|
||||
sheeps
|
||||
LEFT JOIN
|
||||
possibilities ON possibilities.sheep_id = sheeps.id
|
||||
WHERE
|
||||
sheeps.uuid_manager = ?`,
|
||||
[uuid],
|
||||
(err, moderator) => {
|
||||
if (moderator) {
|
||||
let data = {
|
||||
id: moderator.sheep_id,
|
||||
group_id: moderator.group_id,
|
||||
name: moderator.name,
|
||||
icon: moderator.icon,
|
||||
uuid: moderator.uuid,
|
||||
appointment: moderator.appointment,
|
||||
sheepRole: moderator.mode_title,
|
||||
possibilities: {
|
||||
can_view_territory: moderator.can_view_territory == 1 ? true : false
|
||||
}
|
||||
}
|
||||
return res(data);
|
||||
}
|
||||
|
||||
db.get(`
|
||||
SELECT
|
||||
sheeps.*,
|
||||
possibilities.can_view_territory AS can_view_territory
|
||||
FROM
|
||||
sheeps
|
||||
LEFT JOIN
|
||||
possibilities ON possibilities.sheep_id = sheeps.id
|
||||
WHERE
|
||||
sheeps.uuid = ?`,
|
||||
[uuid],
|
||||
(err, sheep) => {
|
||||
if (sheep) {
|
||||
let data = {
|
||||
id: sheep.sheep_id,
|
||||
group_id: sheep.group_id,
|
||||
name: sheep.name,
|
||||
icon: sheep.icon,
|
||||
uuid: sheep.uuid,
|
||||
appointment: sheep.appointment,
|
||||
sheepRole: sheep.mode_title,
|
||||
possibilities: {
|
||||
can_view_territory: sheep.can_view_territory == 1 ? true : false
|
||||
}
|
||||
}
|
||||
return res(data);
|
||||
}
|
||||
|
||||
return res(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user