notifications, chat update

This commit is contained in:
MaksSlyzar 2026-03-29 14:14:54 +03:00
parent 342a674456
commit 6d49b93869
11 changed files with 437 additions and 0 deletions

View File

@ -0,0 +1,70 @@
const Player = require("../models/Player");
const { Op } = require("sequelize");
const Message = require("../models/Message.js");
class ChatManager {
async getGlobalHistory(limit = 50) {
try {
return await Message.findAll({
where: { type: "global" },
limit,
order: [["createdAt", "ASC"]],
include: [
{
model: Player,
as: "sender",
attributes: ["username"],
},
],
});
} catch (error) {
console.error("History Error:", error);
return [];
}
}
async saveMessage({ content, type, senderId, receiverId = null }) {
try {
const newMessage = await Message.create({
content,
type,
senderId,
receiverId,
});
const sender = await Player.findByPk(senderId, {
attributes: ["username"],
});
return {
id: newMessage.id,
content: newMessage.content,
type: newMessage.type,
senderId: newMessage.senderId,
senderName: sender ? sender.username : "Unknown",
receiverId: newMessage.receiverId,
createdAt: newMessage.createdAt,
};
} catch (error) {
console.error("Save Message Error:", error);
throw error;
}
}
async searchPlayers(query, excludeId) {
try {
return await Player.findAll({
where: {
username: { [Op.like]: `%${query}%` },
id: { [Op.ne]: excludeId },
},
attributes: ["id", "username", "level"],
limit: 10,
});
} catch (error) {
console.error("Search Error:", error);
return [];
}
}
}
module.exports = new ChatManager();

View File

@ -0,0 +1,34 @@
const Notification = require("../models/Notification");
class NotificationManager {
async createNotification({ playerId, type, title, message, data = {} }) {
try {
return await Notification.create({
playerId,
type,
title,
message,
data,
});
} catch (error) {
console.error("Notify Error:", error);
}
}
async getPlayerNotifications(playerId) {
return await Notification.findAll({
where: { playerId },
order: [["createdAt", "DESC"]],
limit: 20,
});
}
async markAsRead(notificationId) {
return await Notification.update(
{ isRead: true },
{ where: { id: notificationId } },
);
}
}
module.exports = new NotificationManager();

View File

@ -0,0 +1,16 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../config/db");
const Friend = sequelize.define("Friend", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
status: {
type: DataTypes.ENUM("pending", "accepted"),
defaultValue: "pending",
},
});
module.exports = Friend;

View File

@ -0,0 +1,23 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../config/db");
const Message = sequelize.define("Message", {
content: {
type: DataTypes.TEXT,
allowNull: false,
},
type: {
type: DataTypes.ENUM("global", "private"),
defaultValue: "global",
},
senderId: {
type: DataTypes.STRING,
allowNull: false,
},
receiverId: {
type: DataTypes.STRING,
allowNull: true,
},
});
module.exports = Message;

View File

@ -0,0 +1,29 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../config/db");
const Notification = sequelize.define("Notification", {
playerId: {
type: DataTypes.STRING,
allowNull: false,
},
type: {
type: DataTypes.ENUM("friend_request", "crafting", "system", "trade"),
allowNull: false,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
message: {
type: DataTypes.TEXT,
},
data: {
type: DataTypes.JSON,
},
isRead: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
});
module.exports = Notification;

View File

@ -0,0 +1,27 @@
const Player = require("./Player");
const Message = require("./Message.js");
const Friend = require("./Friend");
const setupAssociations = () => {
Message.belongsTo(Player, {
as: "sender",
foreignKey: "senderId",
});
Message.belongsTo(Player, {
as: "receiver",
foreignKey: "receiverId",
});
Player.hasMany(Message, {
foreignKey: "senderId",
});
Player.belongsToMany(Player, {
through: Friend,
as: "Friends",
foreignKey: "playerId",
otherKey: "friendId",
});
};
module.exports = setupAssociations;

View File

@ -1,12 +1,16 @@
const sequelize = require("../config/db");
const Player = require("./Player");
const Inventory = require("./Inventory");
const setupAssociations = require("./associations");
const Notification = require("./Notification");
Player.hasMany(Inventory, { foreignKey: "playerId", as: "inventory" });
Inventory.belongsTo(Player, { foreignKey: "playerId" });
setupAssociations();
module.exports = {
sequelize,
Player,
Inventory,
Notification,
};

View File

@ -0,0 +1,63 @@
const chatManager = require("../../game/ChatManager");
module.exports = (io, socket) => {
socket.on("chat:get_global_history", async () => {
const history = await chatManager.getGlobalHistory();
const formattedHistory = history.map((m) => ({
id: m.id,
content: m.content,
senderName: m.sender?.username || "Unknown",
senderId: m.senderId,
createdAt: m.createdAt,
type: m.type,
}));
socket.emit("chat:global_history", formattedHistory);
});
socket.on("chat:get_global_history", async () => {
console.log(`Player ${socket.player?.id} requested chat history`);
const history = await chatManager.getGlobalHistory();
const formattedHistory = history.map((m) => ({
id: m.id,
content: m.content,
senderName: m.sender?.username || "Unknown",
senderId: m.senderId,
createdAt: m.createdAt,
type: m.type,
}));
socket.emit("chat:global_history", formattedHistory);
});
socket.on("chat:send_message", async (payload) => {
try {
const senderId = socket.user?.id;
if (!senderId) return;
const messageData = await chatManager.saveMessage({
content: payload.content,
type: payload.type || "global",
senderId: senderId,
receiverId: payload.receiverId,
});
if (messageData.type === "global") {
io.emit("chat:new_message", messageData);
} else {
socket
.to(`user_${payload.receiverId}`)
.emit("chat:new_message", messageData);
socket.emit("chat:new_message", messageData);
}
} catch (err) {
console.log(err);
socket.emit("error", { message: "CHAT_SEND_ERROR" });
}
});
socket.on("player:search", async ({ query }) => {
const senderId = socket.player?.id;
if (!query || !senderId) return;
const results = await chatManager.searchPlayers(query, senderId);
socket.emit("player:search_results", results);
});
};

View File

@ -0,0 +1,114 @@
const Player = require("../../models/Player");
const Friend = require("../../models/Friend");
const NotificationManager = require("../../game/NotificationManager");
const Notification = require("../../models/Notification");
module.exports = (io, socket) => {
socket.on("player:search", async ({ query }) => {
try {
const players = await Player.findAll({
where: {
username: { [require("sequelize").Op.like]: `%${query}%` },
id: { [require("sequelize").Op.ne]: socket.user.id },
},
limit: 5,
attributes: ["id", "username", "level"],
});
socket.emit("player:search_results", players);
} catch (e) {
console.error(e);
}
});
socket.on("friend:add", async ({ friendId }) => {
try {
const player = socket.user;
await NotificationManager.createNotification({
playerId: friendId,
type: "friend_request",
title: "NEW FRIEND REQUEST",
message: `${player.username} wants to add you as a friend.`,
data: { fromId: player.id },
});
io.to(friendId).emit("notification:new", {
type: "friend_request",
title: "NEW FRIEND REQUEST",
message: `${player.username} wants to add you as a friend.`,
data: { fromId: player.id },
createdAt: new Date(),
});
} catch (e) {
socket.emit("error", { message: "FAILED_TO_SEND_REQUEST" });
}
});
socket.on("friend:accept", async ({ friendId, notificationId }) => {
try {
const myId = socket.user.id;
const exists = await Friend.findOne({
where: { playerId: myId, friendId: friendId },
});
if (!exists) {
await Friend.bulkCreate([
{ playerId: myId, friendId: friendId },
{ playerId: friendId, friendId: myId },
]);
await Notification.destroy({
where: { id: notificationId, playerId: myId },
});
const myUpdated = await Player.findByPk(myId, {
include: [
{
model: Player,
as: "Friends",
attributes: ["id", "username", "level"],
},
],
});
socket.emit("friend:list", myUpdated.Friends || []);
const friendUpdated = await Player.findByPk(friendId, {
include: [
{
model: Player,
as: "Friends",
attributes: ["id", "username", "level"],
},
],
});
io.to(friendId).emit("friend:list", friendUpdated.Friends || []);
const unreadCount = await Notification.count({
where: { playerId: myId, isRead: false },
});
socket.emit("notifications:unread_count", unreadCount);
}
} catch (e) {
console.error(e);
socket.emit("error", { message: "FAILED_TO_ACCEPT_FRIEND" });
}
});
socket.on("friend:get_list", async () => {
try {
const player = await Player.findByPk(socket.user.id, {
include: [
{
model: Player,
as: "Friends",
attributes: ["id", "username", "level"],
},
],
});
socket.emit("friend:list", player.Friends || []);
} catch (e) {
console.error(e);
}
});
};

View File

@ -0,0 +1,51 @@
const Notification = require("../../models/Notification");
module.exports = (io, socket) => {
socket.on("notifications:get_all", async () => {
try {
const list = await Notification.findAll({
where: { playerId: socket.user.id },
order: [["createdAt", "DESC"]],
limit: 50,
});
socket.emit("notifications:list", list);
const unreadCount = list.filter((n) => !n.isRead).length;
socket.emit("notifications:unread_count", unreadCount);
} catch (e) {
console.error("Fetch notifications error:", e);
}
});
socket.on("notification:read", async ({ id }) => {
try {
await Notification.update(
{ isRead: true },
{ where: { id, playerId: socket.user.id } },
);
const unreadCount = await Notification.count({
where: { playerId: socket.user.id, isRead: false },
});
socket.emit("notifications:unread_count", unreadCount);
} catch (e) {
console.error("Read notification error:", e);
}
});
socket.on("notification:dismiss", async ({ id }) => {
try {
await Notification.destroy({
where: { id, playerId: socket.player.id },
});
const unreadCount = await Notification.count({
where: { playerId: socket.player.id, isRead: false },
});
socket.emit("notifications:unread_count", unreadCount);
} catch (e) {
console.error("Dismiss notification error:", e);
}
});
};

View File

@ -4,6 +4,9 @@ const inventoryHandler = require("./handlers/inventoryHandler");
const craftingHandler = require("./handlers/craftingHandler");
const adminHandler = require("./handlers/adminHandler");
const dungeonHandler = require("./handlers/dungeonHandler");
const chatHandler = require("./handlers/chatHandler");
const friendHandler = require("./handlers/friendHandler");
const notificationHandler = require("./handlers/notificationHandler");
const initSockets = (io) => {
io.use(socketAuth);
@ -14,6 +17,9 @@ const initSockets = (io) => {
craftingHandler(io, socket);
adminHandler(io, socket);
dungeonHandler(io, socket);
chatHandler(io, socket);
friendHandler(io, socket);
notificationHandler(io, socket);
});
};