This commit is contained in:
parent
27a06adbe6
commit
365aea2997
@ -60,7 +60,7 @@ jobs:
|
||||
image: harbor.galpodlipnik.com/chat-app/backend:latest
|
||||
restart: always
|
||||
env_file:
|
||||
- ./backend/.env
|
||||
- ./.env
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
|
||||
@ -6,6 +6,8 @@ COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run db:generate
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
22
backend/dist/config/database.js
vendored
Normal file
22
backend/dist/config/database.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.disconnectDatabase = exports.connectDatabase = exports.prisma = void 0;
|
||||
const client_1 = require("@prisma/client");
|
||||
exports.prisma = new client_1.PrismaClient({
|
||||
log: ["query", "info", "warn", "error"],
|
||||
});
|
||||
const connectDatabase = async () => {
|
||||
try {
|
||||
await exports.prisma.$connect();
|
||||
console.log("✅ Database connected successfully");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("❌ Database connection failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
exports.connectDatabase = connectDatabase;
|
||||
const disconnectDatabase = async () => {
|
||||
await exports.prisma.$disconnect();
|
||||
};
|
||||
exports.disconnectDatabase = disconnectDatabase;
|
||||
12
backend/dist/config/env.js
vendored
Normal file
12
backend/dist/config/env.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.env = void 0;
|
||||
const zod_1 = require("zod");
|
||||
const envSchema = zod_1.z.object({
|
||||
CORS_ORIGIN: zod_1.z.string().default("*"),
|
||||
DATABASE_URL: zod_1.z.string(),
|
||||
JWT_SECRET: zod_1.z.string(),
|
||||
NODE_ENV: zod_1.z.enum(["development", "production", "test"]).default("development"),
|
||||
PORT: zod_1.z.string().transform(Number).default("3000"),
|
||||
});
|
||||
exports.env = envSchema.parse(process.env);
|
||||
17
backend/dist/controllers/authController.js
vendored
Normal file
17
backend/dist/controllers/authController.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthController = void 0;
|
||||
const authService_js_1 = require("../services/authService.js");
|
||||
class AuthController {
|
||||
static async register(req, res) {
|
||||
const data = req.body;
|
||||
const result = await authService_js_1.AuthService.register(data);
|
||||
res.status(result.success ? 201 : 400).json(result);
|
||||
}
|
||||
static async login(req, res) {
|
||||
const data = req.body;
|
||||
const result = await authService_js_1.AuthService.login(data);
|
||||
res.status(result.success ? 200 : 400).json(result);
|
||||
}
|
||||
}
|
||||
exports.AuthController = AuthController;
|
||||
76
backend/dist/controllers/chatController.js
vendored
Normal file
76
backend/dist/controllers/chatController.js
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ChatController = void 0;
|
||||
const chatService_js_1 = require("../services/chatService.js");
|
||||
class ChatController {
|
||||
static async getChatRooms(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const result = await chatService_js_1.ChatService.getChatRooms(req.user.userId);
|
||||
res.status(result.success ? 200 : 500).json(result);
|
||||
}
|
||||
static async createChatRoom(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const data = req.body;
|
||||
const result = await chatService_js_1.ChatService.createChatRoom(req.user.userId, data);
|
||||
res.status(result.success ? 201 : 500).json(result);
|
||||
}
|
||||
static async getMessages(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const query = {
|
||||
roomId: req.params.roomId,
|
||||
page: req.query.page,
|
||||
limit: req.query.limit,
|
||||
};
|
||||
const result = await chatService_js_1.ChatService.getMessages(query);
|
||||
res.status(result.success ? 200 : 500).json(result);
|
||||
}
|
||||
static async updateChatRoom(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const roomId = req.params.roomId;
|
||||
const data = req.body;
|
||||
const result = await chatService_js_1.ChatService.updateChatRoom(req.user.userId, roomId, data);
|
||||
res.status(result.success ? 200 : result.status || 400).json(result);
|
||||
}
|
||||
static async deleteChatRoom(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const roomId = req.params.roomId;
|
||||
const result = await chatService_js_1.ChatService.deleteChatRoom(req.user.userId, roomId);
|
||||
res.status(result.success ? 200 : result.status || 500).json(result);
|
||||
}
|
||||
static async addChatRoomMember(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const roomId = req.params.roomId;
|
||||
const data = req.body;
|
||||
const result = await chatService_js_1.ChatService.addChatRoomMember(req.user.userId, roomId, data);
|
||||
res.status(result.success ? 200 : result.status || 500).json(result);
|
||||
}
|
||||
static async removeChatRoomMember(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const roomId = req.params.roomId;
|
||||
const data = req.body;
|
||||
const result = await chatService_js_1.ChatService.removeChatRoomMember(req.user.userId, roomId, data);
|
||||
res.status(result.success ? 200 : result.status || 500).json(result);
|
||||
}
|
||||
}
|
||||
exports.ChatController = ChatController;
|
||||
16
backend/dist/controllers/userController.js
vendored
Normal file
16
backend/dist/controllers/userController.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserController = void 0;
|
||||
const userService_1 = require("../services/userService");
|
||||
class UserController {
|
||||
static async updateProfile(req, res) {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, error: "Unauthorized" });
|
||||
return;
|
||||
}
|
||||
const data = req.body;
|
||||
const result = await userService_1.UserService.updateProfile(req.user.userId, data);
|
||||
res.status(result.success ? 200 : 400).json(result);
|
||||
}
|
||||
}
|
||||
exports.UserController = UserController;
|
||||
66
backend/dist/index.js
vendored
Normal file
66
backend/dist/index.js
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const cors_1 = __importDefault(require("cors"));
|
||||
const express_1 = __importDefault(require("express"));
|
||||
const http_1 = __importDefault(require("http"));
|
||||
const socket_io_1 = require("socket.io");
|
||||
const database_js_1 = require("./config/database.js");
|
||||
const env_js_1 = require("./config/env.js");
|
||||
const auth_js_1 = require("./middleware/auth.js");
|
||||
const index_js_1 = require("./routes/index.js");
|
||||
const socketHandlers_js_1 = require("./socket/socketHandlers.js");
|
||||
const app = (0, express_1.default)();
|
||||
const server = http_1.default.createServer(app);
|
||||
const io = new socket_io_1.Server(server, {
|
||||
cors: {
|
||||
methods: ["GET", "POST", "PUT", "DELETE"],
|
||||
origin: "http://localhost:5173",
|
||||
},
|
||||
});
|
||||
app.use((0, cors_1.default)({
|
||||
methods: ["GET", "POST", "PUT", "DELETE"],
|
||||
origin: "http://localhost:5173",
|
||||
}));
|
||||
app.use(express_1.default.json({ limit: "5mb" }));
|
||||
app.use("/api", index_js_1.apiRoutes);
|
||||
app.get("/health", (_, res) => {
|
||||
res.json({ status: "OK", timestamp: new Date().toISOString() });
|
||||
});
|
||||
io.use(auth_js_1.authenticateSocket);
|
||||
io.on("connection", async (socket) => {
|
||||
await (0, socketHandlers_js_1.handleConnection)(io, socket);
|
||||
});
|
||||
app.use((err, _req, res, _next) => {
|
||||
console.error("Unhandled error:", err);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: env_js_1.env.NODE_ENV === "production" ? "Internal server error" : err.message,
|
||||
});
|
||||
});
|
||||
const startServer = async () => {
|
||||
try {
|
||||
await (0, database_js_1.connectDatabase)();
|
||||
server.listen(env_js_1.env.PORT, () => {
|
||||
console.log(`🚀 Server running on port ${env_js_1.env.PORT}`);
|
||||
console.log(`📊 Environment: ${env_js_1.env.NODE_ENV}`);
|
||||
console.log(`🔗 CORS Origin: ${env_js_1.env.CORS_ORIGIN}`);
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Failed to start server:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
process.on("SIGTERM", () => {
|
||||
console.log("SIGTERM received, shutting down gracefully...");
|
||||
server.close(() => {
|
||||
console.log("HTTP server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
(async () => {
|
||||
await startServer();
|
||||
})();
|
||||
51
backend/dist/middleware/auth.js
vendored
Normal file
51
backend/dist/middleware/auth.js
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.authenticateSocket = exports.authenticateToken = void 0;
|
||||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||||
const database_js_1 = require("../config/database.js");
|
||||
const env_js_1 = require("../config/env.js");
|
||||
const authenticateToken = (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
const token = authHeader?.split(" ")[1];
|
||||
if (!token) {
|
||||
res.status(401).json({ success: false, error: "Access token required" });
|
||||
return;
|
||||
}
|
||||
const decoded = jsonwebtoken_1.default.verify(token, env_js_1.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Authentication error:", error);
|
||||
res.status(403).json({ success: false, error: "Invalid or expired token" });
|
||||
}
|
||||
};
|
||||
exports.authenticateToken = authenticateToken;
|
||||
const authenticateSocket = async (socket, next) => {
|
||||
try {
|
||||
const token = socket.handshake.auth.token;
|
||||
if (!token) {
|
||||
return next(new Error("Authentication token required"));
|
||||
}
|
||||
const decoded = jsonwebtoken_1.default.verify(token, env_js_1.env.JWT_SECRET);
|
||||
const user = await database_js_1.prisma.user.findUnique({
|
||||
where: {
|
||||
id: decoded.userId,
|
||||
},
|
||||
});
|
||||
if (!user)
|
||||
return next(new Error("User not found"));
|
||||
const authenticateSocket = socket;
|
||||
authenticateSocket.userId = user.id;
|
||||
authenticateSocket.user = user;
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Socket authentication error:", error);
|
||||
}
|
||||
};
|
||||
exports.authenticateSocket = authenticateSocket;
|
||||
42
backend/dist/middleware/rateLimiter.js
vendored
Normal file
42
backend/dist/middleware/rateLimiter.js
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.checkMessageRateLimit = exports.messageRateLimiter = void 0;
|
||||
class RateLimiter {
|
||||
limits = new Map();
|
||||
maxMessages;
|
||||
windowMs;
|
||||
constructor(maxMessages = 10, windowMs = 60000) {
|
||||
this.maxMessages = maxMessages;
|
||||
this.windowMs = windowMs;
|
||||
}
|
||||
checkLimit(userId) {
|
||||
const now = Date.now();
|
||||
const userLimit = this.limits.get(userId) ?? { count: 0, resetTime: now + this.windowMs };
|
||||
if (now > userLimit.resetTime) {
|
||||
userLimit.count = 0;
|
||||
userLimit.resetTime = now + this.windowMs;
|
||||
}
|
||||
userLimit.count++;
|
||||
this.limits.set(userId, userLimit);
|
||||
return userLimit.count <= this.maxMessages;
|
||||
}
|
||||
getRemainingTime(userId) {
|
||||
const userLimit = this.limits.get(userId);
|
||||
if (!userLimit)
|
||||
return 0;
|
||||
return Math.max(0, userLimit.resetTime - Date.now());
|
||||
}
|
||||
}
|
||||
exports.messageRateLimiter = new RateLimiter(10, 60000); // 10 messages per minute
|
||||
const checkMessageRateLimit = (socket) => {
|
||||
const canSend = exports.messageRateLimiter.checkLimit(socket.userId);
|
||||
if (!canSend) {
|
||||
const remainingTime = exports.messageRateLimiter.getRemainingTime(socket.userId);
|
||||
socket.emit("rate_limit_exceeded", {
|
||||
message: `Too many messages. Please wait ${Math.ceil(remainingTime / 1000)} seconds before sending another message.`,
|
||||
remainingTime,
|
||||
});
|
||||
}
|
||||
return canSend;
|
||||
};
|
||||
exports.checkMessageRateLimit = checkMessageRateLimit;
|
||||
54
backend/dist/middleware/validation.js
vendored
Normal file
54
backend/dist/middleware/validation.js
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.removeChatRoomMemberSchema = exports.addChatRoomMemberSchema = exports.updateChatRoomSchema = exports.updateUserSchema = exports.createChatRoomSchema = exports.registerSchema = exports.loginSchema = exports.validate = void 0;
|
||||
const zod_1 = require("zod");
|
||||
const validate = (schema) => {
|
||||
return (req, res, next) => {
|
||||
try {
|
||||
schema.parse(req.body);
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof zod_1.z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: "Validation failed",
|
||||
details: error.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
};
|
||||
exports.validate = validate;
|
||||
exports.loginSchema = zod_1.z.object({
|
||||
email: zod_1.z.string().email("Invalid email format"),
|
||||
password: zod_1.z.string().min(6, "Password must be at least 6 characters long"),
|
||||
});
|
||||
exports.registerSchema = zod_1.z.object({
|
||||
username: zod_1.z.string().min(3, "Username must be at least 3 characters long"),
|
||||
email: zod_1.z.string().email("Invalid email format"),
|
||||
password: zod_1.z.string().min(6, "Password must be at least 6 characters long"),
|
||||
});
|
||||
exports.createChatRoomSchema = zod_1.z.object({
|
||||
name: zod_1.z.string().min(1, "Room name is required"),
|
||||
description: zod_1.z.string().optional(),
|
||||
memberUsernames: zod_1.z.array(zod_1.z.string()).default([]),
|
||||
});
|
||||
exports.updateUserSchema = zod_1.z.object({
|
||||
username: zod_1.z.string().min(3, "Username must be at least 3 characters long").optional(),
|
||||
email: zod_1.z.string().email("Invalid email format").optional(),
|
||||
password: zod_1.z.string().min(6, "Password must be at least 6 characters long").optional(),
|
||||
avatar: zod_1.z.string().nullable().optional(),
|
||||
});
|
||||
exports.updateChatRoomSchema = zod_1.z.object({
|
||||
name: zod_1.z.string().min(1, "Room name is required"),
|
||||
description: zod_1.z.string().optional(),
|
||||
});
|
||||
exports.addChatRoomMemberSchema = zod_1.z.object({
|
||||
username: zod_1.z.string().min(1, "Username is required"),
|
||||
});
|
||||
exports.removeChatRoomMemberSchema = zod_1.z.object({
|
||||
userId: zod_1.z.string().min(1, "User ID is required"),
|
||||
});
|
||||
10
backend/dist/routes/authRoutes.js
vendored
Normal file
10
backend/dist/routes/authRoutes.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.authRoutes = void 0;
|
||||
const authController_js_1 = require("../controllers/authController.js");
|
||||
const validation_js_1 = require("../middleware/validation.js");
|
||||
const express_1 = require("express");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.authRoutes = router;
|
||||
router.post("/register", (0, validation_js_1.validate)(validation_js_1.registerSchema), authController_js_1.AuthController.register);
|
||||
router.post("/login", (0, validation_js_1.validate)(validation_js_1.loginSchema), authController_js_1.AuthController.login);
|
||||
17
backend/dist/routes/chatRoutes.js
vendored
Normal file
17
backend/dist/routes/chatRoutes.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.chatRoutes = void 0;
|
||||
const chatController_js_1 = require("../controllers/chatController.js");
|
||||
const auth_js_1 = require("../middleware/auth.js");
|
||||
const validation_js_1 = require("../middleware/validation.js");
|
||||
const express_1 = require("express");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.chatRoutes = router;
|
||||
router.use(auth_js_1.authenticateToken);
|
||||
router.get("/chat-rooms", chatController_js_1.ChatController.getChatRooms);
|
||||
router.post("/chat-rooms", (0, validation_js_1.validate)(validation_js_1.createChatRoomSchema), chatController_js_1.ChatController.createChatRoom);
|
||||
router.put("/chat-rooms/:roomId", (0, validation_js_1.validate)(validation_js_1.updateChatRoomSchema), chatController_js_1.ChatController.updateChatRoom);
|
||||
router.delete("/chat-rooms/:roomId", chatController_js_1.ChatController.deleteChatRoom);
|
||||
router.post("/chat-rooms/:roomId/members", (0, validation_js_1.validate)(validation_js_1.addChatRoomMemberSchema), chatController_js_1.ChatController.addChatRoomMember);
|
||||
router.delete("/chat-rooms/:roomId/members", (0, validation_js_1.validate)(validation_js_1.removeChatRoomMemberSchema), chatController_js_1.ChatController.removeChatRoomMember);
|
||||
router.get("/messages/:roomId", chatController_js_1.ChatController.getMessages);
|
||||
12
backend/dist/routes/index.js
vendored
Normal file
12
backend/dist/routes/index.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.apiRoutes = void 0;
|
||||
const express_1 = require("express");
|
||||
const authRoutes_js_1 = require("./authRoutes.js");
|
||||
const chatRoutes_js_1 = require("./chatRoutes.js");
|
||||
const userRoutes_js_1 = require("./userRoutes.js");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.apiRoutes = router;
|
||||
router.use("/auth", authRoutes_js_1.authRoutes);
|
||||
router.use("/users", userRoutes_js_1.userRoutes);
|
||||
router.use("/", chatRoutes_js_1.chatRoutes);
|
||||
11
backend/dist/routes/userRoutes.js
vendored
Normal file
11
backend/dist/routes/userRoutes.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.userRoutes = void 0;
|
||||
const userController_js_1 = require("../controllers/userController.js");
|
||||
const auth_js_1 = require("../middleware/auth.js");
|
||||
const validation_js_1 = require("../middleware/validation.js");
|
||||
const express_1 = require("express");
|
||||
const router = (0, express_1.Router)();
|
||||
exports.userRoutes = router;
|
||||
router.use(auth_js_1.authenticateToken);
|
||||
router.put("/profile", (0, validation_js_1.validate)(validation_js_1.updateUserSchema), userController_js_1.UserController.updateProfile);
|
||||
100
backend/dist/services/authService.js
vendored
Normal file
100
backend/dist/services/authService.js
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
const database_js_1 = require("../config/database.js");
|
||||
const env_js_1 = require("../config/env.js");
|
||||
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
||||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||||
class AuthService {
|
||||
static async register(data) {
|
||||
try {
|
||||
const existingUser = await database_js_1.prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email: data.email }, { username: data.username }],
|
||||
},
|
||||
});
|
||||
if (existingUser) {
|
||||
return {
|
||||
success: false,
|
||||
error: existingUser.email === data.email ? "Email already exists" : "Username already exists",
|
||||
};
|
||||
}
|
||||
const hashedPassword = await bcryptjs_1.default.hash(data.password, 12);
|
||||
const user = await database_js_1.prisma.user.create({
|
||||
data: {
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
const token = this.generateToken({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Registration failed",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async login(data) {
|
||||
try {
|
||||
const user = await database_js_1.prisma.user.findUnique({
|
||||
where: {
|
||||
email: data.email,
|
||||
},
|
||||
});
|
||||
if (!user || !(await bcryptjs_1.default.compare(data.password, user.password))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid email or password",
|
||||
};
|
||||
}
|
||||
const token = this.generateToken({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Login error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Login failed",
|
||||
};
|
||||
}
|
||||
}
|
||||
static generateToken(payload) {
|
||||
return jsonwebtoken_1.default.sign(payload, env_js_1.env.JWT_SECRET, { expiresIn: "7d" });
|
||||
}
|
||||
}
|
||||
exports.AuthService = AuthService;
|
||||
490
backend/dist/services/chatService.js
vendored
Normal file
490
backend/dist/services/chatService.js
vendored
Normal file
@ -0,0 +1,490 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ChatService = void 0;
|
||||
const database_js_1 = require("../config/database.js");
|
||||
class ChatService {
|
||||
static async getChatRooms(userId) {
|
||||
try {
|
||||
const chatRooms = await database_js_1.prisma.chatRoom.findMany({
|
||||
where: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isOnline: true,
|
||||
lastSeen: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: "desc",
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: chatRooms,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Get chat rooms error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to retrieve chat rooms",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async createChatRoom(userId, data) {
|
||||
try {
|
||||
const chatRoom = await database_js_1.prisma.chatRoom.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
createdBy: userId,
|
||||
members: {
|
||||
create: [
|
||||
{
|
||||
userId,
|
||||
role: "admin",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
if (data.memberUsernames.length > 0) {
|
||||
const users = await database_js_1.prisma.user.findMany({
|
||||
where: {
|
||||
username: {
|
||||
in: data.memberUsernames,
|
||||
},
|
||||
},
|
||||
});
|
||||
const memberData = users.map((user) => ({
|
||||
userId: user.id,
|
||||
roomId: chatRoom.id,
|
||||
}));
|
||||
await database_js_1.prisma.chatRoomMember.createMany({
|
||||
data: memberData,
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
const fullChatRoom = await database_js_1.prisma.chatRoom.findUnique({
|
||||
where: { id: chatRoom.id },
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isOnline: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: fullChatRoom,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Create chat room error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to create chat room",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async getMessages(query) {
|
||||
try {
|
||||
const page = Number.parseInt(query.page ?? "1");
|
||||
const limit = Number.parseInt(query.limit ?? "50");
|
||||
const skip = (page - 1) * limit;
|
||||
const messages = await database_js_1.prisma.message.findMany({
|
||||
where: {
|
||||
roomId: query.roomId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
seenBy: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: limit,
|
||||
skip,
|
||||
});
|
||||
const formattedMessages = messages.map((message) => ({
|
||||
...message,
|
||||
seenBy: message.seenBy.map((seen) => seen.userId),
|
||||
}));
|
||||
return {
|
||||
success: true,
|
||||
data: formattedMessages.reverse(),
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Get messages error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to retrieve messages",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async checkRoomMembership(userId, roomId) {
|
||||
try {
|
||||
const membership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
return !!membership;
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Check room membership error:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static async updateChatRoom(userId, roomId, data) {
|
||||
try {
|
||||
const membership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!membership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You are not a member of this chat room",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
if (membership.role !== "admin") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Only admins can update chat room settings",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
const updatedRoom = await database_js_1.prisma.chatRoom.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isOnline: true,
|
||||
lastSeen: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: updatedRoom,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Update chat room error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to update chat room",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
static async deleteChatRoom(userId, roomId) {
|
||||
try {
|
||||
const membership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!membership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You are not a member of this chat room",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
if (membership.role !== "admin") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Only admins can delete chat rooms",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
await database_js_1.prisma.chatRoom.delete({
|
||||
where: { id: roomId },
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: { id: roomId },
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Delete chat room error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to delete chat room",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
static async addChatRoomMember(userId, roomId, data) {
|
||||
try {
|
||||
const membership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!membership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You are not a member of this chat room",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
if (membership.role !== "admin") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Only admins can add members",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
const user = await database_js_1.prisma.user.findUnique({
|
||||
where: { username: data.username },
|
||||
});
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
error: "User not found",
|
||||
status: 404,
|
||||
};
|
||||
}
|
||||
const existingMembership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId: user.id,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (existingMembership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "User is already a member of this chat room",
|
||||
status: 409,
|
||||
};
|
||||
}
|
||||
await database_js_1.prisma.chatRoomMember.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
roomId,
|
||||
role: "member",
|
||||
},
|
||||
});
|
||||
const updatedRoom = await database_js_1.prisma.chatRoom.findUnique({
|
||||
where: { id: roomId },
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isOnline: true,
|
||||
lastSeen: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: updatedRoom,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Add chat room member error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to add member to chat room",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
static async removeChatRoomMember(userId, roomId, data) {
|
||||
try {
|
||||
const membership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!membership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "You are not a member of this chat room",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
if (membership.role !== "admin") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Only admins can remove members",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
// Check if target user is an admin
|
||||
const targetMembership = await database_js_1.prisma.chatRoomMember.findUnique({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId: data.userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!targetMembership) {
|
||||
return {
|
||||
success: false,
|
||||
error: "User is not a member of this chat room",
|
||||
status: 404,
|
||||
};
|
||||
}
|
||||
if (targetMembership.role === "admin") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Cannot remove an admin from the chat room",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
// Remove user from chat room
|
||||
await database_js_1.prisma.chatRoomMember.delete({
|
||||
where: {
|
||||
userId_roomId: {
|
||||
userId: data.userId,
|
||||
roomId,
|
||||
},
|
||||
},
|
||||
});
|
||||
// Get updated chat room
|
||||
const updatedRoom = await database_js_1.prisma.chatRoom.findUnique({
|
||||
where: { id: roomId },
|
||||
include: {
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
isOnline: true,
|
||||
lastSeen: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: updatedRoom,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Remove chat room member error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to remove member from chat room",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ChatService = ChatService;
|
||||
191
backend/dist/services/messageService.js
vendored
Normal file
191
backend/dist/services/messageService.js
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.MessageService = void 0;
|
||||
const database_js_1 = require("../config/database.js");
|
||||
class MessageService {
|
||||
static async sendMessage(userId, data) {
|
||||
try {
|
||||
const message = await database_js_1.prisma.message.create({
|
||||
data: {
|
||||
content: data.content,
|
||||
image: data.image,
|
||||
userId,
|
||||
roomId: data.roomId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatar: true,
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: message,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Send message error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to send message",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async reactToMessage(userId, data) {
|
||||
try {
|
||||
const existingReaction = await database_js_1.prisma.messageReaction.findUnique({
|
||||
where: {
|
||||
userId_messageId_type: {
|
||||
userId,
|
||||
messageId: data.messageId,
|
||||
type: data.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (existingReaction) {
|
||||
await database_js_1.prisma.messageReaction.delete({
|
||||
where: {
|
||||
id: existingReaction.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
await database_js_1.prisma.messageReaction.create({
|
||||
data: {
|
||||
userId,
|
||||
messageId: data.messageId,
|
||||
type: data.type,
|
||||
},
|
||||
});
|
||||
}
|
||||
const message = await database_js_1.prisma.message.findUnique({
|
||||
where: {
|
||||
id: data.messageId,
|
||||
},
|
||||
include: {
|
||||
reactions: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
messageId: data.messageId,
|
||||
reactions: message?.reactions ?? [],
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("React to message error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to react to message",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async markMessagesAsSeen(userId, messageIds) {
|
||||
try {
|
||||
const seenEntries = messageIds.map((messageId) => ({
|
||||
userId,
|
||||
messageId,
|
||||
}));
|
||||
await database_js_1.prisma.messageSeen.createMany({
|
||||
data: seenEntries,
|
||||
skipDuplicates: true,
|
||||
});
|
||||
const messages = await database_js_1.prisma.message.findMany({
|
||||
where: {
|
||||
id: { in: messageIds },
|
||||
},
|
||||
include: {
|
||||
seenBy: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const seenData = messages.map((message) => ({
|
||||
messageId: message.id,
|
||||
seenBy: message.seenBy.map((seen) => seen.userId),
|
||||
}));
|
||||
return {
|
||||
success: true,
|
||||
data: seenData,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Mark messages as seen error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to mark messages as seen",
|
||||
};
|
||||
}
|
||||
}
|
||||
static async deleteMessage(userId, messageId, roomId) {
|
||||
try {
|
||||
const message = await database_js_1.prisma.message.findFirst({
|
||||
where: {
|
||||
id: messageId,
|
||||
userId: userId,
|
||||
roomId: roomId,
|
||||
},
|
||||
});
|
||||
if (!message) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Message not found or you don't have permission to delete it",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
await database_js_1.prisma.message.delete({
|
||||
where: {
|
||||
id: messageId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
messageId,
|
||||
roomId,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Delete message error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to delete message",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.MessageService = MessageService;
|
||||
74
backend/dist/services/userService.js
vendored
Normal file
74
backend/dist/services/userService.js
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserService = void 0;
|
||||
const database_js_1 = require("../config/database.js");
|
||||
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
||||
class UserService {
|
||||
static async updateProfile(userId, data) {
|
||||
try {
|
||||
if (data.username) {
|
||||
const existingUser = await database_js_1.prisma.user.findFirst({
|
||||
where: {
|
||||
username: data.username,
|
||||
id: { not: userId },
|
||||
},
|
||||
});
|
||||
if (existingUser) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Username already taken",
|
||||
};
|
||||
}
|
||||
}
|
||||
if (data.email) {
|
||||
const existingUser = await database_js_1.prisma.user.findFirst({
|
||||
where: {
|
||||
email: data.email,
|
||||
id: { not: userId },
|
||||
},
|
||||
});
|
||||
if (existingUser) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Email already taken",
|
||||
};
|
||||
}
|
||||
}
|
||||
const updateData = {};
|
||||
if (data.username)
|
||||
updateData.username = data.username;
|
||||
if (data.email)
|
||||
updateData.email = data.email;
|
||||
if (data.avatar !== undefined)
|
||||
updateData.avatar = data.avatar;
|
||||
if (data.password) {
|
||||
updateData.password = await bcryptjs_1.default.hash(data.password, 12);
|
||||
}
|
||||
const updatedUser = await database_js_1.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: updateData,
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
avatar: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: updatedUser,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Update profile error:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: "Failed to update profile",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.UserService = UserService;
|
||||
225
backend/dist/socket/socketHandlers.js
vendored
Normal file
225
backend/dist/socket/socketHandlers.js
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.handleConnection = void 0;
|
||||
const database_js_1 = require("../config/database.js");
|
||||
const rateLimiter_js_1 = require("../middleware/rateLimiter.js");
|
||||
const chatService_js_1 = require("../services/chatService.js");
|
||||
const messageService_js_1 = require("../services/messageService.js");
|
||||
const handleConnection = async (io, socket) => {
|
||||
console.log(`User connected: ${socket.user.username} (${socket.user.id})`);
|
||||
await updateUserOnlineStatus(socket.userId, true);
|
||||
const onlineUsers = await database_js_1.prisma.user.findMany({
|
||||
where: { isOnline: true },
|
||||
select: { id: true, username: true },
|
||||
});
|
||||
socket.emit("online_users", onlineUsers);
|
||||
await joinUserRooms(socket);
|
||||
socket.on("send_message", (data) => handleSendMessage(io, socket, data));
|
||||
socket.on("delete_message", (data) => handleDeleteMessage(io, socket, data));
|
||||
socket.on("react_to_message", (data) => handleReactToMessage(io, socket, data));
|
||||
socket.on("typing_start", (data) => handleTypingStart(io, socket, data));
|
||||
socket.on("typing_stop", (data) => handleTypingStop(io, socket, data));
|
||||
socket.on("join_room", (roomId) => handleJoinRoom(socket, roomId));
|
||||
socket.on("leave_room", (roomId) => handleLeaveRoom(socket, roomId));
|
||||
socket.on("messages_seen", (data) => handleMessagesSeen(io, socket, data));
|
||||
socket.on("disconnect", () => handleDisconnect(socket));
|
||||
};
|
||||
exports.handleConnection = handleConnection;
|
||||
const handleTypingStart = async (io, socket, data) => {
|
||||
try {
|
||||
const isMember = await chatService_js_1.ChatService.checkRoomMembership(socket.userId, data.roomId);
|
||||
if (!isMember)
|
||||
return;
|
||||
io.to(data.roomId).emit("user_typing", {
|
||||
roomId: data.roomId,
|
||||
username: data.username,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling typing start: ${error}`);
|
||||
}
|
||||
};
|
||||
const handleTypingStop = async (io, socket, data) => {
|
||||
try {
|
||||
const isMember = await chatService_js_1.ChatService.checkRoomMembership(socket.userId, data.roomId);
|
||||
if (!isMember)
|
||||
return;
|
||||
io.to(data.roomId).emit("user_stopped_typing", {
|
||||
roomId: data.roomId,
|
||||
username: data.username,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling typing stop: ${error}`);
|
||||
}
|
||||
};
|
||||
const updateUserOnlineStatus = async (userId, isOnline) => {
|
||||
try {
|
||||
await database_js_1.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
isOnline,
|
||||
lastSeen: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error updating user online status: ${error}`);
|
||||
}
|
||||
};
|
||||
const joinUserRooms = async (socket) => {
|
||||
try {
|
||||
const userRooms = await database_js_1.prisma.chatRoomMember.findMany({
|
||||
where: { userId: socket.userId },
|
||||
include: { room: true },
|
||||
});
|
||||
userRooms.forEach(async (member) => {
|
||||
await socket.join(member.roomId);
|
||||
socket.to(member.roomId).emit("user_online", {
|
||||
userId: socket.userId,
|
||||
username: socket.user.username,
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error joining user rooms: ${error}`);
|
||||
}
|
||||
};
|
||||
const handleSendMessage = async (io, socket, data) => {
|
||||
try {
|
||||
if (!data.roomId) {
|
||||
socket.emit("error", { message: "Room ID is required to send a message" });
|
||||
return;
|
||||
}
|
||||
if (data.image && data.image.length > 2 * 1024 * 1024) {
|
||||
socket.emit("error", { message: "Image size exceeds 2MB limit" });
|
||||
return;
|
||||
}
|
||||
if (!data.content && !data.image) {
|
||||
socket.emit("error", { message: "Message cannot be empty" });
|
||||
return;
|
||||
}
|
||||
if (!(0, rateLimiter_js_1.checkMessageRateLimit)(socket))
|
||||
return;
|
||||
const isMember = await chatService_js_1.ChatService.checkRoomMembership(socket.userId, data.roomId);
|
||||
if (!isMember) {
|
||||
socket.emit("error", { message: "Not authorized to send messages to this room" });
|
||||
return;
|
||||
}
|
||||
const result = await messageService_js_1.MessageService.sendMessage(socket.userId, data);
|
||||
if (result.success) {
|
||||
io.to(data.roomId).emit("new_message", result.data);
|
||||
io.to(data.roomId).emit("user_stopped_typing", {
|
||||
roomId: data.roomId,
|
||||
username: socket.user.username,
|
||||
});
|
||||
}
|
||||
else {
|
||||
socket.emit("error", { message: "Failed to send message" });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling send message: ${error}`);
|
||||
socket.emit("error", { message: "Failed to send message" });
|
||||
}
|
||||
};
|
||||
const handleDeleteMessage = async (io, socket, data) => {
|
||||
try {
|
||||
const result = await messageService_js_1.MessageService.deleteMessage(socket.userId, data.messageId, data.roomId);
|
||||
if (result.success) {
|
||||
io.to(data.roomId).emit("message_deleted", {
|
||||
messageId: data.messageId,
|
||||
roomId: data.roomId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
socket.emit("error", { message: result.error });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling delete message: ${error}`);
|
||||
socket.emit("error", { message: "Failed to delete message" });
|
||||
}
|
||||
};
|
||||
const handleReactToMessage = async (io, socket, data) => {
|
||||
try {
|
||||
const result = await messageService_js_1.MessageService.reactToMessage(socket.userId, data);
|
||||
if (result.success) {
|
||||
const message = await database_js_1.prisma.message.findUnique({
|
||||
where: { id: data.messageId },
|
||||
select: { roomId: true },
|
||||
});
|
||||
if (message) {
|
||||
io.to(message.roomId).emit("message_reaction_updated", result.data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
socket.emit("error", { message: result.error });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling react to message: ${error}`);
|
||||
socket.emit("error", { message: "Failed to react to message" });
|
||||
}
|
||||
};
|
||||
const handleMessagesSeen = async (io, socket, data) => {
|
||||
try {
|
||||
const isMember = await chatService_js_1.ChatService.checkRoomMembership(socket.userId, data.roomId);
|
||||
if (!isMember) {
|
||||
socket.emit("error", { message: "Not authorized to access this room" });
|
||||
return;
|
||||
}
|
||||
const messagesInRoom = await database_js_1.prisma.message.findMany({
|
||||
where: {
|
||||
id: { in: data.messageIds },
|
||||
roomId: data.roomId,
|
||||
},
|
||||
});
|
||||
const validMessageIds = messagesInRoom.map((msg) => msg.id);
|
||||
if (validMessageIds.length === 0)
|
||||
return;
|
||||
const result = await messageService_js_1.MessageService.markMessagesAsSeen(socket.userId, validMessageIds);
|
||||
if (result.success && result.data) {
|
||||
for (const item of result.data) {
|
||||
io.to(data.roomId).emit("message_seen_update", {
|
||||
messageId: item.messageId,
|
||||
seenBy: item.seenBy,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling messages seen: ${error}`);
|
||||
socket.emit("error", { message: "Failed to mark messages as seen" });
|
||||
}
|
||||
};
|
||||
const handleJoinRoom = async (socket, roomId) => {
|
||||
await socket.join(roomId);
|
||||
};
|
||||
const handleLeaveRoom = async (socket, roomId) => {
|
||||
await socket.leave(roomId);
|
||||
};
|
||||
const handleDisconnect = async (socket) => {
|
||||
console.log(`User disconnected: ${socket.user.username}`);
|
||||
await updateUserOnlineStatus(socket.userId, false);
|
||||
try {
|
||||
const userRooms = await database_js_1.prisma.chatRoomMember.findMany({
|
||||
where: {
|
||||
userId: socket.userId,
|
||||
},
|
||||
});
|
||||
userRooms.forEach((member) => {
|
||||
socket.to(member.roomId).emit("user_offline", {
|
||||
userId: socket.userId,
|
||||
username: socket.user.username,
|
||||
});
|
||||
socket.to(member.roomId).emit("user_stopped_typing", {
|
||||
roomId: member.roomId,
|
||||
username: socket.user.username,
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Error handling user disconnect: ${error}`);
|
||||
}
|
||||
};
|
||||
2
backend/dist/types/index.js
vendored
Normal file
2
backend/dist/types/index.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
Loading…
x
Reference in New Issue
Block a user