Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
'use strict';
/* eslint-disable no-console */

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const path = require('path');
const { setupSocketHandlers } = require('./socket/socketHandlers');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

const PORT = process.env.PORT || 3000;

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

setupSocketHandlers(io);

server.listen(PORT, () => {
console.log(`Server listening on port:${PORT}`);
});
70 changes: 70 additions & 0 deletions src/models/roomManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const rooms = {
general: { name: 'General', messages: [] },
tech: { name: 'Technology', messages: [] },
random: { name: 'Random', messages: [] },
};

function getAllRooms() {
return rooms;
}

function getRoomsList() {
return Object.entries(rooms).map(([id, data]) => ({ id, name: data.name }));
}

function getRoom(roomId) {
return rooms[roomId];
}

function createRoom(roomName) {
const roomId = `room_${Date.now()}`;

rooms[roomId] = { name: roomName, messages: [] };

return roomId;
}

function renameRoom(roomId, newName) {
if (rooms[roomId]) {
rooms[roomId].name = newName;

return true;
}

return false;
}

function deleteRoom(roomId) {
if (rooms[roomId] && roomId !== 'general') {
delete rooms[roomId];

return true;
}

return false;
}
Comment on lines +37 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The deleteRoom function only deletes the room from the rooms object, but does not provide any mechanism to notify or update users who are currently in that room. According to the requirements, when a room is deleted, all users in that room should be notified and their state should be updated. You should coordinate this logic with your socket handlers to ensure users are properly informed and moved if necessary.


function addMessage(roomId, message) {
if (rooms[roomId]) {
rooms[roomId].messages.push(message);

return true;
}

return false;
}

function roomExists(roomId) {
return !!rooms[roomId];
}

module.exports = {
getAllRooms,
getRoomsList,
getRoom,
createRoom,
renameRoom,
deleteRoom,
addMessage,
roomExists,
};
60 changes: 60 additions & 0 deletions src/models/userManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const users = {};

function createUser(socketId, username) {
users[socketId] = { username, currentRoom: null };
}

function getUser(socketId) {
return users[socketId];
}

function getUserRoom(socketId) {
const user = users[socketId];

return user ? user.currentRoom : null;
}

function setUserRoom(socketId, roomId) {
if (users[socketId]) {
users[socketId].currentRoom = roomId;

return true;
}

return false;
}

function getUsersInRoom(roomId) {
return Object.entries(users)
.filter(([_, user]) => user.currentRoom === roomId)
.map(([socketId, user]) => ({
socketId,
username: user.username,
}));
}

function removeUser(socketId) {
const user = users[socketId];

if (user) {
delete users[socketId];

return user;
}

return null;
}

function userExists(socketId) {
return !!users[socketId];
}

module.exports = {
createUser,
getUser,
getUserRoom,
getUsersInRoom,
setUserRoom,
removeUser,
userExists,
};
130 changes: 130 additions & 0 deletions src/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Socket.io Chat</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #2d3748;
}
::-webkit-scrollbar-thumb {
background: #4a5568;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #718096;
}
.system-message {
color: #a0aec0;
font-style: italic;
}
</style>
</head>
<body class="bg-gray-800 text-white font-sans">
<!-- Username Modal Overlay -->
<div
id="username-modal"
class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
>
<div class="bg-gray-700 p-8 rounded-lg shadow-xl text-center">
<h2 class="text-2xl font-bold mb-4">Enter Your Username</h2>
<form id="username-form">
<input
id="username-input"
type="text"
placeholder="Your cool name..."
class="w-full bg-gray-800 border border-gray-600 rounded-md px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
required
/>
<button
type="submit"
class="mt-4 w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md transition duration-300"
>
Enter Chat
</button>
</form>
</div>
</div>

<!-- Main Chat Interface -->
<div id="chat-container" class="hidden h-screen w-screen flex">
<!-- Sidebar for Rooms -->
<div class="w-1/4 bg-gray-900 flex flex-col p-4 border-r border-gray-700">
<div class="flex-shrink-0 mb-4">
<h1 class="text-2xl font-bold">Chat Rooms</h1>
<p class="text-sm text-gray-400">
Welcome, <span id="display-username" class="font-bold"></span>!
</p>
</div>

<div id="room-list" class="flex-grow overflow-y-auto mb-4">
<!-- Room items will be populated by JS -->
</div>

<div class="flex-shrink-0 space-y-2">
<button
id="create-room-btn"
class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md text-sm"
>
Create Room
</button>
<button
id="rename-room-btn"
class="w-full bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded-md text-sm"
>
Rename Current
</button>
<button
id="delete-room-btn"
class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md text-sm"
>
Delete Current
</button>
</div>
</div>

<!-- Main Chat Area -->
<div class="w-3/4 flex flex-col">
<div id="chat-header" class="p-4 bg-gray-700 border-b border-gray-600">
<h2 id="current-room-name" class="text-xl font-semibold">
No room selected
</h2>
</div>
<!-- Messages Display -->
<div
id="messages"
class="flex-grow p-4 overflow-y-auto bg-gray-800 flex flex-col space-y-4"
>
<!-- Messages will be populated by JS -->
</div>

<!-- Message Input Form -->
<div class="p-4 bg-gray-700 border-t border-gray-600">
<form id="message-form" class="flex space-x-4">
<input
id="message-input"
type="text"
placeholder="Type a message..."
autocomplete="off"
class="flex-grow bg-gray-800 border border-gray-600 rounded-md px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<button
type="submit"
class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md"
>
Send
</button>
</form>
</div>
</div>
</div>

<script src="/socket.io/socket.io.js"></script>
<script type="module" src="js/main.js"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions src/public/js/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function initializeAuth(state) {
const usernameModal = document.getElementById('username-modal');
const usernameForm = document.getElementById('username-form');
const usernameInput = document.getElementById('username-input');
const displayUsername = document.getElementById('display-username');
const chatContainer = document.getElementById('chat-container');

if (state.username) {
showChatInterface();
}

usernameForm.addEventListener('submit', (e) => {
e.preventDefault();

const newUsername = usernameInput.value.trim();

if (newUsername) {
state.username = newUsername;
window.localStorage.setItem('chat_username', state.username);
showChatInterface();
}
});

function showChatInterface() {
displayUsername.textContent = state.username;
usernameModal.classList.add('hidden');
chatContainer.classList.remove('hidden');
chatContainer.classList.add('flex');
state.socket.emit('set username', state.username);
}
}

export { initializeAuth };
17 changes: 17 additions & 0 deletions src/public/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { initializeAuth } from './auth.js';
import { initializeSocket } from './socket.js';
import { initializeUI } from './ui.js';

document.addEventListener('DOMContentLoaded', () => {
const socket = window.io();

const state = {
socket,
username: window.localStorage.getItem('chat_username'),
currentRoomId: null,
};

initializeAuth(state);
initializeSocket(state);
initializeUI(state);
});
37 changes: 37 additions & 0 deletions src/public/js/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function appendMessage({ author, text, time }, currentUsername) {
const messages = document.getElementById('messages');
const messageElement = document.createElement('div');
const isMyMessage = author === currentUsername;
const formattedTime = new Date(time).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});

messageElement.className = `flex flex-col ${isMyMessage ? 'items-end' : 'items-start'}`;

messageElement.innerHTML = `
<div class="max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${isMyMessage ? 'bg-indigo-500 rounded-br-none' : 'bg-gray-600 rounded-bl-none'}">
<div class="flex items-baseline space-x-2">
<p class="font-bold text-sm ${isMyMessage ? 'text-white' : 'text-blue-300'}">${isMyMessage ? 'You' : author}</p>
<p class="text-xs text-gray-300">${formattedTime}</p>
</div>
<p class="text-white">${text}</p>
</div>
`;

messages.appendChild(messageElement);
messages.scrollTop = messages.scrollHeight;
}

function appendSystemMessage(text) {
const messages = document.getElementById('messages');
const systemMessageElement = document.createElement('div');

systemMessageElement.className = 'text-center my-2';
systemMessageElement.innerHTML = `<p class="system-message text-sm">${text}</p>`;

messages.appendChild(systemMessageElement);
messages.scrollTop = messages.scrollHeight;
}

export { appendMessage, appendSystemMessage };
Loading