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
23 changes: 23 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test

on:
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"license": "GPL-3.0",
"devDependencies": {
"@mate-academy/eslint-config": "latest",
"@mate-academy/scripts": "^1.8.6",
"@mate-academy/scripts": "^2.1.1",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-node": "^11.1.0",
Expand Down
Binary file added src.zip
Binary file not shown.
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;
}
Comment on lines +19 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Potential issue: The 'createRoom' function does not check if a room with the same name already exists. If the requirements specify that room names must be unique, you should add a check to prevent duplicate room names. If uniqueness is not required, you can ignore this comment.


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,
};
50 changes: 50 additions & 0 deletions src/models/userManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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 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,
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);
});
Loading