notifications, chat update
This commit is contained in:
parent
342a674456
commit
6d49b93869
70
game-server/src/game/ChatManager.js
Normal file
70
game-server/src/game/ChatManager.js
Normal 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();
|
||||
34
game-server/src/game/NotificationManager.js
Normal file
34
game-server/src/game/NotificationManager.js
Normal 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();
|
||||
16
game-server/src/models/Friend.js
Normal file
16
game-server/src/models/Friend.js
Normal 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;
|
||||
23
game-server/src/models/Message.js
Normal file
23
game-server/src/models/Message.js
Normal 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;
|
||||
29
game-server/src/models/Notification.js
Normal file
29
game-server/src/models/Notification.js
Normal 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;
|
||||
27
game-server/src/models/associations.js
Normal file
27
game-server/src/models/associations.js
Normal 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;
|
||||
@ -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,
|
||||
};
|
||||
|
||||
63
game-server/src/sockets/handlers/chatHandler.js
Normal file
63
game-server/src/sockets/handlers/chatHandler.js
Normal 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);
|
||||
});
|
||||
};
|
||||
114
game-server/src/sockets/handlers/friendHandler.js
Normal file
114
game-server/src/sockets/handlers/friendHandler.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
51
game-server/src/sockets/handlers/notificationHandler.js
Normal file
51
game-server/src/sockets/handlers/notificationHandler.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user