CodeHub Branch Setup
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
/public
|
||||||
679
index.html
Normal file
679
index.html
Normal 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
206
logs/combined.log
Normal 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
84
logs/error.log
Normal 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
262
logs/exceptions.log
Normal 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
2464
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal 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
121
src/config/config.js
Normal 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
22
src/config/cors.js
Normal 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
51
src/config/logger.js
Normal 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
44
src/config/morgan.js
Normal 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
10
src/config/tokens.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const tokenTypes = {
|
||||||
|
ACCESS: 'access',
|
||||||
|
REFRESH: 'refresh',
|
||||||
|
RESET_PASSWORD: 'resetPassword',
|
||||||
|
VERIFY_EMAIL: 'verifyEmail',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
tokenTypes,
|
||||||
|
};
|
||||||
479
src/controller/chat.controller.js
Normal file
479
src/controller/chat.controller.js
Normal 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
3
src/controller/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
chatController: require("./chat.controller")
|
||||||
|
}
|
||||||
88
src/index.js
Normal file
88
src/index.js
Normal 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
45
src/middlewares/auth.js
Normal 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
78
src/middlewares/error.js
Normal 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,
|
||||||
|
};
|
||||||
19
src/middlewares/error.middleware.js
Normal file
19
src/middlewares/error.middleware.js
Normal 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 };
|
||||||
95
src/middlewares/fileUpload.js
Normal file
95
src/middlewares/fileUpload.js
Normal 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);
|
||||||
|
};
|
||||||
27
src/middlewares/rateLimiter.js
Normal file
27
src/middlewares/rateLimiter.js
Normal 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,
|
||||||
|
};
|
||||||
81
src/middlewares/storage.js
Normal file
81
src/middlewares/storage.js
Normal 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;
|
||||||
33
src/middlewares/validate.js
Normal file
33
src/middlewares/validate.js
Normal 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;
|
||||||
61
src/middlewares/verifyJWTToken.js
Normal file
61
src/middlewares/verifyJWTToken.js
Normal 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
52
src/models/chat.model.js
Normal 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;
|
||||||
|
};
|
||||||
46
src/models/chatMedia.model.js
Normal file
46
src/models/chatMedia.model.js
Normal 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;
|
||||||
|
};
|
||||||
73
src/models/chatMessage.model.js
Normal file
73
src/models/chatMessage.model.js
Normal 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;
|
||||||
|
};
|
||||||
79
src/models/chatParticipantLink.model.js
Normal file
79
src/models/chatParticipantLink.model.js
Normal 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;
|
||||||
|
};
|
||||||
107
src/models/iam_principal.model.js
Normal file
107
src/models/iam_principal.model.js
Normal 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
53
src/models/index.js
Normal 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;
|
||||||
45
src/routes/chat/admin.routes.js
Normal file
45
src/routes/chat/admin.routes.js
Normal 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
7
src/routes/chat/index.js
Normal 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;
|
||||||
|
|
||||||
60
src/routes/chat/user.routes.js
Normal file
60
src/routes/chat/user.routes.js
Normal 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
6
src/routes/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const appRouter = require("express").Router();
|
||||||
|
|
||||||
|
appRouter.use("/chat", require("./chat"));
|
||||||
|
// appRouter.use("/message", require("./messageRouter"));
|
||||||
|
|
||||||
|
module.exports = appRouter
|
||||||
298
src/services/chat.service.js
Normal file
298
src/services/chat.service.js
Normal 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
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
19
src/services/iam_principal.service.js
Normal file
19
src/services/iam_principal.service.js
Normal 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
4
src/services/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
iamprincipalService: require("./iam_principal.service"),
|
||||||
|
chatService: require("./chat.service")
|
||||||
|
}
|
||||||
45
src/socket/index.js
Normal file
45
src/socket/index.js
Normal 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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
10
src/utils/constant/chat.constant.js
Normal file
10
src/utils/constant/chat.constant.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
GROUP_CHAT: 'group_chat',
|
||||||
|
PUBLIC_GROUP: '1',
|
||||||
|
PRIVATE_GROUP: '2',
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
MEMBER: 'member'
|
||||||
|
}
|
||||||
|
};
|
||||||
29
src/utils/constant/socket.constant.js
Normal file
29
src/utils/constant/socket.constant.js
Normal 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",
|
||||||
|
};
|
||||||
|
|
||||||
41
src/utils/handler/ApiError.handler.js
Normal file
41
src/utils/handler/ApiError.handler.js
Normal 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;
|
||||||
19
src/utils/handler/ApiResponse.handler.js
Normal file
19
src/utils/handler/ApiResponse.handler.js
Normal 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;
|
||||||
11
src/utils/handler/Async.handler.js
Normal file
11
src/utils/handler/Async.handler.js
Normal 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);
|
||||||
|
};
|
||||||
20
src/utils/handler/pick.handler.js
Normal file
20
src/utils/handler/pick.handler.js
Normal 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;
|
||||||
22
src/utils/helper/aesCrypto.helper.js
Normal file
22
src/utils/helper/aesCrypto.helper.js
Normal 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 };
|
||||||
32
src/utils/helper/emailValidator.helper.js
Normal file
32
src/utils/helper/emailValidator.helper.js
Normal 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;
|
||||||
12
src/utils/helper/fileValidator.helper.js
Normal file
12
src/utils/helper/fileValidator.helper.js
Normal 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;
|
||||||
85
src/utils/helper/generateExcel.helper.js
Normal file
85
src/utils/helper/generateExcel.helper.js
Normal 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 };
|
||||||
14
src/utils/helper/maskemail.helper.js
Normal file
14
src/utils/helper/maskemail.helper.js
Normal 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;
|
||||||
495
src/utils/helper/notificationMsg.helper.js
Normal file
495
src/utils/helper/notificationMsg.helper.js
Normal 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,
|
||||||
|
};
|
||||||
15
src/utils/helper/otpGenrator.helper.js
Normal file
15
src/utils/helper/otpGenrator.helper.js
Normal 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;
|
||||||
19
src/utils/helper/passwordGenrator.js
Normal file
19
src/utils/helper/passwordGenrator.js
Normal 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;
|
||||||
57
src/utils/helper/transactionNumberGenrator.helper.js
Normal file
57
src/utils/helper/transactionNumberGenrator.helper.js
Normal 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;
|
||||||
30
src/utils/helper/uniqueNumberGeneratorInvestor.helper.js
Normal file
30
src/utils/helper/uniqueNumberGeneratorInvestor.helper.js
Normal 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;
|
||||||
12
src/validation/addParticipant.validation.js
Normal file
12
src/validation/addParticipant.validation.js
Normal 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 };
|
||||||
42
src/validation/chat.validation.js
Normal file
42
src/validation/chat.validation.js
Normal 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
4
src/validation/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
chatValidations: require("./chat.validation"),
|
||||||
|
addParticipantsValidations: require("./addParticipant.validation"),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user