Added AdminManager. Updated NotificationManager

This commit is contained in:
MaksSlyzar 2026-04-02 22:28:06 +03:00
parent 2c61c8c05c
commit 1de6fc980d
9 changed files with 195 additions and 73 deletions

View File

@ -28,7 +28,6 @@ const ChatTab = () => {
useEffect(() => { useEffect(() => {
if (!socket) return; if (!socket) return;
// Початкові запити
socket.emit("friend:get_list"); socket.emit("friend:get_list");
if (activeChat === "global") { if (activeChat === "global") {
socket.emit("chat:get_global_history"); socket.emit("chat:get_global_history");
@ -89,6 +88,8 @@ const ChatTab = () => {
receiverId: activeChat === "global" ? null : activeChat, receiverId: activeChat === "global" ? null : activeChat,
}); });
setInputValue(""); setInputValue("");
console.log(activeChat === "global" ? null : activeChat);
}; };
const selectChat = (id) => { const selectChat = (id) => {

View File

@ -12,7 +12,6 @@ const NotificationsTab = () => {
socket.emit("notifications:get_all"); socket.emit("notifications:get_all");
const handleNewNotify = (notify) => { const handleNewNotify = (notify) => {
console.log(notify);
setNotifications((prev) => [notify, ...prev]); setNotifications((prev) => [notify, ...prev]);
}; };
@ -31,9 +30,14 @@ const NotificationsTab = () => {
const handleAction = (id, action, data) => { const handleAction = (id, action, data) => {
if (action === "accept_friend") { if (action === "accept_friend") {
socket.emit("friend:add", { friendId: data.fromId }); socket.emit("friend:accept", { friendId: data.fromId });
}
socket.emit("notification:read", { id }); socket.emit("notification:read", { id });
} else if (action === "dismiss") {
socket.emit("notification:dismiss", { id });
} else {
socket.emit("notification:read", { id });
}
setNotifications((prev) => prev.filter((n) => n.id !== id)); setNotifications((prev) => prev.filter((n) => n.id !== id));
}; };
@ -45,6 +49,10 @@ const NotificationsTab = () => {
return "fas fa-hammer"; return "fas fa-hammer";
case "system": case "system":
return "fas fa-robot"; return "fas fa-robot";
case "item_received":
return "fas fa-box-open";
case "inventory_clear":
return "fas fa-trash-alt";
default: default:
return "fas fa-bell"; return "fas fa-bell";
} }
@ -66,7 +74,7 @@ const NotificationsTab = () => {
)} )}
{notifications.map((n) => ( {notifications.map((n) => (
<div key={n.id} className={`notify-card ${n.type}`}> <div key={n.id} className={`notify-card ${n.type} ${n.priority}`}>
<div className="notify-icon"> <div className="notify-icon">
<i className={getIcon(n.type)}></i> <i className={getIcon(n.type)}></i>
</div> </div>

View File

@ -0,0 +1,91 @@
const { Player, Inventory } = require("../models");
const DatapackLoader = require("../game/DatapackLoader");
const notificationManager = require("./NotificationManager");
class AdminManager {
constructor() {
this.io = null;
}
init(io) {
this.io = io;
}
async giveItem(targetName, itemId, amount) {
const targetPlayer = await Player.findOne({
where: { username: targetName },
});
if (!targetPlayer) throw new Error(`Player '${targetName}' not found.`);
const itemData = DatapackLoader.getItem(itemId);
if (!itemData) throw new Error(`Item ID '${itemId}' does not exist.`);
const [inventoryItem, created] = await Inventory.findOrCreate({
where: { playerId: targetPlayer.id, itemId: itemId },
defaults: { quantity: amount },
});
if (!created) {
await inventoryItem.increment("quantity", { by: amount });
}
await notificationManager.send({
playerId: targetPlayer.id,
type: "item_received",
title: "Items Received",
message: `Admin granted you ${amount}x ${itemData.name || itemId}.`,
data: { itemId, amount },
priority: "normal",
});
this._updatePlayerInventory(targetPlayer.id);
return { targetName, itemId, amount };
}
async clearInventory(targetName) {
const targetPlayer = await Player.findOne({
where: { username: targetName },
});
if (!targetPlayer) throw new Error(`Player '${targetName}' not found.`);
await Inventory.destroy({ where: { playerId: targetPlayer.id } });
await notificationManager.send({
playerId: targetPlayer.id,
type: "inventory_clear",
title: "Inventory Wiped",
message: "Your inventory has been cleared by an administrator.",
priority: "high",
});
this._updatePlayerInventory(targetPlayer.id, true);
return targetName;
}
async reloadData() {
DatapackLoader.loadAll();
if (this.io) {
this.io.emit("admin:log", "System: Datapacks reloaded by admin.");
}
}
async _updatePlayerInventory(playerId, isEmpty = false) {
if (!this.io) return;
const targetSocket = [...this.io.sockets.sockets.values()].find(
(s) => s.user?.id === playerId,
);
if (targetSocket) {
const items = isEmpty
? []
: await Inventory.findAll({
where: { playerId },
attributes: ["itemId", "quantity"],
});
targetSocket.emit("player:inventory_data", items);
}
}
}
module.exports = new AdminManager();

View File

@ -1,33 +1,93 @@
const Notification = require("../models/Notification"); const Notification = require("../models/Notification");
class NotificationManager { class NotificationManager {
async createNotification({ playerId, type, title, message, data = {} }) { constructor() {
this.io = null;
}
init(io) {
this.io = io;
console.log("[NotificationManager] Initialized with Socket.io");
}
async send({
playerId,
type = "info",
title,
message,
data = {},
priority = "normal",
}) {
try { try {
return await Notification.create({ const notification = await Notification.create({
playerId, playerId,
type, type,
title, title,
message, message,
data, data,
priority,
isRead: false,
}); });
const targetSocket = this._getSocketByPlayerId(playerId);
if (targetSocket) {
targetSocket.emit("notification:new", notification);
const unreadCount = await this.getUnreadCount(playerId);
targetSocket.emit("notifications:unread_count", unreadCount);
}
return notification;
} catch (error) { } catch (error) {
console.error("Notify Error:", error); console.error(
`[NotificationManager] Error sending to ${playerId}:`,
error,
);
} }
} }
async getPlayerNotifications(playerId) { _getSocketByPlayerId(playerId) {
if (!this.io) return null;
return [...this.io.sockets.sockets.values()].find(
(s) => s.user?.id === playerId,
);
}
async getPlayerHistory(playerId, limit = 50) {
return await Notification.findAll({ return await Notification.findAll({
where: { playerId }, where: { playerId },
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],
limit: 20, limit,
}); });
} }
async markAsRead(notificationId) { async getUnreadCount(playerId) {
return await Notification.update( return await Notification.count({
where: { playerId, isRead: false },
});
}
async markAsRead(notificationId, playerId) {
await Notification.update(
{ isRead: true }, { isRead: true },
{ where: { id: notificationId } }, { where: { id: notificationId, playerId } },
); );
return await this.getUnreadCount(playerId);
}
async markAllAsRead(playerId) {
await Notification.update(
{ isRead: true },
{ where: { playerId, isRead: false } },
);
return 0;
}
async delete(notificationId, playerId) {
await Notification.destroy({
where: { id: notificationId, playerId },
});
return await this.getUnreadCount(playerId);
} }
} }

View File

@ -10,6 +10,8 @@ const DatapackLoader = require("./game/DatapackLoader.js");
const path = require("path"); const path = require("path");
const app = express(); const app = express();
const economyService = require("./game/EconomyService.js"); const economyService = require("./game/EconomyService.js");
const NotificationManager = require("./game/NotificationManager.js");
const AdminManager = require("./game/AdminManager.js");
app.use( app.use(
cors({ cors({
@ -80,6 +82,8 @@ server.listen(config.port, async () => {
DatapackLoader.init(datapacksPath, io); DatapackLoader.init(datapacksPath, io);
await sequelize.initDatabase(); await sequelize.initDatabase();
initSockets(io); initSockets(io);
NotificationManager.init(io);
AdminManager.init(io);
economyService.init(io); economyService.init(io);
await registerInApi(); await registerInApi();
setInterval(sendHeartbeat, HEARTBEAT_INTERVAL); setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);

View File

@ -1,7 +1,8 @@
const { Player, Inventory } = require("../../models"); const adminManager = require("../../game/AdminManager");
const DatapackLoader = require("../../game/DatapackLoader");
module.exports = (io, socket) => { module.exports = (io, socket) => {
if (!adminManager.io) adminManager.init(io);
const handleAdminCommand = async ({ command }) => { const handleAdminCommand = async ({ command }) => {
const args = command.trim().split(/\s+/); const args = command.trim().split(/\s+/);
const cmd = args[0].toLowerCase(); const cmd = args[0].toLowerCase();
@ -13,76 +14,33 @@ module.exports = (io, socket) => {
const amount = parseInt(amountStr) || 1; const amount = parseInt(amountStr) || 1;
if (!targetName || !itemId) { if (!targetName || !itemId) {
throw new Error("Usage: /give [player_name] [item_id] [amount]"); throw new Error("Usage: /give [player] [item] [amount]");
}
const targetPlayer = await Player.findOne({
where: { username: targetName },
});
if (!targetPlayer)
throw new Error(`Player '${targetName}' not found in database.`);
const itemData = DatapackLoader.getItem(itemId);
if (!itemData)
throw new Error(`Item ID '${itemId}' does not exist in datapacks.`);
const [inventoryItem, created] = await Inventory.findOrCreate({
where: { playerId: targetPlayer.id, itemId: itemId },
defaults: { quantity: amount },
});
if (!created) {
await inventoryItem.increment("quantity", { by: amount });
} }
const result = await adminManager.giveItem(
targetName,
itemId,
amount,
);
socket.emit( socket.emit(
"admin:log", "admin:log",
`Successfully gave ${amount}x [${itemId}] to ${targetName}.`, `Successfully gave ${result.amount}x [${result.itemId}] to ${result.targetName}.`,
); );
const targetSocket = [...io.sockets.sockets.values()].find(
(s) => s.user?.id === targetPlayer.id,
);
if (targetSocket) {
const updatedItems = await Inventory.findAll({
where: { playerId: targetPlayer.id },
attributes: ["itemId", "quantity"],
});
targetSocket.emit("player:inventory_data", updatedItems);
targetSocket.emit(
"admin:log",
`Admin gave you ${amount}x ${itemId}`,
);
}
break; break;
} }
case "/clear": { case "/clear": {
const [_, targetName] = args; const [_, targetName] = args;
const targetPlayer = await Player.findOne({ if (!targetName) throw new Error("Usage: /clear [player]");
where: { username: targetName },
});
if (!targetPlayer) throw new Error("Player not found.");
await Inventory.destroy({ where: { playerId: targetPlayer.id } }); const target = await adminManager.clearInventory(targetName);
socket.emit("admin:log", `Inventory for ${target} has been wiped.`);
socket.emit(
"admin:log",
`Inventory for ${targetName} has been wiped.`,
);
const targetSocket = [...io.sockets.sockets.values()].find(
(s) => s.user?.id === targetPlayer.id,
);
if (targetSocket) targetSocket.emit("player:inventory_data", []);
break; break;
} }
case "/reload_data": { case "/reload_data": {
socket.emit("admin:log", "Reloading all datapacks..."); socket.emit("admin:log", "Reloading all datapacks...");
DatapackLoader.loadAll(); await adminManager.reloadData();
io.emit("admin:log", "System: Datapacks reloaded by admin.");
break; break;
} }
@ -90,7 +48,6 @@ module.exports = (io, socket) => {
socket.emit("admin:log", `Unknown admin command: ${cmd}`); socket.emit("admin:log", `Unknown admin command: ${cmd}`);
} }
} catch (err) { } catch (err) {
console.error("Admin Command Error:", err.message);
socket.emit("admin:log", `Error: ${err.message}`); socket.emit("admin:log", `Error: ${err.message}`);
} }
}; };

View File

@ -42,6 +42,7 @@ module.exports = (io, socket) => {
if (messageData.type === "global") { if (messageData.type === "global") {
io.emit("chat:new_message", messageData); io.emit("chat:new_message", messageData);
} else { } else {
console.log(payload.receiverId);
socket socket
.to(`user_${payload.receiverId}`) .to(`user_${payload.receiverId}`)
.emit("chat:new_message", messageData); .emit("chat:new_message", messageData);

View File

@ -51,7 +51,7 @@ module.exports = (io, socket) => {
const exists = await Friend.findOne({ const exists = await Friend.findOne({
where: { playerId: myId, friendId: friendId }, where: { playerId: myId, friendId: friendId },
}); });
console.log(myId, friendId);
if (!exists) { if (!exists) {
await Friend.bulkCreate([ await Friend.bulkCreate([
{ playerId: myId, friendId: friendId }, { playerId: myId, friendId: friendId },

View File

@ -37,11 +37,11 @@ module.exports = (io, socket) => {
socket.on("notification:dismiss", async ({ id }) => { socket.on("notification:dismiss", async ({ id }) => {
try { try {
await Notification.destroy({ await Notification.destroy({
where: { id, playerId: socket.player.id }, where: { id, playerId: socket.user.id },
}); });
const unreadCount = await Notification.count({ const unreadCount = await Notification.count({
where: { playerId: socket.player.id, isRead: false }, where: { playerId: socket.user.id, isRead: false },
}); });
socket.emit("notifications:unread_count", unreadCount); socket.emit("notifications:unread_count", unreadCount);
} catch (e) { } catch (e) {