diff --git a/src/index.js b/src/index.js
index ad9a93a7c..779eb0d69 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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}`);
+});
diff --git a/src/models/roomManager.js b/src/models/roomManager.js
new file mode 100644
index 000000000..8875ec5af
--- /dev/null
+++ b/src/models/roomManager.js
@@ -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;
+}
+
+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,
+};
diff --git a/src/models/userManager.js b/src/models/userManager.js
new file mode 100644
index 000000000..8cceace7f
--- /dev/null
+++ b/src/models/userManager.js
@@ -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,
+};
diff --git a/src/public/index.html b/src/public/index.html
new file mode 100644
index 000000000..47b3b7687
--- /dev/null
+++ b/src/public/index.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+ Socket.io Chat
+
+
+
+
+
+
+
+
Enter Your Username
+
+
+
+
+
+
+
+
+
+
Chat Rooms
+
+ Welcome, !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/public/js/auth.js b/src/public/js/auth.js
new file mode 100644
index 000000000..f90eebd90
--- /dev/null
+++ b/src/public/js/auth.js
@@ -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 };
diff --git a/src/public/js/main.js b/src/public/js/main.js
new file mode 100644
index 000000000..3ecafc4c4
--- /dev/null
+++ b/src/public/js/main.js
@@ -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);
+});
diff --git a/src/public/js/messages.js b/src/public/js/messages.js
new file mode 100644
index 000000000..69fb1a353
--- /dev/null
+++ b/src/public/js/messages.js
@@ -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 = `
+
+
+
${isMyMessage ? 'You' : author}
+
${formattedTime}
+
+
${text}
+
+ `;
+
+ 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 = `${text}
`;
+
+ messages.appendChild(systemMessageElement);
+ messages.scrollTop = messages.scrollHeight;
+}
+
+export { appendMessage, appendSystemMessage };
diff --git a/src/public/js/rooms.js b/src/public/js/rooms.js
new file mode 100644
index 000000000..8b4bba885
--- /dev/null
+++ b/src/public/js/rooms.js
@@ -0,0 +1,73 @@
+function renderRooms(rooms, state) {
+ const roomList = document.getElementById('room-list');
+
+ roomList.innerHTML = '';
+
+ rooms.forEach((room) => {
+ const roomElement = document.createElement('button');
+
+ roomElement.textContent = room.name;
+ roomElement.dataset.roomId = room.id;
+
+ roomElement.className = `w-full text-left p-2 rounded-md transition duration-200 ${
+ room.id === state.currentRoomId
+ ? 'bg-indigo-600 font-bold'
+ : 'hover:bg-gray-700'
+ }`;
+ roomElement.addEventListener('click', () => joinRoom(room.id, state));
+ roomList.appendChild(roomElement);
+ });
+}
+
+function joinRoom(roomId, state) {
+ if (roomId === state.currentRoomId) {
+ return;
+ }
+
+ state.socket.emit('join room', roomId, (response) => {
+ if (response.status === 'ok') {
+ state.currentRoomId = roomId;
+ updateCurrentRoomDisplay(roomId);
+ clearAndLoadMessages(response.messages, state.username);
+ updateRoomListStyles(roomId);
+ } else {
+ window.alert(`Error joining room: ${response.message}`);
+ }
+ });
+}
+
+function updateCurrentRoomDisplay(roomId) {
+ const currentRoomName = document.getElementById('current-room-name');
+ const roomList = document.getElementById('room-list');
+ const roomData = Array.from(roomList.children).find(
+ (el) => el.dataset.roomId === roomId,
+ );
+
+ currentRoomName.textContent = roomData ? roomData.textContent : 'Room';
+}
+
+function clearAndLoadMessages(messages, username) {
+ const messagesContainer = document.getElementById('messages');
+
+ messagesContainer.innerHTML = '';
+
+ messages.forEach((message) => {
+ import('./messages.js').then(({ appendMessage }) => {
+ appendMessage(message, username);
+ });
+ });
+}
+
+function updateRoomListStyles(currentRoomId) {
+ const roomList = document.getElementById('room-list');
+
+ Array.from(roomList.children).forEach((el) => {
+ const isActive = el.dataset.roomId === currentRoomId;
+
+ el.classList.toggle('bg-indigo-600', isActive);
+ el.classList.toggle('font-bold', isActive);
+ el.classList.toggle('hover:bg-gray-700', !isActive);
+ });
+}
+
+export { renderRooms, joinRoom };
diff --git a/src/public/js/socket.js b/src/public/js/socket.js
new file mode 100644
index 000000000..be8a4f885
--- /dev/null
+++ b/src/public/js/socket.js
@@ -0,0 +1,28 @@
+import { renderRooms, joinRoom } from './rooms.js';
+import { appendMessage, appendSystemMessage } from './messages.js';
+
+function initializeSocket(state) {
+ state.socket.on('rooms list', (rooms) => {
+ renderRooms(rooms, state);
+
+ if (!state.currentRoomId && rooms.some((r) => r.id === 'general')) {
+ joinRoom('general', state);
+ } else if (!state.currentRoomId && rooms.length > 0) {
+ joinRoom(rooms[0].id, state);
+ }
+ });
+
+ state.socket.on('new message', (message) => {
+ appendMessage(message, state.username);
+ });
+
+ state.socket.on('user joined', (username) => {
+ appendSystemMessage(`${username} has joined the room.`);
+ });
+
+ state.socket.on('user left', (username) => {
+ appendSystemMessage(`${username} has left the room.`);
+ });
+}
+
+export { initializeSocket };
diff --git a/src/public/js/ui.js b/src/public/js/ui.js
new file mode 100644
index 000000000..534df9081
--- /dev/null
+++ b/src/public/js/ui.js
@@ -0,0 +1,69 @@
+import { joinRoom } from './rooms.js';
+
+function initializeUI(state) {
+ const messageForm = document.getElementById('message-form');
+ const messageInput = document.getElementById('message-input');
+ const createRoomBtn = document.getElementById('create-room-btn');
+ const renameRoomBtn = document.getElementById('rename-room-btn');
+ const deleteRoomBtn = document.getElementById('delete-room-btn');
+ const currentRoomName = document.getElementById('current-room-name');
+
+ messageForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+
+ const messageText = messageInput.value.trim();
+
+ if (messageText && state.currentRoomId) {
+ state.socket.emit('send message', messageText);
+ messageInput.value = '';
+ }
+ });
+
+ createRoomBtn.addEventListener('click', () => {
+ const roomName = window.prompt('Enter a name for the new room:');
+
+ if (roomName) {
+ state.socket.emit('create room', roomName, (newRoomId) => {
+ joinRoom(newRoomId, state);
+ });
+ }
+ });
+
+ renameRoomBtn.addEventListener('click', () => {
+ if (!state.currentRoomId || state.currentRoomId === 'general') {
+ window.alert('You cannot rename the General room.');
+
+ return;
+ }
+
+ const newName = window.prompt(
+ `Enter a new name for the room "${currentRoomName.textContent}":`,
+ );
+
+ if (newName) {
+ state.socket.emit('rename room', {
+ roomId: state.currentRoomId,
+ newName,
+ });
+ }
+ });
+
+ deleteRoomBtn.addEventListener('click', () => {
+ if (!state.currentRoomId || state.currentRoomId === 'general') {
+ window.alert('You cannot delete the General room.');
+
+ return;
+ }
+
+ if (
+ window.confirm(
+ `Are you sure you want to delete the room "${currentRoomName.textContent}"?`,
+ )
+ ) {
+ state.socket.emit('delete room', state.currentRoomId);
+ state.currentRoomId = null;
+ }
+ });
+}
+
+export { initializeUI };
diff --git a/src/socket/socketHandlers.js b/src/socket/socketHandlers.js
new file mode 100644
index 000000000..bebe5e03f
--- /dev/null
+++ b/src/socket/socketHandlers.js
@@ -0,0 +1,135 @@
+/* eslint-disable no-console */
+
+const roomManager = require('../models/roomManager');
+const userManager = require('../models/userManager');
+
+function setupSocketHandlers(io) {
+ io.on('connection', (socket) => {
+ console.log(`User connected: ${socket.id}`);
+
+ socket.on('set username', (username) => {
+ userManager.createUser(socket.id, username);
+ console.log(`User ${socket.id} set username to: ${username}`);
+ socket.emit('rooms list', roomManager.getRoomsList());
+ });
+
+ socket.on('join room', (roomId, callback) => {
+ const user = userManager.getUser(socket.id);
+
+ if (!user) {
+ if (callback) {
+ callback({ status: 'error', message: 'User not found' });
+ }
+
+ return;
+ }
+
+ const previousRoomId = user.currentRoom;
+
+ if (previousRoomId) {
+ socket.leave(previousRoomId);
+ socket.to(previousRoomId).emit('user left', user.username);
+ }
+
+ if (!roomManager.roomExists(roomId)) {
+ console.error(`Attempt to join non-existent room: ${roomId}`);
+
+ if (callback) {
+ callback({ status: 'error', message: 'Room not found' });
+ }
+
+ return;
+ }
+
+ socket.join(roomId);
+ userManager.setUserRoom(socket.id, roomId);
+
+ const room = roomManager.getRoom(roomId);
+
+ console.log(
+ `User ${user.username} (${socket.id}) joined room: ${room.name}`,
+ );
+
+ socket.to(roomId).emit('user joined', user.username);
+
+ if (callback) {
+ callback({
+ status: 'ok',
+ messages: room.messages,
+ });
+ }
+ });
+
+ socket.on('send message', (messageText) => {
+ const user = userManager.getUser(socket.id);
+
+ if (!user || !user.currentRoom) {
+ return;
+ }
+
+ const roomId = user.currentRoom;
+ const message = {
+ author: user.username,
+ text: messageText,
+ time: new Date().toISOString(),
+ };
+
+ roomManager.addMessage(roomId, message);
+ io.to(roomId).emit('new message', message);
+ });
+
+ socket.on('create room', (roomName, callback) => {
+ const roomId = roomManager.createRoom(roomName);
+
+ console.log(`Room created: ${roomName} (${roomId})`);
+
+ io.emit('rooms list', roomManager.getRoomsList());
+
+ if (callback) {
+ callback(roomId);
+ }
+ });
+
+ socket.on('rename room', ({ roomId, newName }) => {
+ if (roomManager.renameRoom(roomId, newName)) {
+ console.log(`Room ${roomId} renamed to: ${newName}`);
+ io.emit('rooms list', roomManager.getRoomsList());
+ }
+ });
+
+ socket.on('delete room', (roomId) => {
+ const usersInRoom = roomManager.getUsersInRoom(roomId);
+
+ if (roomManager.deleteRoom(roomId)) {
+ console.log(`Room deleted: ${roomId}`);
+
+ // Notifică utilizatorii din cameră
+ usersInRoom.forEach((userSocketId) => {
+ io.to(userSocketId).emit('room deleted', {
+ roomId,
+ message: 'This room has been deleted.',
+ });
+ });
+
+ // Emite lista actualizată de camere către toți
+ io.emit('rooms list', roomManager.getRoomsList());
+ }
+ });
+
+ socket.on('disconnect', () => {
+ const user = userManager.removeUser(socket.id);
+
+ if (user) {
+ if (user.currentRoom) {
+ socket.to(user.currentRoom).emit('user left', user.username);
+ }
+
+ console.log(`User disconnected: ${user.username} (${socket.id})`);
+ } else {
+ console.log(`User disconnected: ${socket.id}`);
+ }
+ });
+ });
+}
+
+module.exports = { setupSocketHandlers };