CodeHub Branch Setup

This commit is contained in:
Angad Chauhan
2025-06-23 18:55:08 +05:30
commit 2c8e3de2f5
56 changed files with 6850 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
.env
/public

679
index.html Normal file
View File

@@ -0,0 +1,679 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chat Controller Tester</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
.form-section {
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
.form-row {
margin-bottom: 10px;
display: flex;
align-items: center;
}
label {
width: 150px;
font-weight: bold;
}
input,
select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
flex-grow: 1;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
#response {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
white-space: pre-wrap;
}
.participant-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.participant-item input {
flex-grow: 1;
margin-right: 10px;
}
.remove-participant {
color: red;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Chat Controller Tester</h1>
<div class="form-section">
<h2>Create Chat</h2>
<div class="form-row">
<label for="create_user_id">Acting User ID:</label>
<input type="text" id="create_user_id" placeholder="User creating the chat" />
</div>
<div class="form-row">
<label for="is_group">Is Group:</label>
<select id="is_group">
<option value="false">One-to-One Chat</option>
<option value="true">Group Chat</option>
</select>
</div>
<div class="form-row" id="chat-name-row">
<label for="chat_name">Chat Name:</label>
<input type="text" id="chat_name" placeholder="Enter group name (required for groups)" />
</div>
<div class="form-row" id="group-type-row">
<label for="group_type">Group Type:</label>
<select id="group_type">
<option value="public">Public</option>
<option value="private">Private</option>
</select>
</div>
<div class="form-row" id="max-participants-row">
<label for="max_participants">Max Participants:</label>
<input type="number" id="max_participants" placeholder="Leave empty for no limit" />
</div>
<div class="form-row">
<label for="description">Description:</label>
<input type="text" id="description" placeholder="Optional description" />
</div>
<div class="form-row">
<label for="profile_photo">Profile Photo URL:</label>
<input type="text" id="profile_photo" placeholder="Optional image URL" />
</div>
<div class="form-row">
<label>Participants:</label>
<div style="flex-grow: 1;">
<div id="participants-container">
<div class="participant-item">
<input type="text" class="participant-id" placeholder="Enter user ID" />
<span class="remove-participant"></span>
</div>
</div>
<button type="button" id="add-participant">Add Participant</button>
</div>
</div>
<div class="form-row">
<button id="create-chat">Create Chat</button>
<button id="reset-form">Reset Form</button>
</div>
</div>
<div id="response"></div>
<h2>Add Participant to Group Chat</h2>
<div class="form-row">
<label for="add_user_id">Acting User ID:</label>
<input type="text" id="add_user_id" placeholder="User performing the action" />
</div>
<div class="form-row">
<label for="add_chat_id">Chat ID:</label>
<input type="text" id="add_chat_id" placeholder="Target group chat ID" />
</div>
<div class="form-row">
<label for="add_participant_id">New Participant ID:</label>
<input type="text" id="add_participant_id" placeholder="User ID to add" />
</div>
<div class="form-row">
<button id="add-participant-to-group">Add to Group</button>
</div>
<h2>Remove Participant from Group Chat</h2>
<div class="form-row">
<label for="remove_user_id">Acting User ID:</label>
<input type="text" id="remove_user_id" placeholder="User performing the action (admin)" />
</div>
<div class="form-row">
<label for="remove_chat_id">Chat ID:</label>
<input type="text" id="remove_chat_id" placeholder="Target group chat ID" />
</div>
<div class="form-row">
<label for="remove_participant_id">Participant ID:</label>
<input type="text" id="remove_participant_id" placeholder="User ID to remove" />
</div>
<div class="form-row">
<button id="remove-participant-from-group">Remove from Group</button>
</div>
<h2>Get Chat Participants</h2>
<div class="form-row">
<label for="get_chat_id">Chat ID:</label>
<input type="text" id="get_chat_id" placeholder="Enter chat ID to fetch participants" />
</div>
<div class="form-row">
<button id="get-chat-participants">Get Participants</button>
</div>
<h2>Accept/Reject Group Join Request (Private Group)</h2>
<div class="form-section">
<div class="form-row">
<label for="admin_user_id">Admin User ID:</label>
<input type="text" id="admin_user_id" placeholder="Admin user ID" />
</div>
<div class="form-row">
<label for="group_chat_id">Group Chat ID:</label>
<input type="text" id="group_chat_id" placeholder="Private group chat ID" />
</div>
<div class="form-row">
<label for="pending_participant_id">Participant ID:</label>
<input type="text" id="pending_participant_id" placeholder="User ID to accept/reject" />
</div>
<div class="form-row">
<button id="accept-join-request">Accept Join Request</button>
<button id="reject-join-request" style="background-color:#e74c3c;">Reject Join Request</button>
</div>
</div>
<h2>Get All Group Join Requests</h2>
<div class="form-section">
<div class="form-row">
<label for="request_user_id">User ID:</label>
<input type="text" id="request_user_id" placeholder="User ID to fetch group join requests" />
</div>
<div class="form-row">
<button id="get-all-group-requests">Get All Group Join Requests</button>
</div>
</div>
<h2>Get All Join Requests For Particular Group</h2>
<div class="form-section">
<div class="form-row">
<label for="group_requests_chat_id">Group Chat ID:</label>
<input type="text" id="group_requests_chat_id" placeholder="Group Chat ID" />
</div>
<div class="form-row">
<button id="get-group-join-requests">Get Join Requests</button>
</div>
</div>
<div class="form-section">
<h2>Live Chat Demo</h2>
<input type="text" id="chat-message" placeholder="Type your message..." style="width:80%;" />
<button id="send-message">Send</button>
</div>
<div class="form-section">
<h2>Test sendMessage API one to one </h2>
<div class="form-row">
<label for="send_user_id">User ID:</label>
<input type="text" id="send_user_id" placeholder="Sender user ID" />
</div>
<div class="form-row">
<label for="send_chat_id">Chat ID:</label>
<input type="text" id="send_chat_id" placeholder="Chat ID" />
</div>
<div class="form-row">
<label for="send_receiver_id">Receiver ID:</label>
<input type="text" id="send_receiver_id" placeholder="Receiver ID (optional for group)" />
</div>
<div class="form-row">
<label for="send_message_text">Message:</label>
<input type="text" id="send_message_text" placeholder="Type your message..." />
</div>
<div class="form-row">
<button id="send-message-api">Send via API</button>
</div>
</div>
<div class="form-row">
<label for="join_chat_id">Join Chat ID:</label>
<input type="text" id="join_chat_id" placeholder="Enter chat ID to join room" />
<button id="join-room-btn">Join Room</button>
</div>
<div id="chat-window"
style="height:200px;overflow-y:auto;border:1px solid #ccc;padding:10px;margin-bottom:10px;background:#fafafa">
</div>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
document.getElementById("join-room-btn").addEventListener("click", function () {
const chatId = document.getElementById("join_chat_id").value.trim();
if (chatId) {
socket.emit("join-room", { chatId });
alert("Joined room " + chatId);
}
});
// Toggle group-specific fields
const isGroupSelect = document.getElementById("is_group");
const toggleGroupFields = () => {
const isGroup = isGroupSelect.value === "true";
document.getElementById("chat-name-row").style.display = isGroup ? "flex" : "none";
document.getElementById("group-type-row").style.display = isGroup ? "flex" : "none";
document.getElementById("max-participants-row").style.display = isGroup ? "flex" : "none";
};
isGroupSelect.addEventListener("change", toggleGroupFields);
toggleGroupFields(); // Initialize
// Participant management
document.getElementById("add-participant").addEventListener("click", () => {
const container = document.getElementById("participants-container");
const newItem = document.createElement("div");
newItem.className = "participant-item";
newItem.innerHTML = `
<input type="text" class="participant-id" placeholder="Enter user ID">
<span class="remove-participant">✕</span>
`;
container.appendChild(newItem);
newItem.querySelector(".remove-participant").addEventListener("click", () => {
if (document.querySelectorAll(".participant-item").length > 1) {
container.removeChild(newItem);
}
});
});
document.getElementById("reset-form").addEventListener("click", () => {
document.querySelectorAll("input").forEach((input) => (input.value = ""));
document.getElementById("is_group").value = "false";
document.getElementById("group_type").value = "public";
const participantsContainer = document.getElementById("participants-container");
participantsContainer.innerHTML = `
<div class="participant-item">
<input type="text" class="participant-id" placeholder="Enter user ID">
<span class="remove-participant">✕</span>
</div>
`;
toggleGroupFields();
});
document.getElementById("create-chat").addEventListener("click", async () => {
const userId = Number(document.getElementById("create_user_id").value.trim());
const isGroup = document.getElementById("is_group").value === "true";
const chatName = document.getElementById("chat_name").value;
const groupType = document.getElementById("group_type").value;
const maxParticipants = document.getElementById("max_participants").value;
const description = document.getElementById("description").value;
const profilePhoto = document.getElementById("profile_photo").value;
const participantInputs = document.querySelectorAll(".participant-id");
const participants = Array.from(participantInputs)
.map((input) => input.value.trim())
.filter((id) => id !== "")
.map(Number);
if (!userId) {
alert("Acting User ID is required");
return;
}
if (isGroup && !chatName) {
alert("Group chat requires a name");
return;
}
if (!isGroup && participants.length !== 1) {
alert("One-to-one chat requires exactly one participant");
return;
}
const requestBody = {
userId,
chat_name: isGroup ? chatName : null,
is_group: isGroup,
group_type: isGroup ? groupType : null,
max_participants: isGroup ? (maxParticipants || null) : null,
description: description || null,
profile_photo: profilePhoto || null,
participants,
};
try {
const response = await fetch("http://localhost:8000/api/chat/user/createNewChat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody),
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
document.getElementById("add-participant-to-group").addEventListener("click", async () => {
const userId = document.getElementById("add_user_id").value.trim();
const chatId = document.getElementById("add_chat_id").value.trim();
const newParticipantId = document.getElementById("add_participant_id").value.trim();
if (!userId || !chatId || !newParticipantId) {
alert("Please fill all fields (User ID, Chat ID, New Participant ID)");
return;
}
const requestBody = {
chatId,
newParticipantId,
};
try {
const response = await fetch(`http://localhost:8000/api/chat/user/addParticipant/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody),
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
document.getElementById("remove-participant-from-group").addEventListener("click", async () => {
const userId = Number(document.getElementById("remove_user_id").value.trim());
const chatId = Number(document.getElementById("remove_chat_id").value.trim());
const participantId = Number(document.getElementById("remove_participant_id").value.trim());
if (!userId || !chatId || !participantId) {
alert("Please fill all fields (User ID, Chat ID, Participant ID)");
return;
}
const requestBody = {
chatId,
participantId,
};
try {
const response = await fetch(`http://localhost:8000/api/chat/user/removeParticipant/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody),
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
document.getElementById("get-chat-participants").addEventListener("click", async () => {
const chatId = document.getElementById("get_chat_id").value.trim();
if (!chatId) {
alert("Please enter a Chat ID");
return;
}
try {
const response = await fetch(`http://localhost:8000/api/chat/user/getChatParticipants/${chatId}`, {
method: "GET"
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) {
console.error("Error:", result);
}
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
// Accept Join Request
document.getElementById("accept-join-request").addEventListener("click", async () => {
const userId = document.getElementById("admin_user_id").value.trim();
const chatId = document.getElementById("group_chat_id").value.trim();
const participantId = document.getElementById("pending_participant_id").value.trim();
if (!userId || !chatId || !participantId) {
alert("Please fill all fields (Admin User ID, Group Chat ID, Participant ID)");
return;
}
const requestBody = {
chatId,
participantId,
};
try {
const response = await fetch(`http://localhost:8000/api/chat/admin/acceptGroupJoinRequest/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody),
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
// Reject Join Request
document.getElementById("reject-join-request").addEventListener("click", async () => {
const userId = document.getElementById("admin_user_id").value.trim();
const chatId = document.getElementById("group_chat_id").value.trim();
const participantId = document.getElementById("pending_participant_id").value.trim();
if (!userId || !chatId || !participantId) {
alert("Please fill all fields (Admin User ID, Group Chat ID, Participant ID)");
return;
}
const requestBody = {
chatId,
participantId,
};
try {
const response = await fetch(`http://localhost:8000/api/chat/admin/rejectGroupJoinRequest/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody),
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
// Get All Group Join Requests (no token)
document.getElementById("get-all-group-requests").addEventListener("click", async () => {
const userId = document.getElementById("request_user_id").value.trim();
if (!userId) {
alert("Please enter a User ID");
return;
}
try {
const response = await fetch(`http://localhost:8000/api/chat/user/get-all-chat-group-request/${userId}`, {
method: "GET"
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) {
console.error("Error:", result);
}
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
document.getElementById("get-group-join-requests").addEventListener("click", async () => {
const chatId = document.getElementById("group_requests_chat_id").value.trim();
if (!chatId) {
alert("Please enter a Group Chat ID");
return;
}
try {
const response = await fetch(`http://localhost:8000/api/chat/admin/group/${chatId}`, {
method: "GET"
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) {
console.error("Error:", result);
}
} catch (error) {
console.error("Error:", error);
document.getElementById("response").textContent = "Error: " + error.message;
}
});
// ...existing code...
// Live Chat Demo with Socket.IO
const socket = io("http://localhost:3000");
// Listen for incoming messages
socket.on("chat-message", function (data) {
const chatWindow = document.getElementById("chat-window");
const div = document.createElement("div");
if (typeof data === "object" && data.message) {
div.textContent =
(data.sender_xid ? "User " + data.sender_xid + ": " : "") +
data.message +
(data.receiver_xid ? " (to User " + data.receiver_xid + ")" : "");
} else {
div.textContent = typeof data === "string" ? data : JSON.stringify(data);
}
chatWindow.appendChild(div);
chatWindow.scrollTop = chatWindow.scrollHeight;
});
// Send message
document.getElementById("send-message").addEventListener("click", function () {
const input = document.getElementById("chat-message");
const msg = input.value.trim();
if (msg) {
socket.emit("chat-message", msg);
input.value = "";
}
});
// Optionally, send message on Enter key
document.getElementById("chat-message").addEventListener("keydown", function (e) {
if (e.key === "Enter") {
document.getElementById("send-message").click();
}
});
// ANGAD
document.getElementById("send-message-api").addEventListener("click", async function () {
const userId = document.getElementById("send_user_id").value.trim();
const chatId = document.getElementById("send_chat_id").value.trim();
const receiverId = document.getElementById("send_receiver_id").value.trim();
const message = document.getElementById("send_message_text").value.trim();
if (!userId || !chatId || !message) {
alert("User ID, Chat ID, and Message are required!");
return;
}
const body = {
userId: Number(userId),
chatId: Number(chatId),
message: message
};
if (receiverId) body.receiverId = Number(receiverId);
try {
const response = await fetch("http://localhost:8000/api/chat/user/send-message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
const result = await response.json();
document.getElementById("response").textContent = JSON.stringify(result, null, 2);
if (!response.ok) console.error("Error:", result);
} catch (error) {
document.getElementById("response").textContent = "Error: " + error.message;
}
});
// ...existing code...
</script>
<!-- ...existing code...
</script>
</body>
</html>

206
logs/combined.log Normal file
View File

@@ -0,0 +1,206 @@
2025-06-23 12:24:06 [info]: Connection has been established successfully.
2025-06-23 13:33:24 [info]: Connection has been established successfully.
2025-06-23 13:34:16 [info]: Connection has been established successfully.
2025-06-23 13:34:23 [info]: Connection has been established successfully.
2025-06-23 13:34:44 [info]: Connection has been established successfully.
2025-06-23 13:35:54 [info]: Connection has been established successfully.
2025-06-23 13:36:24 [info]: Connection has been established successfully.
2025-06-23 13:38:02 [info]: Connection has been established successfully.
2025-06-23 13:38:03 [info]: Connection has been established successfully.
2025-06-23 13:38:05 [info]: Connection has been established successfully.
2025-06-23 15:17:53 [info]: Connection has been established successfully.
2025-06-23 15:30:30 [info]: Connection has been established successfully.
2025-06-23 15:30:53 [info]: Connection has been established successfully.
2025-06-23 15:31:58 [info]: Connection has been established successfully.
2025-06-23 15:34:35 [info]: Connection has been established successfully.
2025-06-23 15:35:54 [info]: Connection has been established successfully.
2025-06-23 15:36:02 [info]: Connection has been established successfully.
2025-06-23 15:36:05 [info]: Connection has been established successfully.
2025-06-23 15:36:08 [info]: Connection has been established successfully.
2025-06-23 15:37:12 [info]: Connection has been established successfully.
2025-06-23 15:37:15 [info]: Connection has been established successfully.
2025-06-23 15:39:12 [info]: Connection has been established successfully.
2025-06-23 15:39:16 [info]: Connection has been established successfully.
2025-06-23 15:39:33 [info]: Connection has been established successfully.
2025-06-23 15:39:40 [info]: Connection has been established successfully.
2025-06-23 15:39:43 [info]: Connection has been established successfully.
2025-06-23 15:58:39 [info]: Connection has been established successfully.
2025-06-23 15:58:50 [info]: Connection has been established successfully.
2025-06-23 16:05:36 [info]: Connection has been established successfully.
2025-06-23 16:05:43 [info]: Connection has been established successfully.
2025-06-23 16:06:51 [info]: Connection has been established successfully.
2025-06-23 16:06:55 [info]: Connection has been established successfully.
2025-06-23 16:07:00 [info]: Connection has been established successfully.
2025-06-23 16:07:02 [info]: Connection has been established successfully.
2025-06-23 16:07:07 [info]: Connection has been established successfully.
2025-06-23 16:07:12 [info]: Connection has been established successfully.
2025-06-23 16:08:01 [error]: 2025-06-23T10:38:01.003Z ::1 - POST /api/chat/user/send-message 400 - 5.805 ms - error: - - referrer: -
2025-06-23 16:10:45 [error]: 2025-06-23T10:40:45.038Z ::1 - POST /api/chat/user/send-message 400 - 2.629 ms - error: - - referrer: -
2025-06-23 16:11:24 [error]: 2025-06-23T10:41:24.256Z ::1 - POST /api/chat/user/send-message 400 - 0.777 ms - error: - - referrer: -
2025-06-23 16:11:59 [info]: Connection has been established successfully.
2025-06-23 16:12:02 [info]: Connection has been established successfully.
2025-06-23 16:12:09 [info]: Connection has been established successfully.
2025-06-23 16:12:12 [info]: Connection has been established successfully.
2025-06-23 16:12:16 [error]: 2025-06-23T10:42:16.624Z ::1 - POST /api/chat/user/send-message 400 - 7.857 ms - error: - - referrer: -
2025-06-23 16:12:23 [info]: Connection has been established successfully.
2025-06-23 16:12:32 [error]: 2025-06-23T10:42:32.092Z ::1 - POST /api/chat/user/send-message 400 - 6.881 ms - error: - - referrer: -
2025-06-23 16:13:35 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:469:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:13:35 [error]: 2025-06-23T10:43:35.800Z ::1 - POST /api/chat/user/send-message 500 - 178.663 ms - error: - - referrer: -
2025-06-23 16:15:26 [info]: Connection has been established successfully.
2025-06-23 16:15:40 [error]: ApiError is not defined
ReferenceError: ApiError is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:446:13
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:15:40 [error]: 2025-06-23T10:45:40.400Z ::1 - POST /api/chat/user/send-message 500 - 36.646 ms - error: - - referrer: -
2025-06-23 16:15:49 [error]: ApiError is not defined
ReferenceError: ApiError is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:446:13
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:15:49 [error]: 2025-06-23T10:45:49.225Z ::1 - POST /api/chat/user/send-message 500 - 3.966 ms - error: - - referrer: -
2025-06-23 16:16:53 [info]: Connection has been established successfully.
2025-06-23 16:17:02 [info]: Connection has been established successfully.
2025-06-23 16:17:07 [error]: userId, chatId, and message are required
Error: userId, chatId, and message are required
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:447:19
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:17:07 [error]: 2025-06-23T10:47:07.287Z ::1 - POST /api/chat/user/send-message 400 - 28.057 ms - error: - - referrer: -
2025-06-23 16:17:40 [info]: Connection has been established successfully.
2025-06-23 16:17:43 [info]: Connection has been established successfully.
2025-06-23 16:17:50 [info]: Connection has been established successfully.
2025-06-23 16:17:57 [info]: Connection has been established successfully.
2025-06-23 16:18:05 [info]: Connection has been established successfully.
2025-06-23 16:18:12 [info]: Connection has been established successfully.
2025-06-23 16:18:18 [info]: Connection has been established successfully.
2025-06-23 16:18:30 [info]: Connection has been established successfully.
2025-06-23 16:18:34 [info]: Connection has been established successfully.
2025-06-23 16:18:42 [info]: Connection has been established successfully.
2025-06-23 16:18:48 [info]: Connection has been established successfully.
2025-06-23 16:18:52 [info]: Connection has been established successfully.
2025-06-23 16:19:01 [info]: Connection has been established successfully.
2025-06-23 16:19:05 [info]: Connection has been established successfully.
2025-06-23 16:19:13 [info]: Connection has been established successfully.
2025-06-23 16:19:19 [info]: Connection has been established successfully.
2025-06-23 16:19:26 [info]: Connection has been established successfully.
2025-06-23 16:19:30 [info]: Connection has been established successfully.
2025-06-23 16:19:34 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:19:34 [error]: 2025-06-23T10:49:34.487Z ::1 - POST /api/chat/user/send-message 500 - 64.727 ms - error: - - referrer: -
2025-06-23 16:19:49 [info]: Connection has been established successfully.
2025-06-23 16:19:53 [info]: Connection has been established successfully.
2025-06-23 16:19:56 [info]: Connection has been established successfully.
2025-06-23 16:20:00 [info]: Connection has been established successfully.
2025-06-23 16:20:03 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:472:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:03 [error]: 2025-06-23T10:50:03.970Z ::1 - POST /api/chat/user/send-message 500 - 46.643 ms - error: - - referrer: -
2025-06-23 16:20:14 [info]: Connection has been established successfully.
2025-06-23 16:20:26 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:26 [error]: 2025-06-23T10:50:26.686Z ::1 - POST /api/chat/user/send-message 500 - 128.488 ms - error: - - referrer: -
2025-06-23 16:20:43 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:43 [error]: 2025-06-23T10:50:43.447Z ::1 - POST /api/chat/user/send-message 500 - 194.941 ms - error: - - referrer: -
2025-06-23 16:20:51 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:51 [error]: 2025-06-23T10:50:51.789Z ::1 - POST /api/chat/user/send-message 500 - 21.653 ms - error: - - referrer: -
2025-06-23 16:21:09 [info]: Connection has been established successfully.
2025-06-23 16:21:19 [info]: Connection has been established successfully.
2025-06-23 16:21:23 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:21:23 [error]: 2025-06-23T10:51:23.705Z ::1 - POST /api/chat/user/send-message 500 - 51.833 ms - error: - - referrer: -
2025-06-23 16:23:06 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:23:06 [error]: 2025-06-23T10:53:06.631Z ::1 - POST /api/chat/user/send-message 500 - 98.187 ms - error: - - referrer: -
2025-06-23 16:24:22 [info]: Connection has been established successfully.
2025-06-23 16:24:29 [info]: Connection has been established successfully.
2025-06-23 16:24:32 [info]: Connection has been established successfully.
2025-06-23 16:24:42 [info]: 2025-06-23T10:54:42.073Z ::1 - POST /api/chat/user/send-message 200 - 134.873 ms
2025-06-23 16:27:55 [info]: Connection has been established successfully.
2025-06-23 16:28:07 [info]: Connection has been established successfully.
2025-06-23 16:28:35 [info]: Connection has been established successfully.
2025-06-23 16:28:37 [info]: Connection has been established successfully.
2025-06-23 16:28:44 [info]: Connection has been established successfully.
2025-06-23 16:28:48 [info]: Connection has been established successfully.
2025-06-23 16:28:52 [info]: Connection has been established successfully.
2025-06-23 16:28:58 [info]: 2025-06-23T10:58:58.319Z ::1 - POST /api/chat/user/send-message 200 - 91.881 ms
2025-06-23 16:29:25 [info]: Connection has been established successfully.
2025-06-23 16:29:33 [info]: Connection has been established successfully.
2025-06-23 16:35:28 [info]: 2025-06-23T11:05:28.290Z ::1 - OPTIONS /api/chat/user/send-message 204 - 1.182 ms
2025-06-23 16:35:28 [info]: 2025-06-23T11:05:28.420Z ::1 - POST /api/chat/user/send-message 200 - 126.046 ms
2025-06-23 16:45:14 [info]: 2025-06-23T11:15:14.461Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.329 ms
2025-06-23 16:45:14 [info]: 2025-06-23T11:15:14.543Z ::1 - POST /api/chat/user/send-message 200 - 79.716 ms
2025-06-23 16:48:37 [info]: 2025-06-23T11:18:37.803Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.166 ms
2025-06-23 16:48:37 [info]: 2025-06-23T11:18:37.887Z ::1 - POST /api/chat/user/send-message 200 - 82.069 ms
2025-06-23 16:48:50 [info]: 2025-06-23T11:18:50.251Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.149 ms
2025-06-23 16:48:50 [info]: 2025-06-23T11:18:50.333Z ::1 - POST /api/chat/user/send-message 200 - 80.274 ms
2025-06-23 16:49:21 [info]: 2025-06-23T11:19:21.907Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.150 ms
2025-06-23 16:49:21 [info]: 2025-06-23T11:19:21.990Z ::1 - POST /api/chat/user/send-message 200 - 80.565 ms
2025-06-23 16:51:24 [info]: 2025-06-23T11:21:24.129Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.164 ms
2025-06-23 16:51:24 [info]: 2025-06-23T11:21:24.225Z ::1 - POST /api/chat/user/send-message 200 - 93.860 ms
2025-06-23 16:51:38 [info]: 2025-06-23T11:21:38.023Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.244 ms
2025-06-23 16:51:38 [info]: 2025-06-23T11:21:38.099Z ::1 - POST /api/chat/user/send-message 200 - 72.892 ms
2025-06-23 16:57:13 [info]: 2025-06-23T11:27:13.044Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.203 ms
2025-06-23 16:57:13 [info]: 2025-06-23T11:27:13.116Z ::1 - POST /api/chat/user/send-message 200 - 69.735 ms
2025-06-23 17:03:53 [info]: Connection has been established successfully.
2025-06-23 17:03:56 [info]: Connection has been established successfully.
2025-06-23 17:04:45 [info]: 2025-06-23T11:34:45.292Z ::ffff:127.0.0.1 - OPTIONS /api/chat/user/send-message 204 - 1.125 ms
2025-06-23 17:04:45 [info]: 2025-06-23T11:34:45.489Z ::ffff:127.0.0.1 - POST /api/chat/user/send-message 200 - 192.779 ms
2025-06-23 17:05:14 [info]: 2025-06-23T11:35:14.824Z ::1 - OPTIONS /api/chat/user/send-message 204 - 0.163 ms
2025-06-23 17:05:14 [info]: 2025-06-23T11:35:14.941Z ::1 - POST /api/chat/user/send-message 200 - 114.128 ms
2025-06-23 17:05:31 [info]: 2025-06-23T11:35:31.334Z ::1 - OPTIONS /api/chat/user/createNewChat 204 - 0.159 ms
2025-06-23 17:05:31 [info]: 2025-06-23T11:35:31.426Z ::1 - POST /api/chat/user/createNewChat 200 - 79.259 ms
2025-06-23 17:08:05 [info]: Connection has been established successfully.
2025-06-23 17:08:22 [info]: Connection has been established successfully.
2025-06-23 17:15:13 [info]: 2025-06-23T11:45:13.647Z ::1 - OPTIONS /api/chat/user/send-message 204 - 1.051 ms
2025-06-23 17:15:13 [info]: 2025-06-23T11:45:13.779Z ::1 - POST /api/chat/user/send-message 200 - 128.302 ms
2025-06-23 17:21:03 [info]: Connection has been established successfully.
2025-06-23 17:21:11 [info]: Connection has been established successfully.
2025-06-23 17:21:14 [info]: Connection has been established successfully.
2025-06-23 17:21:20 [info]: Connection has been established successfully.
2025-06-23 17:21:28 [info]: 2025-06-23T11:51:28.579Z ::1 - POST /api/chat/user/send-message 200 - 75.033 ms
2025-06-23 17:24:14 [info]: Connection has been established successfully.
2025-06-23 17:24:25 [info]: Connection has been established successfully.
2025-06-23 17:24:30 [info]: Connection has been established successfully.
2025-06-23 17:24:55 [info]: 2025-06-23T11:54:55.734Z ::1 - OPTIONS /api/chat/user/send-message 204 - 1.127 ms
2025-06-23 17:24:55 [info]: 2025-06-23T11:54:55.899Z ::1 - POST /api/chat/user/send-message 200 - 160.824 ms
2025-06-23 17:25:36 [info]: Connection has been established successfully.

84
logs/error.log Normal file
View File

@@ -0,0 +1,84 @@
2025-06-23 16:08:01 [error]: 2025-06-23T10:38:01.003Z ::1 - POST /api/chat/user/send-message 400 - 5.805 ms - error: - - referrer: -
2025-06-23 16:10:45 [error]: 2025-06-23T10:40:45.038Z ::1 - POST /api/chat/user/send-message 400 - 2.629 ms - error: - - referrer: -
2025-06-23 16:11:24 [error]: 2025-06-23T10:41:24.256Z ::1 - POST /api/chat/user/send-message 400 - 0.777 ms - error: - - referrer: -
2025-06-23 16:12:16 [error]: 2025-06-23T10:42:16.624Z ::1 - POST /api/chat/user/send-message 400 - 7.857 ms - error: - - referrer: -
2025-06-23 16:12:32 [error]: 2025-06-23T10:42:32.092Z ::1 - POST /api/chat/user/send-message 400 - 6.881 ms - error: - - referrer: -
2025-06-23 16:13:35 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:469:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:13:35 [error]: 2025-06-23T10:43:35.800Z ::1 - POST /api/chat/user/send-message 500 - 178.663 ms - error: - - referrer: -
2025-06-23 16:15:40 [error]: ApiError is not defined
ReferenceError: ApiError is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:446:13
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:15:40 [error]: 2025-06-23T10:45:40.400Z ::1 - POST /api/chat/user/send-message 500 - 36.646 ms - error: - - referrer: -
2025-06-23 16:15:49 [error]: ApiError is not defined
ReferenceError: ApiError is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:446:13
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:15:49 [error]: 2025-06-23T10:45:49.225Z ::1 - POST /api/chat/user/send-message 500 - 3.966 ms - error: - - referrer: -
2025-06-23 16:17:07 [error]: userId, chatId, and message are required
Error: userId, chatId, and message are required
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:447:19
at C:\Wdi Projects\SSA chat\src\utils\handler\Async.handler.js:10:18
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:149:13)
at Route.dispatch (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\route.js:119:3)
at Layer.handle [as handle_request] (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\layer.js:95:5)
at C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:284:15
at Function.process_params (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:346:12)
at next (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:280:10)
at Function.handle (C:\Wdi Projects\SSA chat\node_modules\express\lib\router\index.js:175:3)
2025-06-23 16:17:07 [error]: 2025-06-23T10:47:07.287Z ::1 - POST /api/chat/user/send-message 400 - 28.057 ms - error: - - referrer: -
2025-06-23 16:19:34 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:19:34 [error]: 2025-06-23T10:49:34.487Z ::1 - POST /api/chat/user/send-message 500 - 64.727 ms - error: - - referrer: -
2025-06-23 16:20:03 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:472:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:03 [error]: 2025-06-23T10:50:03.970Z ::1 - POST /api/chat/user/send-message 500 - 46.643 ms - error: - - referrer: -
2025-06-23 16:20:26 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:26 [error]: 2025-06-23T10:50:26.686Z ::1 - POST /api/chat/user/send-message 500 - 128.488 ms - error: - - referrer: -
2025-06-23 16:20:43 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:43 [error]: 2025-06-23T10:50:43.447Z ::1 - POST /api/chat/user/send-message 500 - 194.941 ms - error: - - referrer: -
2025-06-23 16:20:51 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:20:51 [error]: 2025-06-23T10:50:51.789Z ::1 - POST /api/chat/user/send-message 500 - 21.653 ms - error: - - referrer: -
2025-06-23 16:21:23 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:21:23 [error]: 2025-06-23T10:51:23.705Z ::1 - POST /api/chat/user/send-message 500 - 51.833 ms - error: - - referrer: -
2025-06-23 16:23:06 [error]: receiverId is not defined
ReferenceError: receiverId is not defined
at C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:471:27
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
2025-06-23 16:23:06 [error]: 2025-06-23T10:53:06.631Z ::1 - POST /api/chat/user/send-message 500 - 98.187 ms - error: - - referrer: -

262
logs/exceptions.log Normal file
View File

@@ -0,0 +1,262 @@
2025-06-23 13:33:50 [error]: uncaughtException: get is not defined
ReferenceError: get is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: get is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 13:33:54 [error]: uncaughtException: getMediaFi is not defined
ReferenceError: getMediaFi is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: getMediaFi is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 13:33:57 [error]: uncaughtException: getMediaFiles is not defined
ReferenceError: getMediaFiles is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: getMediaFiles is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 13:33:58 [error]: uncaughtException: getMediaFilesBy is not defined
ReferenceError: getMediaFilesBy is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: getMediaFilesBy is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 13:34:02 [error]: uncaughtException: getMediaFilesById is not defined
ReferenceError: getMediaFilesById is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: getMediaFilesById is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:420:5)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 16:07:09 [error]: uncaughtException: Unexpected token ')'
C:\Wdi Projects\SSA chat\src\routes\chat\user.routes.js:48
router.post('/send-message', chatController.);
^
SyntaxError: Unexpected token ')'
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\routes\chat\index.js:3:25)
C:\Wdi Projects\SSA chat\src\routes\chat\user.routes.js:48
router.post('/send-message', chatController.);
^
SyntaxError: Unexpected token ')'
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\routes\chat\index.js:3:25)
2025-06-23 16:15:15 [error]: uncaughtException: Invalid or unexpected token
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:445
return res.status(400).json(new ApiResponse(400, null, "userId, chatId, and message are required"));\
^
SyntaxError: Invalid or unexpected token
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:445
return res.status(400).json(new ApiResponse(400, null, "userId, chatId, and message are required"));\
^
SyntaxError: Invalid or unexpected token
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 16:16:41 [error]: uncaughtException: co is not defined
ReferenceError: co is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:4:1)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
ReferenceError: co is not defined
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:4:1)
at Module._compile (node:internal/modules/cjs/loader:1562:14)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 16:16:44 [error]: uncaughtException: Identifier 'module' has already been declared
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:5
module.exports = {
^
SyntaxError: Identifier 'module' has already been declared
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:5
module.exports = {
^
SyntaxError: Identifier 'module' has already been declared
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
2025-06-23 17:21:17 [error]: uncaughtException: Unexpected identifier 'io'
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:476
const io = req.app.get('io');
^^
SyntaxError: Unexpected identifier 'io'
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)
C:\Wdi Projects\SSA chat\src\controller\chat.controller.js:476
const io = req.app.get('io');
^^
SyntaxError: Unexpected identifier 'io'
at wrapSafe (node:internal/modules/cjs/loader:1512:18)
at Module._compile (node:internal/modules/cjs/loader:1534:20)
at Object..js (node:internal/modules/cjs/loader:1699:10)
at Module.load (node:internal/modules/cjs/loader:1313:32)
at Function._load (node:internal/modules/cjs/loader:1123:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1335:12)
at require (node:internal/modules/helpers:136:16)
at Object.<anonymous> (C:\Wdi Projects\SSA chat\src\controller\index.js:2:21)

2464
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "ssa-chat",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.8.4",
"compression": "^1.8.0",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"http-status": "^2.1.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.2",
"mysql2": "^3.14.0",
"nodemon": "^3.1.9",
"pg": "^8.16.0",
"request-ip": "^3.3.0",
"sequelize": "^6.37.7",
"socket.io": "^4.8.1",
"winston": "^3.17.0",
"yup": "^1.6.1"
}
}

121
src/config/config.js Normal file
View File

@@ -0,0 +1,121 @@
const dotenv = require('dotenv');
const path = require('path');
const yup = require('yup');
const crypto = require('crypto');
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = yup
.object()
.shape({
NODE_ENV: yup
.string()
.oneOf(['production', 'development', 'test'])
.required(),
PORT: yup.number().default(3000),
API_VERSION: yup.string().default('v1').notRequired('Api Version'),
// DataBase
DB_USERNAME: yup.string().required('DB Username is required'),
DB_PASSWORD: yup.string().required('DB Password is required'),
DB_DATABASE_NAME: yup.string().required('Database name is required'),
DB_HOSTNAME: yup
.string()
.default('127.0.0.1')
.required('DB Hostname is required'),
DB_PORT: yup.number().default(3306).required('DB Port is required'),
})
try {
const envVars = envVarsSchema.validateSync(process.env, {
abortEarly: false,
stripUnknown: true,
});
module.exports = {
env: envVars?.NODE_ENV,
port: envVars?.PORT,
apiVersion: envVars.API_VERSION,
mysql: {
development: {
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
dialect: 'postgres',
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
},
pool: {
max: 10, // Maximum number of connection in pool
min: 0, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
},
},
test: {
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
dialect: 'mysql',
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
},
pool: {
max: 10, // Maximum number of connection in pool
min: 0, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
},
},
production: {
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
dialect: 'mysql',
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
socketPath: '/var/run/mysqld/mysqld.sock',
},
pool: {
max: 20, // Maximum number of connection in pool
min: 5, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
},
},
},
jwt: {
secret: envVars?.JWT_SECRET,
accessExpirationMinutes: envVars?.JWT_ACCESS_EXPIRATION_MINUTES,
refreshExpirationDays: envVars?.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes:
envVars?.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes:
envVars?.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
},
expiryTime: {
otp_in_Min: envVars.OTP_EXPIRE_IN_MIN || 2,
},
};
} catch (error) {
throw new Error(
`Config validation error: ${Array.isArray(error?.errors) ? error.errors?.join(', ') : error}`
);
}

22
src/config/cors.js Normal file
View File

@@ -0,0 +1,22 @@
const config = require('./config');
// CORS Configuration
const corsOptions = (req, callback) => {
const allowedOrigins =
config.env !== 'development'
? [`http://${req.hostname}`, 'http://localhost:5173']
: ['http://localhost:5173']; // Replace with your frontend URL
const corsConfig = {
origin: allowedOrigins.includes(req.header('Origin'))
? req.header('Origin')
: false,
credentials: true, // Allow cookies and credentials
};
if (config.env === 'production') {
corsOptions.sameSite = 'Strict'; // Prevent cross-site request forgery
}
callback(null, corsConfig);
};
module.exports = corsOptions;

51
src/config/logger.js Normal file
View File

@@ -0,0 +1,51 @@
// src/config/logger.js
const winston = require('winston');
const path = require('path');
const { format, transports } = winston;
const { combine, timestamp, printf, colorize, errors } = format;
// Create log format
const logFormat = printf(({ level, message, timestamp, stack }) => {
return `${timestamp} [${level}]: ${message}${stack ? `\n${stack}` : ''}`;
});
// Create logger instance
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
errors({ stack: true }),
logFormat
),
transports: [
// Console transport with colors
new transports.Console({
format: combine(
colorize(),
printf(info => `${info.timestamp} [${info.level}]: ${info.message}`)
),
}),
// File transports
new transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
new transports.File({
filename: 'logs/combined.log',
maxsize: 5242880,
maxFiles: 5
})
],
exceptionHandlers: [
new transports.File({ filename: 'logs/exceptions.log' })
]
});
// Handle uncaught promise rejections
process.on('unhandledRejection', (reason) => {
throw reason;
});
module.exports = logger;

44
src/config/morgan.js Normal file
View File

@@ -0,0 +1,44 @@
const morgan = require('morgan');
const requestIp = require('request-ip');
const config = require('./config');
const logger = require('./logger');
// Custom tokens
morgan.token('clientIp', (req) => {
const ip = requestIp.getClientIp(req);
// Anonymize IP in production for GDPR compliance
return config.env === 'production' && ip
? ip.replace(/\.\d+$/, '.XXX')
: ip || 'unknown';
});
morgan.token('error', (req, res) => res.locals.error?.message || '');
// Formats
const getFormat = (isError = false) => {
const base = `${config.env === 'development' ? ':date[iso] ' : ''}:clientIp - :method :url :status`;
return isError
? `${base} - :response-time ms - error: :error - referrer: :referrer`
: `${base} - :response-time ms`;
};
// Success handler (2xx, 3xx responses)
const successHandler = morgan(getFormat(), {
skip: (req, res) => res.statusCode >= 400,
stream: {
write: (msg) => logger.info(msg.trim())
}
});
// Error handler (4xx, 5xx responses)
const errorHandler = morgan(getFormat(true), {
skip: (req, res) => res.statusCode < 400,
stream: {
write: (msg) => {
const status = msg.match(/:status (\d+)/)?.[1] || 500;
status >= 500 ? logger.error(msg.trim()) : logger.warn(msg.trim());
}
}
});
module.exports = { successHandler, errorHandler };

10
src/config/tokens.js Normal file
View File

@@ -0,0 +1,10 @@
const tokenTypes = {
ACCESS: 'access',
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
};
module.exports = {
tokenTypes,
};

View File

@@ -0,0 +1,479 @@
const { iamprincipalService, chatService } = require("../services");
const ApiResponse = require("../utils/handler/ApiResponse.handler");
const { AsyncHandler } = require("../utils/handler/Async.handler");
const ApiError = require("../utils/handler/ApiError.handler");
module.exports = {
createChat: AsyncHandler(async (req, res) => {
const { userId } = req.body;
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
const {
chat_name,
is_group,
group_type,
max_participants,
description,
profile_photo,
participants = []
} = req.body;
if (is_group && !chat_name) {
return res.status(400).json(new ApiResponse(400, null, "Group chat requires a name"));
}
let chat;
let result;
if (!is_group) {
// One-to-one chat
if (participants.length !== 1) {
return res.status(400).json(new ApiResponse(400, null,
"One-to-one chat requires exactly one participant"));
}
const otherUserId = participants[0];
// Check if other user exists
const invalidParticipants = await chatService.validateParticipants([otherUserId]);
if (invalidParticipants.length > 0) {
return res.status(400).json(new ApiResponse(400, null,
"Invalid participant user"));
}
// Check for existing chat (modified part)
const existingChat = await chatService.findOneToOneChat(userId, otherUserId);
if (existingChat) {
const otherUser = await iamprincipalService.findUser(otherUserId);
result = {
chat: existingChat,
otherUser: {
id: otherUser.id,
name: `${otherUser.full_name}`,
profile_photo: otherUser.profile_photo
},
isNew: false
};
return res.status(200).json(new ApiResponse(200, result, "Chat already exists"));
}
// Create new chat if none exists
const chat = await chatService.createOneToOneChat(userId, otherUserId, profile_photo);
const otherUser = await iamprincipalService.findUser(otherUserId);
result = {
chat: chat,
otherUser: {
id: otherUser.id,
name: `${otherUser.full_name}`,
profile_photo: otherUser.profile_photo
},
isNew: true
};
} else {
// Group chat logic
const allParticipants = [...new Set([userId, ...participants])];
// Validate all participants exist
const invalidParticipants = await chatService.validateParticipants(participants);
if (invalidParticipants.length > 0) {
return res.status(400).json(new ApiResponse(400, null,
`Invalid participants: ${invalidParticipants.join(', ')}`));
}
const chat = await chatService.createGroupChat(
userId,
{ chat_name, group_type, max_participants, description, profile_photo },
allParticipants
);
const participantDetails = await iamprincipalService.getParticipantsDetails(allParticipants);
result = {
chat: chat,
participants: participantDetails,
isNew: true
};
}
res
.status(200)
.json(new ApiResponse(200, result, 'Chat created successfully.'));
}),
addParticipant: AsyncHandler(async (req, res) => {
const userId = String(req.params.userId); // acting user
const { chatId, newParticipantId } = req.body;
if (!chatId || !newParticipantId) {
return res.status(400).json(new ApiResponse(400, null, "chatId and newParticipantId are required"));
}
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
// Validate the participant to be added exists
const newUser = await iamprincipalService.getById(newParticipantId);
if (!newUser) {
return res.status(400).json(new ApiResponse(400, null, "Invalid participant user"));
}
// Fetch the chat to validate it's a group and the user has permission
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
if (chat.participants.some(participant => String(participant.id) === String(newParticipantId))) {
return res.status(400).json(new ApiResponse(400, null, "Participant already in the chat"));
}
if (!chat.is_group) {
return res.status(400).json(new ApiResponse(400, null, "Cannot add members to a one-to-one chat"));
}
if (chat.max_participants && chat.participants.length >= chat.max_participants) {
return res.status(400).json(new ApiResponse(400, null, "Cannot add more participants to this group chat"));
}
if (chat.group_type === 'public') {
// Directly add participant to public group
const updatedChat = await chatService.addParticipantToGroup(chatId, newParticipantId, userId);
const participantDetails = await iamprincipalService.findUser(newParticipantId);
return res.status(200).json(new ApiResponse(200, {
chat: updatedChat,
addedParticipant: {
id: participantDetails.id,
name: participantDetails.user_name,
profile_photo: participantDetails.profile_photo
}
}, "Participant added successfully."));
} else if (chat.group_type === 'private') {
// Create join request for private group
await chatService.createJoinRequest(chatId, newParticipantId);
return res.status(200).json(new ApiResponse(200, {
chatId,
requestedParticipant: {
id: newUser.id,
name: newUser.user_name,
profile_photo: newUser.profile_photo
},
is_requested: true
}, "Join request sent. Awaiting admin approval."));
} else {
return res.status(400).json(new ApiResponse(400, null, "Unknown group type"));
}
}),
removeParticipant: AsyncHandler(async (req, res) => {
const userId = String(req.params.userId); // acting user
const { chatId, participantId } = req.body;
if (!chatId || !participantId) {
return res.status(400).json(new ApiResponse(400, null, "chatId and participantId are required"));
}
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
// Validate the participant to be added exists
const newUser = await iamprincipalService.getById(participantId);
if (!newUser) {
return res.status(400).json(new ApiResponse(400, null, "Invalid participant user"));
}
// Fetch the chat to validate it's a group and the user has permission
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
if (!chat.is_group) {
return res.status(400).json(new ApiResponse(400, null, "Cannot add members to a one-to-one chat"));
}
const isParticipantInChat = chat.participants.some(
participant => String(participant.id) === String(participantId)
);
if (!isParticipantInChat) {
return res.status(400).json(new ApiResponse(400, null, "Participant is not in the chat"));
}
// Optional: Verify userId has permission to add (e.g., is admin/creator)
if (chat.group_admin_id != userId) {
return res.status(403).json(new ApiResponse(403, null, "Only admins can remove participants"));
}
// Add participant
const updatedChat = await chatService.removeParticipantToGroup(chatId, participantId);
const participantDetails = await iamprincipalService.findUser(participantId);
res.status(200).json(new ApiResponse(200, {
chat: updatedChat,
removedParticipant: {
id: participantDetails.id,
name: participantDetails.user_name,
profile_photo: participantDetails.profile_photo
}
}, "Participant removed successfully."));
}),
getAllChatParticipants: AsyncHandler(async (req, res) => {
const { chatId } = req.params;
if (!chatId) {
return res.status(400).json(new ApiResponse(400, null, "chatId is required"));
}
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
const filteredParticipants = chat.participants.map(participant => ({
id: participant.id,
chat_participant_link: participant.chat_participant_link,
is_active: participant.is_active,
profile_photo: participant.profile_photo,
last_login_datetime: participant.last_login_datetime,
email_address: participant.email_address,
phone_number: participant.phone_number,
gender: participant.gender,
date_of_birth: participant.date_of_birth,
full_name: participant.full_name,
user_name: participant.user_name
}));
res.status(200).json(new ApiResponse(200, {
chat_id: chat.id,
chat_name: chat.chat_name,
is_group: chat.is_group,
participants: filteredParticipants
}, "Participants fetched successfully."));
}),
getAllRequestForGroup: AsyncHandler(async (req, res) => {
const userId = String(req.params.userId);
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
const chatsRequest = await chatService.getAllRequest(userId);
if (!chatsRequest || chatsRequest.length === 0) {
return res.status(404).json(new ApiResponse(404, null, "No request found"));
}
res.status(200).json(new ApiResponse(200, chatsRequest, "Chats request successfully."));
}),
shareFilesInChats: AsyncHandler(async (req, res) => {
const { userId, chatId } = req.body;
if (!userId || !chatId) {
return res.status(400).json(new ApiResponse(400, null, "userId and chatId are required"));
}
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "User not found"));
}
const files = req.files?.files || [];
if (!chatId || files.length === 0) {
return res.status(400).json(new ApiResponse(400, null, "chatId and at least one file are required"));
}
const fileNames = files.map(file => file.filename);
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
console.log(chat);
if (!chat.participants.some(participant => String(participant.id) === String(userId))) {
return res.status(403).json(new ApiResponse(403, null, "User not part of the chat"));
}
const media = await chatService.saveChatMedia(chatId, fileNames);
res.status(200).json(new ApiResponse(200, media, "Files shared successfully."));
}),
acceptGroupJoinRequest: AsyncHandler(async (req, res) => {
const userId = String(req.params.userId); // acting user (admin)
const { chatId, participantId } = req.body;
if (!chatId || !participantId) {
return res.status(400).json(new ApiResponse(400, null, "chatId and participantId are required"));
}
// Validate acting user
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
// Fetch chat
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
if (!chat.is_group || chat.group_type !== 'private') {
return res.status(400).json(new ApiResponse(400, null, "This action is only for private groups"));
}
if (chat.group_admin_id != userId) {
return res.status(403).json(new ApiResponse(403, null, "Only admins can accept requests"));
}
// Check if participant has a pending request
const hasRequest = await chatService.hasPendingJoinRequest(chatId, participantId);
if (!hasRequest) {
return res.status(400).json(new ApiResponse(400, null, "No pending join request for this participant"));
}
// Accept request
await chatService.acceptJoinRequest(chatId, participantId);
res.status(200).json(new ApiResponse(200, null, "Join request accepted"));
}),
rejectGroupJoinRequest: AsyncHandler(async (req, res) => {
const userId = String(req.params.userId); // acting user (admin)
const { chatId, participantId } = req.body;
if (!chatId || !participantId) {
return res.status(400).json(new ApiResponse(400, null, "chatId and participantId are required"));
}
// Validate acting user
const principal = await iamprincipalService.getById(userId);
if (!principal) {
return res.status(400).json(new ApiResponse(400, "Invalid user"));
}
// Fetch chat
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
if (!chat.is_group || chat.group_type !== 'private') {
return res.status(400).json(new ApiResponse(400, null, "This action is only for private groups"));
}
if (chat.group_admin_id != userId) {
return res.status(403).json(new ApiResponse(403, null, "Only admins can reject requests"));
}
// Check if participant has a pending request
const hasRequest = await chatService.hasPendingJoinRequest(chatId, participantId);
if (!hasRequest) {
return res.status(400).json(new ApiResponse(400, null, "No pending join request for this participant"));
}
// Reject request
await chatService.rejectJoinRequest(chatId, participantId);
res.status(200).json(new ApiResponse(200, null, "Join request rejected"));
}),
getAllRequestsForGroupAdmin: AsyncHandler(async (req, res) => {
const { chatId } = req.params;
if (!chatId) {
return res.status(400).json(new ApiResponse(400, null, "chatId is required"));
}
// Fetch the chat to ensure it exists and is a group
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
if (!chat.is_group) {
return res.status(400).json(new ApiResponse(400, null, "Not a group chat"));
}
// Get all pending join requests for this group
const requests = await chatService.getAllJoinRequestsForGroup(chatId);
res.status(200).json(new ApiResponse(200, requests, "Join requests fetched successfully."));
}),
getMediaFilesByChatId: AsyncHandler(async (req, res) => {
const { chatId } = req.params;
if (!chatId) {
return res.status(400).json(new ApiResponse(400, null, "chatId is required"));
}
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
return res.status(404).json(new ApiResponse(404, null, "Chat not found"));
}
// Get media files associated with the chat
const mediaFiles = await chatService.getMediaFilesByChatId(chatId);
if (!mediaFiles || mediaFiles.length === 0) {
return res.status(404).json(new ApiResponse(404, null, "No media files found for this chat"));
}
res.status(200).json(new ApiResponse(200, mediaFiles, "Media files fetched successfully."));
}),
sendMessage: AsyncHandler(async (req, res) => {
const { userId, chatId, message, receiverId } = req.body;
if (!userId || !chatId || !message) {
throw new ApiError(400, 'userId, chatId, and message are required');
}
const principal = await iamprincipalService.getById(userId);
if (!principal) {
throw new ApiError(400, 'User not found');
}
const chat = await chatService.getChatWithParticipants(chatId);
if (!chat) {
throw new ApiError(400, 'Chat not found');
}
if (!chat.participants.some(participant => String(participant.id) === String(userId))) {
throw new ApiError(400, 'User not part of the chat');
}
const savedMessage = await chatService.saveMessage({
chat_xid: chatId,
sender_xid: userId,
receiver_xid: receiverId || null,
message: message,
message_type: "text"
});
const io = req.app.get('io');
io.to(chatId).emit('chat-message', savedMessage.dataValues);
res.status(200).json(new ApiResponse(200, savedMessage, "Message sent successfully."));
}),
}

3
src/controller/index.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
chatController: require("./chat.controller")
}

88
src/index.js Normal file
View File

@@ -0,0 +1,88 @@
const express = require("express");
const cors = require("cors");
const { createServer } = require("node:http");
const { Server } = require("socket.io");
const { errorConverter, errorHandler } = require('./middlewares/error.js');
const verifyJWTToken = require("./middlewares/verifyJWTToken.js");
const app = express();
const config = require("./config/config.js");
const morgan = require("./config/morgan");
const compression = require("compression");
if (config.env !== "test") {
app.use(morgan.successHandler);
app.use(morgan.errorHandler);
}
// const IP = '192.168.50.127'; // Replace with your local IP address
// gzip compression
app.use(compression());
// socket io
const server = createServer(app);
const io = new Server(server, {
pingTimeout: 60000,
cors: {
origin: "*",
credentials: true,
},
});
app.set("io", io);
var corOptions = {
origin: "*",
};
//middleware
app.use(cors(corOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// make images access public by ther correct path
app.use("/public", express.static("public"));
app.get("/", (req, res) => {
res.json({ message: "hello from api" });
});
// app.use(verifyJWTToken); // verify jwt token in socket
// router
const router = require("./routes");
const db = require("./models/index.js");
const { initializeSocketIO } = require("./socket/index.js");
const ApiError = require("./utils/handler/ApiError.handler.js");
// api route
app.use("/api", router);
//port
const PORT = process.env.PORT || 8000;
// send back a 404 error for any unknown api request
app.use((req, res, next) => {
next(new ApiError(404, "Not found"));
});
// convert error to ApiError, if needed
app.use(errorConverter);
// handle error
app.use(errorHandler);
//server
initializeSocketIO(io);
db.sequelize
.sync({ force: false })
.then(() => {
console.log("Yes re-sync done!");
server.listen(PORT, () => {
// server.listen(PORT, IP, () => {
console.log(`Server is running on port ${PORT}`);
// console.log(`Server running at http://${IP}:${PORT}`);
});
})
.catch((err) => {
console.log("Error : ", err.message);
// process.exit(1);
});

45
src/middlewares/auth.js Normal file
View File

@@ -0,0 +1,45 @@
const jwt = require('jsonwebtoken');
const httpStatus = require('http-status');
const { roleRights } = require('../config/roles'); // Replace with your actual JWT secret configuration
const ApiError = require('../utils/handler/ApiError.handler');
const config = require('../config/config');
const verifyCallback = async (req, resolve, reject, requiredRights) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret);
req.user = decoded;
if (requiredRights.length) {
const userRights = roleRights.get(req.user.role);
const hasRequiredRights = requiredRights.every((requiredRight) =>
userRights.includes(requiredRight)
);
if (!hasRequiredRights && req.params.userId !== req.user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
} catch (error) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Please authenticate', error)
);
}
};
const auth =
(...requiredRights) =>
async (req, res, next) => {
return new Promise((resolve, reject) => {
verifyCallback(req, resolve, reject, requiredRights);
})
.then(() => next())
.catch((err) => next(err));
};
module.exports = auth;

78
src/middlewares/error.js Normal file
View File

@@ -0,0 +1,78 @@
const {
UniqueConstraintError,
BaseError,
ValidationError,
} = require('sequelize');
const ApiError = require('../utils/handler/ApiError.handler');
const config = require('../config/config');
const httpStatus = require('http-status');
const logger = require('../config/logger');
const multer = require('multer');
const errorConverter = (err, req, res, next) => {
let error = err;
if (error instanceof multer.MulterError) {
// Handle Multer errors
error = new ApiError(
httpStatus.BAD_REQUEST,
error.message,
error,
true,
err.stack
);
} else if (
error instanceof ValidationError ||
error instanceof UniqueConstraintError
) {
// Handle Sequelize validation and unique constraint errors
const messages = error.errors.map((e) => e.message);
error = new ApiError(
httpStatus.BAD_REQUEST,
messages.join(', '),
messages,
true,
err.stack
);
} else if (!(error instanceof ApiError)) {
// Handle other errors
const statusCode =
error.statusCode || error instanceof BaseError
? httpStatus.BAD_REQUEST
: httpStatus.INTERNAL_SERVER_ERROR;
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, error, false, err.stack);
}
next(error);
};
const errorHandler = (err, req, res, next) => {
let { statusCode, message, errors } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
errors = [];
}
res.locals.errorMessage = message;
const response = {
code: statusCode,
message,
errors,
stack: config.env === 'development' ? err.stack : undefined
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).json(response);
};
module.exports = {
errorConverter,
errorHandler,
};

View File

@@ -0,0 +1,19 @@
// errorMiddleware.js
const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || "Internal Server Error";
const errors = err.errors || [];
const stack = err.stack;
res.status(statusCode).json({
error: {
statusCode,
message,
errors,
stack,
},
});
};
module.exports = { errorHandler };

View File

@@ -0,0 +1,95 @@
const multer = require("multer");
const fs = require("fs");
const path = require("path");
function generateFormattedDate() {
const now = new Date();
return `${now.getDate().toString().padStart(2, "0")}-${(now.getMonth() + 1)
.toString()
.padStart(2, "0")}-${now.getFullYear()}`;
}
function generateFormattedDateTime() {
const now = new Date();
const date = `${now.getDate().toString().padStart(2, "0")}-${(now.getMonth() + 1)
.toString()
.padStart(2, "0")}-${now.getFullYear()}`;
const time = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes()
.toString()
.padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`;
return `${date} ${time}`;
}
function generateFormattedDateTimeWithTimestamp() {
const now = new Date();
const date = `${now.getDate().toString().padStart(2, "0")}-${(now.getMonth() + 1)
.toString()
.padStart(2, "0")}-${now.getFullYear()}`;
const time = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes()
.toString()
.padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`;
const unixTimestamp = Math.floor(now.getTime() / 1000); // Get Unix timestamp
// return {
// formattedDateTime: `${date} ${time}`, // "DD-MM-YYYY HH:MM:SS"
// timestamp: unixTimestamp // Unix timestamp
// };
return unixTimestamp;
}
const _createMulterOrginalFileStorage = (filename) => {
// console.log(filename);
return multer.diskStorage({
// Set the destination directory for uploaded files
destination: function (req, file, cb) {
// Check if the 'public' directory exists, and create it if it doesn't
if (!fs.existsSync("public")) {
try {
fs.mkdirSync("public");
} catch (err) {
return cb(err);
}
}
// Check if the specified directory exists inside 'public', and create it if it doesn't
if (!fs.existsSync(`public/${filename}`)) {
try {
fs.mkdirSync(`public/${filename}`);
} catch (err) {
return cb(err);
}
}
// Call the callback with the path to the destination directory
cb(null, `public/${filename}`);
},
// Set the filename for uploaded files
filename: function (req, file, cb) {
// console.log(file);
const formattedDate = generateFormattedDateTimeWithTimestamp();
// Use the original filename, but append the current timestamp and file extension
const transformedFilename = file.originalname
.trim() // Trim whitespace
.toLowerCase() // Convert to lowercase
.replace(/[^a-z\d.]+/g, "_") // Replace non-alphanumeric characters with underscores
.replace(/_+/g, "_") // Replace consecutive underscores with a single underscore
.replace(/\.[^.]+$/, "") // Get everything before the first dot
.replace(/\./g, "_") // Remove Dot
.replace(/^_|_$/g, ""); // Remove leading and trailing underscores
cb(
null,
transformedFilename +
"_" +
formattedDate +
path.extname(file.originalname)
);
},
});
};
module.exports = (foldername) => {
return _createMulterOrginalFileStorage(foldername);
};

View File

@@ -0,0 +1,27 @@
const { default: rateLimit } = require('express-rate-limit');
const logger = require('../config/logger');
const ApiError = require('../utils/handler/ApiError.handler');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
skipSuccessfulRequests: true,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
return req.clientIp || req.ip; // Using IP address from requestIp.mw() instead of req.ip
},
handler: (req, res, next, options) => {
logger.warn(`Too many requests hit from this IP: ${req.clientIp}`);
// Throwing an ApiError with appropriate status code and message
throw new ApiError(
options.statusCode || 429, // 429 for "Too Many Requests" is a common status code
`Too many requests. You are only allowed ${options.max} requests per ${options.windowMs / 60000} minutes`
);
},
});
module.exports = {
authLimiter,
};

View File

@@ -0,0 +1,81 @@
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const ApiError = require('../utils/handler/ApiError.handler');
const maxFileSize = 10 * 1024 * 1024; // 10 MB (in bytes)
const allowedFileTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'video/mp4',
'video/x-msvideo',
'video/x-matroska',
'video/webm',
'video/ogg',
'video/quicktime',
];
// Define the storage engine function with parameters
const uploader = (folderName) =>
multer.diskStorage({
destination: function (req, file, cb) {
// Check if the 'public' directory exists, and create it if it doesn't
if (!fs.existsSync('public')) {
try {
fs.mkdirSync('public');
} catch (err) {
return cb(err);
}
}
if (!fs.existsSync(`public/${folderName}`)) {
try {
fs.mkdirSync(`public/${folderName}`);
} catch (err) {
return cb(err);
}
}
cb(null, `public/${folderName}`);
},
filename: function (req, file, cb) {
const transformedFilename = file.originalname
.trim() // Trim whitespace
.toLowerCase() // Convert to lowercase
.replace(/[^a-z\d.]+/g, '_') // Replace non-alphanumeric characters with underscores
.replace(/_+/g, '_') // Replace consecutive underscores with a single underscore
.replace(/\.[^.]+$/, '') // Get everything before the first dot
.replace(/\./g, '_') // Remove Dot
.replace(/^_|_$/g, '');
cb(null, transformedFilename + path.extname(file.originalname));
},
});
// Define the file filter function with parameters
const fileFilter = (allowedTypes = allowedFileTypes) =>
function (req, file, cb) {
if (allowedTypes.includes(file.mimetype)) {
cb(null, true); // Allow the file
} else {
cb(new ApiError(400, 'File type not allowed')); // Reject the file
}
};
// Set up the multer storage with parameters and file filter
const storage = (folderName, allowedTypes, options = {}) =>
multer({
storage: uploader(folderName || 'asset'),
limits: {
fileSize: options.fileSize || maxFileSize,
},
fileFilter: fileFilter(allowedTypes),
...options, // Merge additional options
});
// Example usage:
// const upload = storage("document", allowedFileTypes, { fileSize: 5 * 1024 * 1024, preservePath: true });
// You can then use `upload` as middleware in your routes.
module.exports = storage;

View File

@@ -0,0 +1,33 @@
const httpStatus = require('http-status');
const ApiError = require('../utils/handler/ApiError.handler');
const pick = require('../utils/handler/pick.handler');
const validate = (schema) => (req, res, next) => {
const validSchema = pick(schema, [
'params',
'query',
'body',
'file',
'files',
]);
const object = pick(req, Object.keys(validSchema));
const promises = Object.keys(validSchema).map((key) =>
validSchema[key].validate(object[key], { abortEarly: false })
);
Promise.all(promises)
.then((validatedValues) => {
validatedValues.forEach((value, index) => {
const key = Object.keys(validSchema)[index];
req[key] = value;
});
next();
})
.catch((err) => {
const errorMessage = err.inner.map((detail) => detail.message).join(', ');
next(new ApiError(httpStatus.BAD_REQUEST, errorMessage));
});
};
module.exports = validate;

View File

@@ -0,0 +1,61 @@
const jwt = require("jsonwebtoken");
const { IamPrincipal } = require("../models");
require("dotenv").config();
const db = require('../models');
const { Op } = require("sequelize");
// Middleware to verify the token
const verifyToken = async (req, res, next) => {
// Get token from the 'Authorization' header (format: 'Bearer <token>')
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(403).json({ message: 'No token provided' });
}
// Split the token from the 'Bearer' scheme
const token = authHeader.split(' ')[1];
if (!token) {
return res.status(403).json({ message: 'Token is missing' });
}
// // Verify the token
// jwt.verify(token, SECRET_KEY, (err, decoded) => {
// if (err) {
// return res.status(401).json({ message: 'Invalid token' });
// }
// // Save the decoded user info to the request object
// req.user = decoded;
// next(); // Proceed to the next middleware or controller
// });
// Decode the token without verifying the signature
decoded = jwt.decode(token);
if (!decoded || !decoded.exp) {
return res.status(400).json({ message: 'Invalid token' });
}
const currentTimestamp = Math.floor(Date.now() / 1000); // Current time in seconds
if (currentTimestamp > decoded.exp) {
return res.status(401).json({ message: 'Token has expired' });
}
// Check if im_principal_id exists in the table
const checkUser = await db.IamPrincipal.findOne({
where: {
id: decoded.sub, // Check that the id matches decoded.sub
active: '1', // Ensure the user is active (assuming '1' means active)
deleted_at: { [Op.eq]: null } // Ensure delete_at is null (i.e., not deleted)
}
});
if (!checkUser) {
return res.status(401).json({ message: 'Invalid token' });
}
// Save the decoded user info to the request object
req.token = decoded;
next(); // Proceed to the next middleware or controller
};
module.exports = verifyToken; // Export the middleware

52
src/models/chat.model.js Normal file
View File

@@ -0,0 +1,52 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class chat extends Model {
static associate(models) {
// Many-to-many relationship with iam_principal through chat_participant_link
this.belongsToMany(models.iam_principal, {
through: models.chat_participant_link,
foreignKey: 'chat_xid',
otherKey: 'principal_xid',
as: 'participants'
});
// Direct hasMany relationship with the link table
this.hasMany(models.chat_participant_link, {
foreignKey: 'chat_xid',
as: 'participantLinks'
});
}
}
chat.init(
{
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
chat_name: { type: DataTypes.STRING(255), allowNull: true },
is_group: { type: DataTypes.BOOLEAN, allowNull: true },
group_type: { type: DataTypes.STRING, allowNull: true },
group_admin_id: { type: DataTypes.INTEGER, allowNull: true },
max_participants: { type: DataTypes.INTEGER, allowNull: true },
description: { type: DataTypes.TEXT, allowNull: true },
profile_photo: { type: DataTypes.STRING, allowNull: true },
is_active: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: true,
},
created_by: { type: DataTypes.INTEGER, allowNull: true },
updated_by: { type: DataTypes.INTEGER, allowNull: true },
},
{
sequelize,
tableName: 'chat',
timestamps: true,
paranoid: true,
}
);
return chat;
};

View File

@@ -0,0 +1,46 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class chatMedia extends Model {
static associate(models) {
this.belongsTo(models.chat, {
foreignKey: "chat_xid",
as: "media",
onDelete: "CASCADE",
});
}
}
chatMedia.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
chat_xid: { type: DataTypes.INTEGER, allowNull: false, },
image_file_name: {
type: DataTypes.STRING,
allowNull: true,
},
image_path: {
type: DataTypes.STRING,
allowNull: true,
},
image_size: {
type: DataTypes.STRING,
allowNull: true,
},
is_active: {
type: DataTypes.BOOLEAN,
},
delivery_status: { type: DataTypes.ENUM('sent', 'delivered', 'read'), defaultValue: 'sent' },
},
{
sequelize,
tableName: 'chatMedia',
timestamps: true,
paranoid: true,
}
);
return chatMedia;
};

View File

@@ -0,0 +1,73 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class chatMessage extends Model {
static associate(models) {
// Define the association to chat
this.belongsTo(models.chat, {
foreignKey: "chat_xid",
as: "chat",
onDelete: "CASCADE",
});
// Define the association to iam_principal
this.belongsTo(models.iam_principal, {
foreignKey: "sender_xid",
as: "sender",
onDelete: "CASCADE",
});
this.belongsTo(models.iam_principal, {
foreignKey: "receiver_xid",
as: "receiver",
onDelete: "CASCADE",
});
}
}
chatMessage.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
chat_xid: {
type: DataTypes.BIGINT,
allowNull: false,
references: {
model: 'chat',
key: 'id'
}
},
sender_xid: {
type: DataTypes.BIGINT,
allowNull: false,
references: {
model: 'iam_principal',
key: 'id'
}
},
receiver_xid: {
type: DataTypes.BIGINT,
allowNull: true,
references: {
model: 'iam_principal',
key: 'id'
}
},
message: { type: DataTypes.STRING, allowNull: true },
message_type: { type: DataTypes.STRING, allowNull: true, defaultValue: 'text' },
delivery_status: { type: DataTypes.ENUM('sent', 'delivered', 'read'), defaultValue: 'sent' },
is_read: { type: DataTypes.BOOLEAN, allowNull: true },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
},
{
sequelize,
tableName: 'chatMessage',
timestamps: true,
paranoid: true,
}
);
return chatMessage;
};

View File

@@ -0,0 +1,79 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class chat_participant_link extends Model {
static associate(models) {
// Belongs to chat
this.belongsTo(models.chat, {
foreignKey: 'chat_xid',
as: 'chat'
});
// Belongs to iam_principal
this.belongsTo(models.iam_principal, {
foreignKey: 'principal_xid',
as: 'participant'
});
}
}
chat_participant_link.init(
{
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
},
chat_xid: {
type: DataTypes.BIGINT,
allowNull: false,
},
principal_xid: {
type: DataTypes.BIGINT,
allowNull: false,
},
role: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'e.g., "admin", "member", etc.'
},
joined_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
left_at: {
type: DataTypes.DATE,
allowNull: true
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
is_requested:{
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
comment: 'Indicates if the participant has requested to join the chat'
},
added_by: {
type: DataTypes.INTEGER,
allowNull: true
},
removed_by: {
type: DataTypes.INTEGER,
allowNull: true
}
},
{
sequelize,
tableName: 'chat_participant_link',
timestamps: true,
paranoid: true,
underscored: true
}
);
return chat_participant_link;
};

View File

@@ -0,0 +1,107 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class iam_principal extends Model {
static associate(models) {
this.belongsToMany(models.chat, {
through: models.chat_participant_link,
foreignKey: 'principal_xid',
otherKey: 'chat_xid',
as: 'chats'
});
// Direct hasMany relationship with the link table
this.hasMany(models.chat_participant_link, {
foreignKey: 'principal_xid',
as: 'chatParticipations'
});
}
}
iam_principal.init(
{
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
principal_type_xid: {
type: DataTypes.INTEGER,
allowNull: false,
},
principal_source_xid: {
type: DataTypes.INTEGER,
allowNull: false,
},
google_id: { type: DataTypes.STRING, allowNull: true },
apple_id: { type: DataTypes.STRING, allowNull: true },
facebook_id: { type: DataTypes.STRING, allowNull: true },
microsoft_id: { type: DataTypes.STRING, allowNull: true },
user_name: { type: DataTypes.STRING, allowNull: true },
password_hash: { type: DataTypes.STRING, allowNull: true },
pin: { type: DataTypes.STRING(4), allowNull: true },
full_name: { type: DataTypes.STRING, allowNull: true },
gender: { type: DataTypes.STRING, allowNull: true },
date_of_birth: { type: DataTypes.DATEONLY, allowNull: true },
phone_number: { type: DataTypes.STRING(15), allowNull: true },
other_phone_number: { type: DataTypes.STRING(15), allowNull: true },
email_address: { type: DataTypes.STRING(50), allowNull: true },
address_line1: { type: DataTypes.STRING(255), allowNull: true },
address_line2: { type: DataTypes.STRING(255), allowNull: true },
lat: { type: DataTypes.STRING(100), allowNull: true },
long: { type: DataTypes.STRING(100), allowNull: true },
city_xid: { type: DataTypes.BIGINT, allowNull: true },
state_xid: { type: DataTypes.BIGINT, allowNull: true },
country_xid: { type: DataTypes.BIGINT, allowNull: true },
post_code: { type: DataTypes.STRING(10), allowNull: true },
last_login_datetime: { type: DataTypes.DATE, allowNull: true },
profile_photo: { type: DataTypes.STRING, allowNull: true },
banner_photo: { type: DataTypes.STRING, allowNull: true },
referral_code: { type: DataTypes.STRING, allowNull: true },
description: { type: DataTypes.STRING, allowNull: true },
about: { type: DataTypes.STRING, allowNull: true },
position: { type: DataTypes.STRING, allowNull: true },
training_scores: { type: DataTypes.STRING, allowNull: true },
height: { type: DataTypes.STRING, allowNull: true },
weight: { type: DataTypes.STRING, allowNull: true },
batting_average: { type: DataTypes.STRING, allowNull: true },
is_active: {
type: DataTypes.ENUM('1', '0'),
defaultValue: '1',
comment: '1=Active, 0=InActive',
},
group_notification: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: '1=Active, 0=InActive',
},
community_notification: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: '1=Active, 0=InActive',
},
new_follower_notification: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: '1=Active, 0=InActive',
},
direct_message_notification: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: '1=Active, 0=InActive',
},
created_by: { type: DataTypes.INTEGER, allowNull: true },
modified_by: { type: DataTypes.INTEGER, allowNull: true },
},
{
sequelize,
tableName: 'iam_principal',
timestamps: true,
paranoid: true,
underscored: true,
}
);
return iam_principal;
};

53
src/models/index.js Normal file
View File

@@ -0,0 +1,53 @@
'use strict';
const { Sequelize } = require('sequelize');
const config = require('../config/config');
const logger = require('../config/logger');
// Initialize Sequelize instance
const sequelize = new Sequelize(
config.mysql[config.env].database,
config.mysql[config.env].username,
config.mysql[config.env].password,
config.mysql[config.env]
);
// Test database connection
sequelize
.authenticate()
.then(() => logger.info('Connection has been established successfully.'))
.catch((error) => logger.error('Unable to connect to the database: ', error));
// Load models
const db = {};
db.iam_principal = require('./iam_principal.model')(sequelize);
db.chat = require('./chat.model')(sequelize);
db.chat_participant_link = require('./chatParticipantLink.model')(sequelize);
db.chatMessage = require('./chatMessage.model')(sequelize);
db.chatMedia = require('./chatMedia.model')(sequelize);
// Handle model associations
(async () => {
for (const modelName of Object.keys(db)) {
if (db[modelName].associate) {
await db[modelName].associate(db);
}
}
})();
// Initialize plugins after models are set up to avoid circular dependencies
// const Transaction = require('./plugins/Transaction');
// const Pagination = require('./plugins/Pagination');
// const BulkUpsert = require('./plugins/BulkUpsert');
// db.transaction = new Transaction(sequelize);
// db.bulkUpsert = new BulkUpsert(sequelize);
// db.pagination = Pagination;
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

View File

@@ -0,0 +1,45 @@
const router = require('express').Router();
const { chatController } = require('../../controller');
const fileUpload = require('../../middlewares/fileUpload');
const multer = require('multer');
const maxFileSize = 2 * 1024 * 1024; // 10 MB (in bytes)
const allowedFileTypes = ["image/jpeg", "image/png", "image/gif"];
const Store = multer({
storage: fileUpload("images"),
limits: { fileSize: maxFileSize },
fileFilter: function (req, file, cb) {
const allowedTypes = allowedFileTypes;
if (allowedTypes.includes(file.mimetype)) {
cb(null, true); // Allow the file
} else {
cb(new Error("File type not allowed")); // Reject the file
}
},
});
const profileUpload = Store.fields([{ name: "chat_images", maxCount: 1 }]);
/**
* Created By: Pradyumn Dwivedi
* Created At: 02 May 2024
* Use: Create chat route
*/
router.post('/createNewChat', (req, res)=> {
res.send('Chat endpoint');
});
router.get('/getAllChats', (req, res)=> {
res.send('Chat endpoint');
});
router.get('/getAllChatsNotificationCount', (req, res)=> {
res.send('Chat endpoint');
});
router.post('/acceptGroupJoinRequest/:userId', chatController.acceptGroupJoinRequest);
router.post('/rejectGroupJoinRequest/:userId', chatController.rejectGroupJoinRequest);
router.get('/group/:chatId', chatController.getAllRequestsForGroupAdmin);
//export chat route
module.exports = router;

7
src/routes/chat/index.js Normal file
View File

@@ -0,0 +1,7 @@
const chatRouter = require('express').Router();
chatRouter.use('/user', require('./user.routes'));
chatRouter.use('/admin', require('./admin.routes'));
module.exports = chatRouter;

View File

@@ -0,0 +1,60 @@
const router = require('express').Router();
const { chatController } = require('../../controller');
const fileUpload = require('../../middlewares/fileUpload');
const multer = require('multer');
const { chatValidations, addParticipantsValidations } = require('../../validation');
const validate = require('../../middlewares/validate');
const maxFileSize = 2 * 1024 * 1024; // 10 MB (in bytes)
const allowedFileTypes = [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"video/mp4",
"video/mpeg",
"video/quicktime",
"video/x-msvideo",
"video/x-matroska"
];
const Store = multer({
storage: fileUpload("media"),
limits: { fileSize: maxFileSize },
fileFilter: function (req, file, cb) {
const allowedTypes = allowedFileTypes;
if (allowedTypes.includes(file.mimetype)) {
cb(null, true); // Allow the file
} else {
cb(new Error("File type not allowed")); // Reject the file
}
},
});
const mediaUpload = Store.fields([{ name: "files", maxCount: 5 }]);
/**
* Created By: Pradyumn Dwivedi
* Created At: 02 May 2024
* Use: Create chat route
*/
router.post('/createNewChat', validate(chatValidations.chat), chatController.createChat);
router.post('/addParticipant/:userId', validate(addParticipantsValidations.addParticipant), chatController.addParticipant);
router.post('/removeParticipant/:userId', validate(addParticipantsValidations.removeParticipant), chatController.removeParticipant);
router.get('/getChatParticipants/:chatId', chatController.getAllChatParticipants);
// ANGAD
router.get('/get-all-chat-group-request', chatController.getAllRequestForGroup);
router.post('/share-files', mediaUpload, chatController.shareFilesInChats);
router.post('/send-message', chatController.sendMessage);
// router.get('/getAllChats', (req, res)=> {
// res.send('Chat endpoint');
// });
// router.get('/getAllChatsNotificationCount', (req, res)=> {
// res.send('Chat endpoint');
// });
//export chat route
module.exports = router;

6
src/routes/index.js Normal file
View File

@@ -0,0 +1,6 @@
const appRouter = require("express").Router();
appRouter.use("/chat", require("./chat"));
// appRouter.use("/message", require("./messageRouter"));
module.exports = appRouter

View File

@@ -0,0 +1,298 @@
const { Op } = require("sequelize");
const db = require("../models");
const chatConstant = require("../utils/constant/chat.constant");
module.exports = {
findOneToOneChat: async (userId, otherUserId) => {
// Step 1: Find chat ID that has exactly both users
const chatIds = await db.chat_participant_link.findAll({
where: {
principal_xid: {
[Op.in]: [userId, otherUserId]
}
},
attributes: ['chat_xid'],
group: ['chat_xid'],
having: db.sequelize.literal('COUNT(DISTINCT principal_xid) = 2'),
raw: true
});
const targetChatId = chatIds.length > 0 ? chatIds[0].chat_xid : null;
if (!targetChatId) return null;
// Step 2: Fetch full chat with participant links
const chat = await db.chat.findOne({
where: {
id: targetChatId,
is_group: false
},
include: [{
model: db.chat_participant_link,
as: 'participantLinks'
}]
});
return chat;
},
createOneToOneChat: async (creatorId, otherUserId, profilePhoto) => {
const transaction = await db.sequelize.transaction();
try {
const chat = await db.chat.create({
chat_name: null,
is_group: false,
group_type: null,
profile_photo: profilePhoto,
created_by: creatorId,
is_active: true
}, { transaction });
await db.chat_participant_link.bulkCreate([
{ chat_xid: chat.id, principal_xid: creatorId, role: 'member' },
{ chat_xid: chat.id, principal_xid: otherUserId, role: 'member' }
], { transaction });
await transaction.commit();
return chat;
} catch (error) {
await transaction.rollback();
throw error;
}
},
createGroupChat: async (creatorId,
{
chat_name,
group_type,
max_participants,
description,
profile_photo
}, participants) => {
const transaction = await db.sequelize.transaction();
try {
const chat = await db.chat.create({
chat_name,
is_group: true,
group_type,
max_participants,
description,
profile_photo,
group_admin_id: creatorId,
created_by: creatorId,
is_active: true
}, { transaction });
const participantLinks = participants.map(participantId => ({
chat_xid: chat.id,
principal_xid: participantId,
role: participantId === creatorId ? 'admin' : 'member',
added_by: creatorId
}));
await db.chat_participant_link.bulkCreate(participantLinks, { transaction });
await transaction.commit();
return chat;
} catch (error) {
await transaction.rollback();
throw error;
}
},
getChatWithParticipants: async (chatId, includeRequested = false) => {
const throughOptions = {
attributes: ['role']
};
if (!includeRequested) {
throughOptions.where = { is_requested: false };
}
return db.chat.findByPk(chatId, {
include: [{
model: db.iam_principal,
as: 'participants',
through: throughOptions
}]
});
},
getChatDetails: async (chatId) => {
return db.chat.findByPk(chatId)
},
validateParticipants: async (participantIds) => {
const idsAsStrings = participantIds.map(String);
const existingUsers = await db.iam_principal.findAll({
where: { id: { [Op.in]: idsAsStrings } },
attributes: ['id']
});
const existingIds = existingUsers.map(user => user.id.toString());
return idsAsStrings.filter(id => !existingIds.includes(id));
},
addParticipantToGroup: async (chatId, newParticipantId, userId) => {
const addParticipant = await db.chat_participant_link.create({
chat_xid: chatId,
principal_xid: newParticipantId,
role: chatConstant.roles.MEMBER,
added_by: userId,
joined_at: new Date()
})
return addParticipant;
},
removeParticipantToGroup: async (chatId, newParticipantId) => {
return await db.chat_participant_link.destroy({
where: {
chat_xid: chatId,
principal_xid: newParticipantId
}
})
},
getAllRequest: async (chatId) => {
return await db.chat_participant_link.findAll({
where: {
chat_xid: chatId,
is_requested: true,
is_active: true
},
include: [{
model: db.iam_principal,
as: 'participant',
attributes: ['id', 'name', 'profile_photo']
}]
});
},
saveChatMedia: async (chatId, fileNames) => {
const mediaRecords = fileNames.map(name => ({
chat_xid: chatId,
image_file_name: name,
is_active: true,
is_read: false
}));
return await db.chatMedia.bulkCreate(mediaRecords);
},
createJoinRequest: async (chatId, userId) => {
// Check if already requested
const existing = await db.chat_participant_link.findOne({
where: {
chat_xid: chatId,
principal_xid: userId
}
});
if (existing) {
// If already requested or already a member, do nothing or update as needed
if (existing.is_requested) {
return existing;
}
// If already a member, don't create a new request
throw new Error("User is already a participant or has a pending request.");
}
// Create a new participant link with is_requested = true
return await db.chat_participant_link.create({
chat_xid: chatId,
principal_xid: userId,
role: chatConstant.roles.MEMBER,
is_requested: true,
is_active: true,
joined_at: new Date()
});
},
// ...existing code...
getAllJoinRequestsForGroup: async (chatId) => {
// Returns all users who have requested to join (is_requested: true)
const requests = await db.chat_participant_link.findAll({
where: {
chat_xid: chatId,
is_requested: true
},
include: [
{
model: db.iam_principal,
as: 'participant', // adjust if your association uses a different alias
attributes: ['id', 'user_name', 'full_name', 'profile_photo']
}
]
});
return requests.map(r => ({
id: r.principal_xid,
user_name: r.principal?.user_name,
full_name: r.principal?.full_name,
profile_photo: r.principal?.profile_photo,
requested_at: r.createdAt
}));
},
hasPendingJoinRequest: async (chatId, participantId) => {
const request = await db.chat_participant_link.findOne({
where: {
chat_xid: chatId,
principal_xid: participantId,
is_requested: true
}
});
return !!request;
},
acceptJoinRequest: async (chatId, participantId) => {
const transaction = await db.sequelize.transaction();
try {
// Update the participant link to mark as accepted
await db.chat_participant_link.update({
is_requested: false,
is_active: true,
joined_at: new Date()
}, {
where: {
chat_xid: chatId,
principal_xid: participantId
},
transaction
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
rejectJoinRequest: async (chatId, participantId) => {
const transaction = await db.sequelize.transaction();
try {
// Either delete the request or mark it as rejected
await db.chat_participant_link.destroy({
where: {
chat_xid: chatId,
principal_xid: participantId,
is_requested: true
},
transaction
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
saveMessage: async ({ chat_xid, sender_xid, receiver_xid, message, message_type }) => {
return await db.chatMessage.create({
chat_xid,
sender_xid,
receiver_xid,
message,
message_type
});
},
}

View File

@@ -0,0 +1,19 @@
const { Op } = require("sequelize")
const db = require("../models")
module.exports = {
create: async (data) => await db.iam_principal.create(data),
getAll: async () => await db.iam_principal.findAll(),
getById: async (id) => await db.iam_principal.findByPk(id),
getParticipantsDetails: async (participants) => {
return await db.iam_principal.findAll({
where: { id: { [Op.in]: participants } },
attributes: ['id', 'full_name', 'user_name', 'profile_photo']
})
},
findUser: async(userId)=> {
return await db.iam_principal.findByPk(userId, {
attributes: ['id', 'full_name', 'user_name', 'profile_photo']
})
}
}

4
src/services/index.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
iamprincipalService: require("./iam_principal.service"),
chatService: require("./chat.service")
}

45
src/socket/index.js Normal file
View File

@@ -0,0 +1,45 @@
const JWT = require("jsonwebtoken");
const db = require("../models");
const { CONNECTED_EVENT, DISCONNECT_EVENT, SOCKET_ERROR_EVENT } = require("../utils/constant/socket.constant");
require("dotenv").config();
const verfiedAccessToken = (token) => {
return JWT.decode(token);
};
module.exports = {
initializeSocketIO: (io) => {
io.on("connection", async (socket) => {
try {
socket.emit(CONNECTED_EVENT);
// Join a chat room (for both one-to-one and group)
socket.on('join-room', ({ chatId }) => {
socket.join(chatId);
console.log(`User with socket ID ${socket.id} joined room: ${chatId}`);
socket.emit('joined-room', { chatId });
});
// Send message to a chat room
socket.on('chat-message', (data) => {
console.log("New message data:", data);
if (data.chatId) {
io.to(data.chatId).emit('chat-message', data);
}
});
console.log("User connected 🗼", socket.id);
socket.on(DISCONNECT_EVENT, () => {
console.log("user has disconnected 🚫. socketId:", socket.id);
});
} catch (error) {
socket.emit(
SOCKET_ERROR_EVENT,
error?.message ||
"Something went wrong while connecting to the socket."
);
}
});
},
};

View File

@@ -0,0 +1,10 @@
module.exports = {
GROUP_CHAT: 'group_chat',
PUBLIC_GROUP: '1',
PRIVATE_GROUP: '2',
roles: {
ADMIN: 'admin',
MEMBER: 'member'
}
};

View File

@@ -0,0 +1,29 @@
module.exports = {
// ? once user is ready to go
CONNECTED_EVENT: "connected",
// ? when user gets disconnected
DISCONNECT_EVENT: "disconnect",
// ? when user joins a socket room
JOIN_CHAT_EVENT: "joinChat",
// ? when participant gets removed from group, chat gets deleted or leaves a group
LEAVE_CHAT_EVENT: "leaveChat",
// ? when admin updates a group name
UPDATE_GROUP_NAME_EVENT: "updateGroupName",
// ? when message send to server
MESSAGE_SENT_EVENT: "messageSent",
// ? when new message is received
MESSAGE_RECEIVED_EVENT: "messageReceived",
// ? when there is new one on one chat, new group chat or user gets added in the group
NEW_CHAT_EVENT: "newChat",
// ? when there is an error in socket
SOCKET_ERROR_EVENT: "socketError",
// ? when participant stops typing
STOP_TYPING_EVENT: "stopTyping",
// ? when participant starts typing
TYPING_EVENT: "typing",
// ? when participant sent message
REFRESH_CHAT_EVENT: "refreshChat",
//for sending notification for unread messages to reciever
REFRESH_NOTIFICATION_COUNT_EVENT: "refreshNotificationCount",
};

View File

@@ -0,0 +1,41 @@
/**
* Custom error class for handling API errors. Extends the native Error class.
* @class ApiError
* @extends Error
*
* @param {number} statusCode - HTTP status code associated with the error.
* @param {string} [message="Something went wrong"] - Error message (default is a generic message).
* @param {Array} [errors=[]] - Array containing specific error details or additional information.
* @param {boolean} [isOperational=true] - Indicates if the error is operational or not.
* @param {string} [stack=""] - Stack trace for the error (optional, defaults to an empty string).
*/
class ApiError extends Error {
constructor(
statusCode,
message = 'Something went wrong',
errors = [],
isOperational = true,
stack = ''
) {
// Call the constructor of the parent class (Error)
super(message);
// Custom properties for API error handling
this.statusCode = statusCode; // HTTP status code associated with the error.
this.data = null; // Additional data associated with the error (initially set to null).
this.message = message; // Error message for the API response.
this.success = false; // Indicates the failure nature of the API request.
this.errors = errors; // Specific error details or additional information.
this.isOperational = isOperational; // Indicates if the error is operational or not.
// Set the stack trace if provided; otherwise, capture the current stack trace.
if (stack) {
this.stack = stack; // Provided stack trace.
} else {
Error.captureStackTrace(this, this.constructor); // Capture the current stack trace.
}
}
}
// Export the ApiError class for use in other modules.
module.exports = ApiError;

View File

@@ -0,0 +1,19 @@
/**
* Class representing a standard API response.
* @class ApiResponse
*
* @param {number} statusCode - HTTP status code associated with the response.
* @param {any} data - Data to be returned in the response.
* @param {string} [message="Success"] - Response message (default is "Success").
*/
class ApiResponse {
constructor(statusCode, data, message = 'Success') {
this.statusCode = statusCode; // HTTP status code associated with the response.
this.data = data; // Data to be returned in the response.
this.message = message; // Response message (default is "Success").
this.success = statusCode < 400; // Indicates if the response is successful based on the status code.
}
}
// Export the ApiResponse class for use in other modules.
module.exports = ApiResponse;

View File

@@ -0,0 +1,11 @@
/**
* Asynchronous error handler for Express routes.
* @function AsyncHandler
*
* @param {Function} fn - Asynchronous function to be executed.
* @returns {Function} Middleware function that handles errors for asynchronous routes.
*/
module.exports.AsyncHandler = (fn) => (req, res, next) => {
// Wrap the asynchronous function in a promise and handle any errors by passing them to the next middleware.
Promise.resolve(fn(req, res, next)).catch(next);
};

View File

@@ -0,0 +1,20 @@
/**
* Create an object composed of the picked object properties.
* @function pick
*
* @param {Object} object - The source object to pick properties from.
* @param {string[]} keys - The array of property names to pick from the source object.
* @returns {Object} New object with only the picked properties.
*/
const pick = (object, keys) => {
return keys.reduce((obj, key) => {
// Check if the object has the specified property
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
obj[key] = object[key]; // Assign the property to the new object
}
return obj; // Return the new object
}, {}); // Initialize the new object as an empty object
};
// Export the pick function for use in other modules.
module.exports = pick;

View File

@@ -0,0 +1,22 @@
const { Buffer } = require('buffer');
const crypto = require('crypto');
// AES encryption function using crypto
function aesEncrypt(trandata, key) {
const iv = Buffer.from('PGKEYENCDECIVSPC'); // 16-byte IV for AES
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(trandata, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
// AES decryption function using crypto
function aesDecryption(encryptedHex, key) {
const iv = Buffer.from('PGKEYENCDECIVSPC');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
module.exports = { aesEncrypt, aesDecryption };

View File

@@ -0,0 +1,32 @@
const dns = require('node:dns');
const logger = require('../../config/logger');
// Async function to resolve MX records for a domain
async function resolveMx(domain) {
return new Promise((resolve, reject) => {
dns.resolveMx(domain, (err, mxRecords) => {
if (err) {
reject(err);
return;
}
const addresses = mxRecords.map((mxRecord) => mxRecord.exchange);
resolve(addresses);
});
});
}
// Async function to check email address validity
async function checkEmailValidity(email) {
try {
const domain = email?.split('@')[1];
const addresses = await resolveMx(domain, 'MX');
if (addresses && addresses?.length > 0) {
return true;
}
return false; // No MX record exists
} catch (err) {
logger.warn(err);
return false; // Error occurred
}
}
module.exports = checkEmailValidity;

View File

@@ -0,0 +1,12 @@
function isValidFileSize(value) {
// Check the file size here, e.g., to ensure it's not larger than a specific limit.
// Adjust the maximum size as needed (in bytes).
const maxSizeInBytes = 10 * 1024 * 1024; // 10 MB
if (value && value.size <= maxSizeInBytes) {
return true; // Valid file size
}
return false; // Invalid file size
}
module.exports = isValidFileSize;

View File

@@ -0,0 +1,85 @@
const ExcelJS = require('exceljs');
const fs = require('fs');
const logger = require('../../config/logger');
/**
* Generates an Excel file using provided headers and data, and streams it directly in the HTTP response.
*
* @param {Array} headers - An array of objects where each object defines a column in the Excel file.
* Each object should have the following structure:
* - header: {string} The column name to be displayed in the Excel sheet.
* - key: {string} The key that maps to the data in the data array.
* - width: {number} The width of the column in the Excel sheet.
*
* Example:
* const headers = [
* { header: 'ID', key: 'id', width: 10 },
* { header: 'Name', key: 'name', width: 30 },
* { header: 'Age', key: 'age', width: 10 }
* ];
*
* @param {Array} data - An array of objects where each object represents a row in the Excel file.
* The keys in each object must correspond to the keys defined in the headers array.
*
* Example:
* const data = [
* { id: 1, name: 'John Doe', age: 30 },
* { id: 2, name: 'Jane Smith', age: 25 },
* { id: 3, name: 'Mike Johnson', age: 35 }
* ];
*
* @param {Object} res - The HTTP response object, used to stream the generated Excel file back to the client.
*
* @returns {Promise<void>} - A promise that resolves once the Excel file has been fully written to the response.
*/
async function generateExcelWithResponse(headers, data, res) {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
// Set headers (columns)
worksheet.columns = headers;
// Add data rows
data.forEach((row) => {
worksheet.addRow(row);
});
// Set the headers for the response to trigger a file download
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
res.setHeader('Content-Disposition', 'attachment; filename="data.xlsx"');
// Stream the workbook directly to the response
await workbook.xlsx.write(res);
res.end(); // Close the stream after writing the file
}
async function generateExcelWithFilepath(headers, data, outputPath) {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
// Set the headers (columns) dynamically
worksheet.columns = headers;
// Add rows from the data array
data.forEach((row) => {
worksheet.addRow(row);
});
// Create a writable stream to save the file
const writeStream = fs.createWriteStream(outputPath);
// Stream workbook to the write stream
await workbook.xlsx
.write(writeStream)
.then(() => {
logger.info(`Excel file generated at ${outputPath}`);
})
.catch((err) => {
logger.error('Error generating Excel:', err);
});
}
module.exports = { generateExcelWithResponse, generateExcelWithFilepath };

View File

@@ -0,0 +1,14 @@
function maskEmail(email) {
const [localPart, domain] = email.split('@');
const maskedLocal =
localPart[0] +
'*'.repeat(Math.max(localPart.length - 2, 1)) +
localPart.slice(-1);
const [domainName, domainExtension] = domain.split('.');
const maskedDomain =
domainName[0] +
'*'.repeat(Math.max(domainName.length - 2, 1)) +
domainName.slice(-1);
return `${maskedLocal}@${maskedDomain}.${domainExtension}`;
}
module.exports = maskEmail;

View File

@@ -0,0 +1,495 @@
const {
sendNotification,
} = require('../../services/push_notification/onesignal.service');
// Function to get deposit received message
// const getDepositReceivedMessage = amount => ({
// en: `Funds Added to Your Wallet: A deposit of $${amount} has been credited to your account.`,
// ar: `تمت إضافة أموال إلى محفظتك: تمت إضافة إيداع بقيمة ${amount} دولار إلى حسابك.`
// });
// // Function to get educational material published message
// const getEducationalMaterialPublishedMessage = () => ({
// en: 'Learn and Grow! Expand your knowledge with new educational material in our Academy.',
// ar: 'تعلم ونم! وسع معرفتك من خلال المواد التعليمية الجديدة في أكاديميتنا.'
// });
// // Function to get investment opportunity cancelled message
// const investmentOpportunityCancelled = investmentName => ({
// en: `Investment Opportunity Cancelled: ${investmentName} has been cancelled.`,
// ar: `تم إلغاء فرصة الاستثمار: ${investmentName} قد تم إلغاؤه.`
// });
// Function to get investor qualification upgrade message
// const investorQualificationUpgrade = () => ({
// en: 'Congratulations, Investor! Your Investor Qualification has been upgraded. Enjoy enhanced benefits!',
// ar: 'تهانينا، مستثمر! تم ترقية مؤهلاتك كمستثمر. استمتع بالمزايا المعززة!'
// });
// // Function to get KYC status approved message
// const kycStatusApproved = () => ({
// en: 'KYC Approved: Great news! Your KYC verification is successful.',
// ar: 'تمت الموافقة على KYC: أخبار رائعة! تم التحقق من KYC بنجاح.'
// });
// Function to get KYC status declined message
// const kycStatusDeclined = () => ({
// en: 'KYC Update: Your KYC status requires attention. Please pass the verification again.',
// ar: 'تحديث KYC: حالة KYC الخاصة بك تتطلب انتباهاً. يرجى إعادة إجراء التحقق.'
// });
// // Function to get new investment opportunity message
// const newInvestmentOpportunity = () => ({
// en: 'Discover New Opportunities! A fresh investment opportunity awaits you. Dive in now!',
// ar: 'اكتشف الفرص الجديدة! تنتظرك فرصة استثمارية جديدة. ابدأ الآن!'
// });
// // Function to get subscription investment closed message
// const subscriptionInvestmentClosed = () => ({
// en: 'Investment Update: Your investment opportunity has just closed.',
// ar: 'تحديث الاستثمار: لقد تم إغلاق فرصة استثمارك للتو.'
// });
// // Function to get subscription investment updated message
// const subscriptionInvestmentUpdated = () => ({
// en: 'Investment Status Alert: Your investment subscription details have been updated.',
// ar: 'تنبيه حالة الاستثمار: تمت تحديث تفاصيل اشتراكك في الاستثمار.'
// });
// Function to get withdrawal completed message
// const withdrawalCompleted = async () => {
// title = 'Withdrawal Complete';
// message = "Withdrawal Complete: Your withdrawal is processed. Check your bank for funds!',"
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// const investmentOpportunityCancelled = async (investmentName, playerIds = [], imageUrl, additionalData = {}) => {
// title = `Investment Opportunity Cancellation Notification`,
// message = `Investment Opportunity Cancelled: ${investmentName} has been cancelled.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send new investment opportunity notification
// const sendNewInvestmentOpportunityNotification = async (investmentName, manualDate, manualTime, expectedReturn, playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'New investment opportunity';
// const message = `${investmentName} launching on ${manualDate} at ${manualTime} with an expected return of ${expectedReturn}%. Tap for more details.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send investment open notification
// const sendInvestmentOpenNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = `${investmentName} is LIVE`;
// const message = `${investmentName} is now open for investment! Tap for more details.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send investment fully subscribed notification
// const sendInvestmentFullySubscribedNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = `${investmentName} fully subscribed`;
// const message = `${investmentName} has been fully subscribed and is now closed to new investors.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send deposit received notification
// const sendDepositReceivedNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'Deposit received';
// const message = 'A new deposit has been made into your Tanami wallet. Explore exclusive investment opportunities only at Tanami.';
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// const sendFawateerDepositReceivedNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'Deposit received';
// const message = 'A new deposit has been made into your Tanami wallet. Explore exclusive investment opportunities only at Tanami.';
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send distribution notice notification
// const sendDistributionNoticeNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'Distribution notice';
// const message = `New distribution received regarding your investment in ${investmentName}. Tap for more details.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send distribution notice notification
// const sendExitedNoticeNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = `${investmentName} has exited`;
// const message = `Big news! We've successfully exited our investment in ${investmentName}. Tap for more details.`;
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send KYC approved notification
// const sendKYCApprovedNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'You\'re verified!';
// const message = 'KYC approved - You\'re all set to start investing! Tap to explore the latest exclusive opportunities available.';
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
// // Function to send investor status upgrade notification
// const sendInvestorStatusUpgradeNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
// const title = 'You\'ve been upgraded!';
// const message = 'Congrats! You can now enjoy investing with no limits! Tap to explore the latest exclusive opportunities available.';
// return await sendNotification(title, message, playerIds, imageUrl, additionalData);
// };
const sendLocalizedNotifications = async (
playerIds = { playerIdsForArebic: [], playerIdsEnglish: [] },
title,
message,
imageUrl = '',
additionalData = {}
) => {
return await Promise.all([
playerIds.playerIdsEnglish?.length > 0 &&
sendNotification(
title,
message,
playerIds.playerIdsEnglish,
imageUrl,
additionalData
),
playerIds.playerIdsForArebic?.length > 0 &&
sendNotification(
{ en: title.ar, ar: title.ar }, // Correctly set Arabic title
{ en: message.ar, ar: message.ar }, // Correctly set Arabic message
playerIds.playerIdsForArebic,
imageUrl,
additionalData
),
]);
};
const _withdrawalCompleted = async (
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Withdrawal Complete', ar: 'اكتمال السحب' };
const message = {
en: 'Withdrawal Complete: Your withdrawal is processed. Check your bank for funds.',
ar: 'اكتمال السحب: تم معالجة السحب الخاص بك. تحقق من البنك للحصول على الأموال.',
};
return await sendNotification(
title,
message,
playerIds,
imageUrl,
additionalData
);
};
const withdrawalCompleted = async (
playerIds = {
playerIdsForArebic: [],
playerIdsEnglish: [],
},
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Withdrawal Complete', ar: 'اكتمال السحب' };
const message = {
en: 'Withdrawal Complete: Your withdrawal is processed. Check your bank for funds.',
ar: 'اكتمال السحب: تم معالجة السحب الخاص بك. تحقق من البنك للحصول على الأموال.',
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const _investmentOpportunityCancelled = async (
investmentName,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = {
en: 'Investment Opportunity Cancellation Notification',
ar: 'إشعار إلغاء فرصة استثمار',
};
const message = {
en: `Investment Opportunity Cancelled: ${investmentName} has been cancelled.`,
ar: `تم إلغاء فرصة الاستثمار: "${investmentName}".`,
};
return await sendNotification(
title,
message,
playerIds,
imageUrl,
additionalData
);
};
const investmentOpportunityCancelled = async (
investmentName,
playerIds = {
playerIdsForArebic: [],
playerIdsEnglish: [],
},
imageUrl = '',
additionalData = {}
) => {
const title = {
en: 'Investment Opportunity Cancellation Notification',
ar: 'إشعار إلغاء فرصة استثمار',
};
const message = {
en: `Investment Opportunity Cancelled: ${investmentName} has been cancelled.`,
ar: `تم إلغاء فرصة الاستثمار: ${investmentName}.`,
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendNewInvestmentOpportunityNotification = async (
investmentName,
manualDate,
manualTime,
expectedReturn,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = {
en: 'New investment opportunity',
ar: 'فرصة استثمارية جديدة',
};
const message = {
en: `${investmentName} launching on ${manualDate} at ${manualTime} with an expected return of ${expectedReturn}%. Tap for more details.`,
ar: `${investmentName} سيتم إطلاقها في ${manualDate} في ${manualTime} مع عائد متوقع بنسبة ${expectedReturn}%. اضغط لمزيد من التفاصيل.`,
};
return await sendNotification(
title,
message,
playerIds,
imageUrl,
additionalData
);
};
const sendInvestmentOpenNotification = async (
investmentName,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = {
en: `${investmentName} is LIVE`,
ar: `${investmentName} مفتوح الآن`,
};
const message = {
en: `${investmentName} is now open for investment! Tap for more details.`,
ar: `${investmentName} مفتوح الآن للاستثمار! اضغط لمزيد من التفاصيل.`,
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendInvestmentFullySubscribedNotification = async (
investmentName,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = {
en: `${investmentName} fully subscribed`,
ar: `${investmentName} تم الاشتراك فيه بالكامل`,
};
const message = {
en: `${investmentName} has been fully subscribed and is now closed to new investors.`,
ar: `${investmentName} تم الاشتراك فيه بالكامل وهو مغلق الآن للمستثمرين الجدد.`,
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendDepositReceivedNotification = async (
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Deposit received', ar: 'تم استلام الإيداع' };
const message = {
en: 'A new deposit has been made into your Tanami wallet. Explore exclusive investment opportunities only at Tanami.',
ar: 'تم إجراء إيداع جديد في محفظة تنامي الخاصة بك. استكشف فرص الاستثمار الحصرية فقط مع تنامي.',
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendFawateerDepositReceivedNotification = async (
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Deposit received', ar: 'تم استلام الإيداع' };
const message = {
en: 'A new deposit has been made into your Tanami wallet. Explore exclusive investment opportunities only at Tanami.',
ar: 'تم إجراء إيداع جديد في محفظة تنامي الخاصة بك. استكشف فرص الاستثمار الحصرية فقط مع تنامي.',
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendDistributionNoticeNotification = async (
investmentName,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Distribution notice', ar: 'إشعار التوزيع' };
const message = {
en: `New distribution received regarding your investment in ${investmentName}.Tap for more details.`,
ar: `تم استلام توزيع جديد يتعلق باستثمارك في ${investmentName}. انقر لمزيد من التفاصيل.`,
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
// return await sendNotification(
// title,
// message,
// playerIds,
// imageUrl,
// additionalData
// );
};
const _sendExitedNoticeNotification = async (
investmentName,
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = {
en: `${investmentName} has exited`,
ar: `${investmentName} تم الخروج منه`,
};
const message = {
en: `We've successfully exited our investment in ${investmentName}.`,
ar: `لقد نجحنا في الخروج من استثمارنا في ${investmentName}.`,
};
return await sendNotification(
title,
message,
playerIds,
imageUrl,
additionalData
);
};
const sendExitedNoticeNotification = async (
investmentName,
playerIds = {
playerIdsForArebic: [],
playerIdsEnglish: [],
},
imageUrl = '',
additionalData = {}
) => {
const title = {
en: `${investmentName} has exited`,
ar: `${investmentName} تم الخروج منه`,
};
const message = {
en: `We've successfully exited our investment in ${investmentName}.Tap for more details.`,
ar: `لقد نجحنا في إنهاء استثمارنا في ${investmentName}. انقر لمزيد من التفاصيل.`,
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendKYCApprovedNotification = async (
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: "You're verified!", ar: 'تم التحقق منك!' };
const message = {
en: "You're all set to start investing! Tap to explore the latest exclusive opportunities available.",
ar: 'أنت جاهز تمامًا لبدء الاستثمار! انقر لاستكشاف أحدث الفرص الحصرية المتاحة',
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
const sendInvestorStatusUpgradeNotification = async (
playerIds = [],
imageUrl = '',
additionalData = {}
) => {
const title = { en: 'Investor status upgrade', ar: 'ترقية حالة المستثم' };
const message = {
en: 'You can now enjoy investing with no limits! Tap to explore the latest exclusive opportunities available.',
ar: 'مبروك! يمكنك الآن الاستمتاع بالاستثمار بلا حدود!',
};
return await sendLocalizedNotifications(
playerIds,
title,
message,
imageUrl,
additionalData
);
};
// Exporting functions
module.exports = {
// getDepositReceivedMessage,
// getEducationalMaterialPublishedMessage,
investmentOpportunityCancelled,
// investorQualificationUpgrade,
// kycStatusApproved,
// kycStatusDeclined,
// newInvestmentOpportunity,
// subscriptionInvestmentClosed,
// subscriptionInvestmentUpdated,
withdrawalCompleted,
sendNewInvestmentOpportunityNotification,
sendInvestmentOpenNotification,
sendInvestmentFullySubscribedNotification,
sendDepositReceivedNotification,
sendDistributionNoticeNotification,
sendKYCApprovedNotification,
sendInvestorStatusUpgradeNotification,
sendExitedNoticeNotification,
sendFawateerDepositReceivedNotification,
};

View File

@@ -0,0 +1,15 @@
const config = require('../../config/config');
const generateOTP = () => {
var digits = '0123456789';
let OTP = '';
if (config.byPassOTP) {
OTP = '123456';
} else {
for (let i = 0; i < 6; i++) {
OTP += digits[Math.floor(Math.random() * 10)];
}
}
return OTP;
};
module.exports = generateOTP;

View File

@@ -0,0 +1,19 @@
// Function to generate a random password with a specified length
function generatePassword(length) {
const charset =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
password += charset.charAt(randomIndex);
}
return password;
}
// Generate an 8-character password
// const password = generatePassword(8);
// console.log("Generated Password:", password);
module.exports = generatePassword;

View File

@@ -0,0 +1,57 @@
const moment = require('moment');
class TransactionNumberGenerator {
constructor() {
this.timestampMap = new Map();
this.lock = { isLocked: false };
}
_acquireLock() {
while (this.lock.isLocked) {
/* Busy wait */
}
this.lock.isLocked = true;
}
_releaseLock() {
this.lock.isLocked = false;
}
// Convert date and time to Unix timestamp, handling 'YYYY-MM-DD' and 'YYYY-MM-DD HH:mm:ss'
_getUnixTimestamp(dateTime) {
if (dateTime instanceof Date) {
// Convert Date object to 'YYYY-MM-DD HH:mm:ss'
dateTime = moment(dateTime).format('YYYY-MM-DD HH:mm:ss');
}
// Determine whether the input includes time
const format = dateTime.includes(' ')
? 'YYYY-MM-DD HH:mm:ss'
: 'YYYY-MM-DD';
// Parse based on the format and return Unix timestamp
return moment(dateTime, format).unix();
}
generateUniqueTransactionNumber(dateTime = new Date()) {
this._acquireLock();
const timestamp = this._getUnixTimestamp(dateTime);
if (!this.timestampMap.has(timestamp)) {
this.timestampMap.set(timestamp, 0);
}
let counter = this.timestampMap.get(timestamp) + 1;
this.timestampMap.set(timestamp, counter);
this._releaseLock();
// Create unique number with timestamp and counter
const uniqueNumber = ((timestamp % 10000000000) * 1000 + counter)
.toString()
.padStart(11, '0');
return uniqueNumber;
}
}
module.exports = TransactionNumberGenerator;

View File

@@ -0,0 +1,30 @@
class uniqueNumberGeneratorInvestor {
constructor(existingNumbers) {
this.existingNumbers = existingNumbers; // Store existing numbers
this.currentYear = new Date().getFullYear().toString().slice(-2); // Get last two digits of the current year
}
generate() {
let uniqueNumber;
let attempts = 0; // Track the number of attempts to find a unique number
while (attempts < 10000) {
// Limit attempts to avoid infinite loops
const randomPart = Math.floor(Math.random() * 100000); // Generate a random number
uniqueNumber = this.currentYear + randomPart.toString().padStart(5, '0'); // Create the unique number
// Check if the generated number is unique
if (!this.existingNumbers.includes(uniqueNumber)) {
this.existingNumbers.push(uniqueNumber); // Add to existing numbers to avoid future collisions
return uniqueNumber; // Return the unique number found
}
attempts++; // Increment attempts
}
throw new Error(
'Unable to generate a unique number after 10,000 attempts.'
); // Error if unable to generate
}
}
module.exports = uniqueNumberGeneratorInvestor;

View File

@@ -0,0 +1,12 @@
const yup = require('yup');
const addParticipant = yup.object().shape({
newParticipantId: yup.number().required(),
chatId: yup.string().required(),
});
const removeParticipant = yup.object().shape({
participantId: yup.number().required(),
chatId: yup.string().required(),
});
module.exports = { addParticipant, removeParticipant };

View File

@@ -0,0 +1,42 @@
const yup = require('yup');
const chat = yup.object().shape({
receiver_xid: yup.number().required(),
chat_name: yup.string().required(),
is_group: yup.boolean().required(),
group_type: yup.string().optional(),
max_participants: yup.number().optional(),
description: yup.string().optional(),
// profile_photo: yup
// .string()
// .of(
// yup.mixed().test("isFile", "Invalid file format", (value) => {
// if (!value) return true; // No attachment provided is allowed.
// return isValidFileSize(value); // Implement custom validation for file size here.
// })
// )
// .default([])
// .optional()
// .notRequired(),
});
const message = yup.object().shape({
chat_xid: yup.number().required(),
message: yup.string().max(1000).optional(),
message_type: yup.string().optional(),
sender_xid: yup.number().optional(),
receiver_xid: yup.number().optional(),
// attachment: yup
// .array()
// .of(
// yup.mixed().test("isFile", "Invalid file format", (value) => {
// if (!value) return true; // No attachment provided is allowed.
// return isValidFileSize(value); // Implement custom validation for file size here.
// })
// )
// .default([])
// .optional()
// .notRequired(),
});
module.exports = { chat, message };

4
src/validation/index.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
chatValidations: require("./chat.validation"),
addParticipantsValidations: require("./addParticipant.validation"),
}