https://github.com/yanamlnk/irc-server
Internet Relay Chat: group project to create chat web application using socket.io
https://github.com/yanamlnk/irc-server
expressjs jest mongodb mongoose nodejs react socket-io
Last synced: 2 months ago
JSON representation
Internet Relay Chat: group project to create chat web application using socket.io
- Host: GitHub
- URL: https://github.com/yanamlnk/irc-server
- Owner: yanamlnk
- Created: 2025-01-06T13:59:00.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-18T10:59:10.000Z (over 1 year ago)
- Last Synced: 2026-01-03T13:19:57.646Z (6 months ago)
- Topics: expressjs, jest, mongodb, mongoose, nodejs, react, socket-io
- Language: JavaScript
- Homepage:
- Size: 5.31 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# irc-server
1. [Installation](#installation)
2. [Start the server](#start-the-server)
3. [How to use chat](#how-to-use)
4. [Technologies used](#technologies-used)
5. [Folder structure](#folder-structure)
6. [Backend sockets](#backend-sockets)
## Installation
- Clone repo using `git clone` and link to the repo (http/ssh)
### Set up backend
1. open [MongoDB](https://www.mongodb.com/) and login
2. in **Project**, create new project by clickin gon the button on the right
3. Project name: `irc-chat`
4. got to **Clusters** and build a cluster:
- free
- name: irc-chat
- provider: AWS
- your region
- click on create deployment
5. create username and set a passrowd that you want
6. choose connection method: **Drivers**
7. Choose driver Node.js
8. in Terminal run command `npm install mongodb`
9. in your cluster, click on **Connect**
10. copy a link for connection with the name of the user you have created in the link.
11. go to the root of the project.
12. got to the backend folder `cd backend`
13. there, create environment config file: `touch .env`
14. in this file write down the following (using `vim`, `nano`, or VSCode):
- `PORT=3001`
- `MONGO_URI=` and paste the link taken from MongiDb connection. Don't forget to replace `` with your password you have set up for this user
- `DB_NAME=irc-chat`
15. Now run command `npm install`
**Great, backend is set up!** 💃
### Set up frontend
Don't worry, not so hard as with backend!
1. return to the root of the project `cd ..`
2. go to the frontend dorectory: `cd frontend`
3. run `npm install`
**You are a star! Everything is ready to start**💫
## Start the server
1. Open backend folder and run `npm run dev`
2. Open frintend folder and run `npm start`
Wow that's all!
Chat is available on `http://localhost:3000`
### Psst, you want to run some tests?
- go to backend and run `npm test`. Or from root run using one command:
```
cd ./backend && npm test
```
- test report available on the terminal and on the folder `coverage` located in `backend` directory. It is called `test-report.html`
- coverage report is available in `coverage/lcov-report/index.html`
## How to use
**General**
- user can connect using any name. If name is already in use - the new name will be generated by principal `chosen_name + 4 random digits`
- if user don't want to choose name, he can use connect without a chosen name, and like that, the name will be chosen generically
- with connection user is automatically connected to the channel called `#general`. User can't quit `#general` channel.
- all channel names start with `#`
- channel messages are persistant, private - not
- connection is one-time. It is impossible to log-in into the exiting user
**Commands**
- `/nick nickname`: change the nickname of the user visible on the channel. Per channel there can be different nickname. If nickname is already taken, new one will be generated automatically by using the same principle as with name
- `/list [string]`: list the available channels from the server. If string is specified, only displays
those whose name contains the string.
- `/create channel`: create a channel with the specified name. Even if specified name was without `#` on the beginning, channel will be created with `#`. User who created the channel will be automatically added as a joined user
- `/delete channel`: delete the channel with the specified name. Anyone can delete channel. Even if you are not a part of the channel. If somebody is currently in a channel that was just deleted, he will receive a notification about deletion and will be redirected to the `#general`
- `/join channel`: join the specified channel. other users will receive a notification from the bot about joined user
- `/quit channel`: quit the specified channel. if users tries to quit channel that he is not part of, on the top there will be an error `Channel not found`
- `/users`: list the users currently in the channel
- `/msg nickname message`: send a private message to the specified nickname. More about private messages below.
**Private and Channel messages**
- Channel messages are persistent and can be visible by anybody. History of previous messages will be visible to new users
- Private messages are not persistent. They will dissapear as soon as user changes the channel.
- You can't send private messages to the user that is not online. The error will pop up telling that user is not online
- You can send messages only to the users that are in the channel
**Interface**
- Interface gives user a possibility to see all the channel joined on the left, all users on the channel on the right, and message space on the center
- You can click between channels to change the view
- public messages are dark, private - white
## Technologies used
- Database: MongoDB
- Backend: Node.js, Express, Socket.io, Mongoose
- Frontend: React
- Test: Jest, MongoMemoryServer
- Package manager: npm
## Folder structure
```
├── backend/
│ ├── models/
│ ├── services/
│ ├── sockets/
│ └── src/
└── frontend/
├── public/
└── src/
└── components/
```
2 main folders: `backend` and `frontend`
### Backend:
- `models` to create models schema
- `services` to create methods that "communicate" with database
- `sockets` for sockets
> `services` and `sockets` are divided by 3 main categories: Channel, User and Message
- `src` for server, socket and database connection
- `tests` for tests
### Frontend:
- `src` has App.js and `components` folder with all necessary components
## Backend sockets
1. [Channel socket](#channel-socket)
1. [getChannelId](#getchanneid)
2. [listChannelsOfUser](#listchannelsofuser)
3. [listUsersInChannel](#listusersinchannel)
4. [joinChannel](#joinchannel)
5. [createChannel](#createchannel)
6. [quitChannel](#quitchannel)
7. [renameChannel](#renamechannel)
8. [deleteChannel](#deletechannel)
9. [listChannels](#listchannels)
10. [Special Notes](#special-notes)
2. [User socket](#user-socket)
1. [chooseName](#choosename)
2. [listUsers](#listusers)
3. [getNickname](#getnickname)
4. [Special Notes](#special-notes-1)
3. [Message socket](#message-socket)
1. [getChannelMessages](#getchannelmessages)
2. [channelMessage](#channelmessage)
3. [privateMessage](#privatemessage)
4. [Special Notes](#special-notes-2)
## Channel socket
## getChannelId
**Input Parameters:**
- `channelName` (string): Name of the channel
**Returns:**
- Success: `{ success: true, channelId: string }`
- Error: `{ success: false, message: string }`
**Emits:** None
## listChannelsOfUser
**Input Parameters:**
- `userID` (string): ID of the user
**Returns:**
- Success: `{ success: true, channels: Array<{ channel_id: string, name: string }> }`
- Error: `{ success: false, message: string }`
**Emits:** None
## listUsersInChannel
**Input Parameters:**
- `channelId` (string): ID of the channel
**Returns:**
- Success: `{ success: true, users: Array<{ user_id: string, nickname: string }> }`
- Error: `{ success: false, message: string }`
**Emits:** None
## joinChannel
**Input Parameters:**
- Object containing:
- `userId` (string): ID of the user joining
- `channelName` (string): Name of the channel to join
**Returns:**
- Success: `{ success: true, channel: { channel_id: string, name: string, users: Array<{ user_id: string, name: string }> } }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'userJoinedChannel'
- Data: `{ userId: string, channelId: string, channelName: string, userName: string }`
- Target: All users in the channel
## createChannel
**Input Parameters:**
- Object containing:
- `userId` (string): ID of the user creating the channel
- `name` (string): Name for the new channel
**Returns:**
- Success: `{ success: true, channel: { channel_id: string, name: string, users: Array<{ user_id: string, name: string }> } }`
- Error: `{ success: false, message: string }`
**Emits:** None
## quitChannel
**Input Parameters:**
- Object containing:
- `userId` (string): ID of the user leaving
- `channelId` (string): ID of the channel
**Returns:**
- Success: `{ success: true, channel: { channel_id: string, name: string, users: Array<{ user_id: string, name: string }>, deletedUserNickname: string } }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'userLeftChannel'
- Data: `{ userId: string, channelId: string, userName: string }`
- Target: All users in the channel
## renameChannel
**Input Parameters:**
- Object containing:
- `channelId` (string): ID of the channel
- `newName` (string): New name for the channel
**Returns:**
- Success: `{ success: true, channel: { channel_id: string, name: string, old_name: string, users: Array<{ user_id: string, name: string }> } }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'channelRenamed'
- Data: `{ channel: { channel_id: string, name: string, old_name: string, users: Array<{ user_id: string, name: string }> } }`
- Target: All users in the channel
## deleteChannel
**Input Parameters:**
- `channelName` (string): Name of the channel to delete
**Returns:**
- Success: `{ success: true, channel: { channelId: string, name: string } }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'channelDeleted'
- Data: `{ channel: { channelId: string, name: string } }`
- Target: All users in the channel
## listChannels
**Input Parameters:**
- `searchString` (string, optional): Search string to filter channels
**Returns:**
- Success: `{ success: true, channels: Array<{ channel_id: string, name: string }> }`
- Error: `{ success: false, message: string }`
**Emits:** None
## Special Notes
1. Channel '#general' has special restrictions:
- Cannot be quit by users
- Cannot be renamed
2. When joining a channel:
- If a user's nickname is already taken, a random 4-digit number will be appended
- Users automatically join the socket room with the channel's ID
3. Error Handling:
- All operations return `{ success: false, message: string }` on error
- Common errors include: channel not found, user not found, invalid IDs
- All errors are logged server-side
## User Socket
## chooseName
**Input Parameters:**
- `name` (string): Desired username
**Returns:**
- Success: `{ success: true, user: { id: string, name: string } }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'userJoinedChannel'
- Data: `{ userId: string, channelId: string, channelName: string, userName: string }`
- Target: All users in the #general channel
**Special Behavior:**
- Automatically adds user to #general channel
- Sets socket.userId and socket.userName
- If username is taken, appends random 4-digit number
- Joins the socket to the #general channel room
## changeName
**Input Parameters:**
- Object containing:
- `userId` (string): ID of the user
- `newName` (string): Desired new nickname
- `channelId` (string): ID of the channel where name is being changed
**Returns:**
- Success: `{ success: true, newName: string }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'userChangedName'
- Data: `{ userId: string, channelId: string, newName: string }`
- Target: All other users in the channel
**Special Behavior:**
- If new nickname is taken in the channel, appends random 4-digit number
- Nickname change is channel-specific
- If new nickname is same as current nickname, returns without changes
## getNickname
**Input Parameters:**
- Object containing:
- `userId` (string): ID of the user
- `channelId` (string): ID of the channel
**Returns:**
- Success: `{ success: true, nickname: string }`
- Error: `{ success: false, message: string }`
**Emits:** None
# Special Notes
1. Nickname System:
- Nicknames are channel-specific
- A user can have different nicknames in different channels
- When a nickname collision occurs, a random 4-digit number is appended
2. #general Channel:
- All new users are automatically joined to #general upon creation
- User's initial nickname in #general matches their username
3. Error Handling:
- All operations return `{ success: false, message: string }` on error
- Common errors include:
- Username/nickname already taken
- User not found in channel
- Invalid user or channel IDs
- All errors are logged server-side
4. Socket Properties:
- After successful user creation, the socket stores:
- socket.userId: User's ID
- socket.userName: User's name
## Message socket
## getChannelMessages
**Input Parameters:**
- Object containing:
- `channelId` (string): ID of the channel to fetch messages from
**Returns:**
- Success: `{ success: true, messages: Array<{ id: string, text: string, sender: string, timestamp: Date, channelId: string }> }`
- Error: `{ success: false, message: string }`
**Emits:** None
**Special Behavior:**
- Returns messages sorted by timestamp in ascending order
- Only returns channel messages (recipientType: 'Channel')
## channelMessage
**Input Parameters:**
- Object containing:
- `text` (string): Message content
- `channelId` (string): ID of the channel
- `senderMessage` (string): Sender's name
**Returns:**
- Success: `{ success: true, message: MessageObject }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'newMessage'
- Data: `{ ...messageObject, isSent: true }`
- Target: All other users in the channel
**Special Behavior:**
- Validates that sender has chosen a name
- Verifies sender is a member of the channel
- Saves message to database before broadcasting
## privateMessage
**Input Parameters:**
- Object containing:
- `text` (string): Message content
- `recipientName` (string): Recipient's nickname in the channel
- `channelId` (string): ID of the channel context
- `senderMessage` (string): Sender's name
**Returns:**
- Success: `{ success: true, message: MessageObject }`
- Error: `{ success: false, message: string }`
**Emits:**
- Event: 'newPrivateMessage'
- Data: `{ ...messageObject, isSent: true }`
- Target: Both sender and recipient sockets
## Special Notes
1. Message Validation:
- Message text cannot be empty
- Sender and recipient are required
- Channel ID is required
- For private messages, recipient name is required
2. Private Messaging Requirements:
- Both users must be in the same channel
- Recipient must be online
- Recipient must be found in the channel
- Sender must have chosen a name
- Messages are sent in the context of a specific channel
3. Channel Message Structure:
- id: Unique message identifier
- text: Message content
- sender: Sender's name
- timestamp: Message creation time
- channelId: Channel context
- recipientType: 'Channel' for public, 'Private' for private messages
4. Error Handling:
- All operations return `{ success: false, message: string }` on error
- Common errors include:
- Empty message text
- Missing recipient
- User not in channel
- Recipient not online
- Channel not found
- All errors are logged server-side
5. Socket Room Management:
- System verifies sender and recipient presence in channel using socket.io's native room functionality
- Messages are broadcast only to appropriate recipients based on message type