Added AdminManager. Updated NotificationManager
This commit is contained in:
parent
2c61c8c05c
commit
1de6fc980d
@ -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) => {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
91
game-server/src/game/AdminManager.js
Normal file
91
game-server/src/game/AdminManager.js
Normal 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();
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user