├── e2e
├── src
│ ├── config.js
│ ├── services
│ │ ├── channel.js
│ │ ├── api.js
│ │ └── uiHelpers.js
│ ├── content
│ │ └── app.css
│ └── app.js
├── package.json
└── index.html
├── src
├── config.js
├── models
│ └── Message.js
├── helpers
│ └── index.js
├── routes
│ └── rooms.js
├── app.js
├── namespaces
│ └── index.js
└── repositories
│ └── channelRepository.js
├── .gitignore
├── package.json
├── LICENSE
├── test
├── helpers.test.js
└── channelRepository.test.js
└── README.md
/e2e/src/config.js:
--------------------------------------------------------------------------------
1 | export const baseUrl = 'http://localhost:3000';
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | exports.channels = [
2 | 'random',
3 | 'pullrequests',
4 | ];
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directories
2 | node_modules/
3 |
4 | # Dependency files
5 | package-lock.json
6 |
7 | # parcel-bundler cache (https://parceljs.org/)
8 | .cache
9 | dist/
10 |
11 | # IDE
12 | .vscode
--------------------------------------------------------------------------------
/src/models/Message.js:
--------------------------------------------------------------------------------
1 | module.exports = class Message {
2 | constructor(userId, text, timestamp) {
3 | this.userId = userId;
4 | this.text = text;
5 | this.timestamp = timestamp;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | exports.isDefine = (input) => !!input;
2 |
3 | const nonAnyWhiteSpace = /\S/;
4 | exports.isNotDefineOrWhiteSpace = (input) => (
5 | !exports.isDefine(input) ||
6 | !nonAnyWhiteSpace.test(input)
7 | );
--------------------------------------------------------------------------------
/e2e/src/services/channel.js:
--------------------------------------------------------------------------------
1 | import ioClient from 'socket.io-client';
2 | // TODO: Discuss to move out of this service.
3 | export const messageFactory = (channel, user) => ({
4 | compose: (text) => ({
5 | channel,
6 | user,
7 | text,
8 | }),
9 | });
10 |
11 | export const createSocket = ({url, channel, options}) =>
12 | ioClient(`${url}/${channel}`, options);
--------------------------------------------------------------------------------
/e2e/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "socket_example_client",
3 | "version": "1.0.0",
4 | "description": "Test that environment works",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start:e2e": "node ../src/app.js | parcel index.html",
9 | "start:client": "parcel index.html"
10 | },
11 | "author": "Jaime Salas",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "parcel": "^1.9.7"
15 | },
16 | "dependencies": {
17 | "socket.io": "^2.1.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/e2e/src/services/api.js:
--------------------------------------------------------------------------------
1 | import { baseUrl } from '../config';
2 |
3 | const apiUrl = `${baseUrl}/api`;
4 |
5 | export const fetchRooms = () => (
6 | fetch(`${apiUrl}/rooms`)
7 | .then((response) => {
8 | if (response.ok) {
9 | return response.json();
10 | }
11 | })
12 | );
13 |
14 | export const canenrollRoom = (roomId, userId) => {
15 | return fetch(`${apiUrl}/rooms/canenroll/${roomId}/user`, {
16 | method: 'POST',
17 | body: JSON.stringify({
18 | userId
19 | }),
20 | mode: 'cors',
21 | headers: { 'Content-Type': 'application/json' }
22 | }).then((response) => {
23 | if (response.ok) {
24 | return response.json();
25 | }
26 | });
27 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-chat-back",
3 | "version": "1.0.0",
4 | "description": "Backend for chat demo",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha --reporter spec",
8 | "start": "node src/app.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/Lemoncode/redux-chat-back.git"
13 | },
14 | "author": "Lemoncode",
15 | "license": "MIT",
16 | "dependencies": {
17 | "body-parser": "^1.18.3",
18 | "cors": "^2.8.4",
19 | "express": "^4.16.3",
20 | "socket.io": "^2.1.1"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/Lemoncode/redux-chat-back/issues"
24 | },
25 | "homepage": "https://github.com/Lemoncode/redux-chat-back#readme",
26 | "devDependencies": {
27 | "chai": "^4.1.2",
28 | "mocha": "^5.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/routes/rooms.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | module.exports = (repository) => (
5 | router
6 | .get('/', async (_, res, next) => {
7 | try {
8 | const rooms = await repository.channelList();
9 | console.log(rooms);
10 | res.json(rooms);
11 | } catch (error) {
12 | next(error);
13 | }
14 | })
15 | .post('/canenroll/:roomId/user', async (req, res, next) => {
16 | try {
17 | const { roomId } = req.params;
18 | const { userId } = req.body;
19 | const exists = await repository.existUser(roomId, userId);
20 | res.json(!exists);
21 | } catch (error) {
22 | next(error);
23 | }
24 | })
25 | );
26 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express'),
2 | app = express(),
3 | bodyParser = require('body-parser'),
4 | cors = require('cors'),
5 | repository = require('./repositories/channelRepository'),
6 | namespaceFactory = require('./namespaces');
7 |
8 | const { channels } = require('./config');
9 |
10 | const initializeChannels = async (io, channels, repository) => {
11 | await Promise.all(
12 | channels.map(c => repository.createChannel(c))
13 | );
14 | channels.map(c => ({
15 | io,
16 | channel: c,
17 | repository
18 | }))
19 | .forEach(i => namespaceFactory(i));
20 | };
21 |
22 | app.use(bodyParser.urlencoded({ extended: true }));
23 | app.use(bodyParser.json());
24 | app.use(cors());
25 |
26 | const roomsRouter = require('./routes/rooms')(repository);
27 | app.use('/api/rooms', roomsRouter);
28 |
29 | const server = require('http').Server(app);
30 | const io = require('socket.io')(server);
31 | server.listen(3000);
32 | // TODO: Create env variable to initialize repository in memomory
33 | initializeChannels(io, channels, repository);
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Lemoncode
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/e2e/src/services/uiHelpers.js:
--------------------------------------------------------------------------------
1 | export const clearMessageText = (element) => element.value = '';
2 |
3 | export const addMessageText = (containerId, tag, message) => {
4 | const messageElement = document.createElement(tag);
5 | messageElement.innerText = `${message.user}: ${message.text}`;
6 | document.getElementById(containerId).appendChild(messageElement);
7 | };
8 |
9 | const createRoomItem = (roomName) => {
10 | const roomItem = document.createElement('li');
11 | roomItem.innerText = roomName;
12 | return roomItem;
13 | };
14 |
15 | export const populateRooms = (rooms, elementId) => {
16 | const roomsContainer = document.getElementById(elementId);
17 | const roomItems = rooms.map(createRoomItem);
18 | roomItems.forEach((ri) => roomsContainer.appendChild(ri));
19 | };
20 |
21 | export const changeSelectedRoom = (selectedRoomId, value) => {
22 | document.getElementById(selectedRoomId).innerText = value;
23 | };
24 |
25 | export const selectedRoomValue = (selectedRoomId) => (
26 | document.getElementById(selectedRoomId).innerHTML
27 | );
28 |
29 | export const userInputValue = (userId) => (
30 | document.getElementById(userId).value
31 | );
--------------------------------------------------------------------------------
/src/namespaces/index.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ io, channel, repository }) => {
2 | const namespace = io.of(channel);
3 |
4 | namespace.on('connection', (socket) => {
5 | const { user } = socket.handshake.query;
6 | repository.addUser(channel, user)
7 | .then(() => {
8 | console.log(`Added user:${user} to channel: random`)
9 | })
10 | .catch(() => socket.disconnect());
11 |
12 | socket.on('messages', () => {
13 | console.log('messages request');
14 | repository.getMessages(channel)
15 | .then((messages) => {
16 | socket.emit('messages', messages);
17 | })
18 | .catch(e => console.log(e));
19 | });
20 |
21 | socket.on('message', (message) => {
22 | const { channel, user, text } = message;
23 | const serverMessage = {
24 | channelKey: channel,
25 | userId: user,
26 | text,
27 | };
28 |
29 | repository.addMessage(serverMessage)
30 | .then(() => {
31 | namespace.emit('message', message);
32 | })
33 | .catch(e => console.log(e));
34 | });
35 | });
36 | };
--------------------------------------------------------------------------------
/e2e/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Document
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/helpers.test.js:
--------------------------------------------------------------------------------
1 | const expect = require('chai').expect;
2 |
3 | const { isNotDefineOrWhiteSpace } = require('../src/helpers');
4 |
5 | describe('helpers', () => {
6 | describe('isNotDefineOrWhiteSpace', () => {
7 | describe('when input is null', () => {
8 | it('returns true', () => {
9 | const input = null;
10 | const result = isNotDefineOrWhiteSpace(input);
11 | expect(result).true;
12 | });
13 | });
14 |
15 | describe('when input is undefined', () => {
16 | it('resturns true', () => {
17 | let input;
18 | const result = isNotDefineOrWhiteSpace(input);
19 | expect(result).true;
20 | });
21 | });
22 |
23 | describe('when input is empty', () => {
24 | it('returns true', () => {
25 | const input = '';
26 | const result = isNotDefineOrWhiteSpace(input);
27 | expect(result).true;
28 | });
29 | });
30 |
31 | describe('when input is white space', () => {
32 | it('returns true', () => {
33 | const input = ' ';
34 | const result = isNotDefineOrWhiteSpace(input);
35 | expect(result).true;
36 | });
37 | });
38 |
39 | describe('when input has value', () => {
40 | it('returns false', () => {
41 | const input = 'v';
42 | const result = isNotDefineOrWhiteSpace(input);
43 | expect(result).false;
44 | });
45 | });
46 |
47 | describe('when input starts with white sapace and has characters', () => {
48 | it('returns false', () => {
49 | const input = ' CA';
50 | const result = isNotDefineOrWhiteSpace(input);
51 | expect(result).false;
52 | });
53 | });
54 | });
55 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # redux-chat-back
2 |
3 | ## Summary
4 |
5 | * This solution works with `express` in combination with `socket.io`. To run this app you will need node >= 10.x.x
6 |
7 | ## Start API
8 |
9 | * To start the main API on development mode from root solution folder.
10 | * The `API`, will run in `localhost`, `port:3000`.
11 | * The protocol for API is `http`.
12 |
13 | ```bash
14 | $ npm start
15 | ```
16 |
17 | ## Unit Testing
18 |
19 | * To run unit tests from root folder:
20 |
21 | ```bash
22 | $ npm test
23 | ```
24 |
25 |
26 | ## Funtcional Test API
27 |
28 | ### To test `echo` with server, from `e2e` as root folder run:
29 |
30 | ```bash
31 | $ npm run start:echo
32 | ```
33 |
34 | * This will start up the `client` and the `API`.
35 | * Open the client on browser: `http://localhost:1234/`
36 | * Type and send message, in browser console must appear:
37 | - `/random-channel#79PVp22K8iHx6tTCAAAA` This identifies the socket.io namespace and socket id
38 | - `{channel: "random-channel", user: "pepe", text: "your text..."}` The message that we sent to server, and it's sending back to us. Just an echo.
39 |
40 | ### To test `API` with `client`, we need to open two separate node processes. First we have to run the server, from root folder:
41 |
42 | ```bash
43 | $ npm start
44 | ```
45 | * The `API`, will run in `localhost`, `port:3000`.
46 |
47 | * Once server is up and running, we can launch client, from `e2e` folder run
48 | ```bash
49 | $ npm run start:client
50 | ```
51 |
52 | * The `client`, will run in `localhost`, `port:1234`
53 |
54 | # About Basefactor + Lemoncode
55 |
56 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
57 |
58 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.
59 |
60 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.
61 |
62 | For the LATAM/Spanish audience check out [Online Devops Bootcamp](https://lemoncode.net/bootcamp-devops#bootcamp-devops/inicio)
63 |
--------------------------------------------------------------------------------
/e2e/src/content/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Verdana, Tahoma, sans-serif;
3 | }
4 |
5 | /*
6 | Main block
7 | */
8 | .chat {
9 | display: grid;
10 | grid-template-columns: 1fr 1fr 1fr 1fr;
11 | grid-template-rows: auto auto auto;
12 | grid-column-gap: 16px;
13 | grid-template-areas: "main main main rooms"
14 | "main main main rooms"
15 | "sender sender sender room-selector";
16 | }
17 |
18 | @media screen and (max-width: 800px) {
19 | /* Here we can use a flex conatiner */
20 | .chat {
21 | display: grid;
22 | grid-template-columns: 1fr 1fr;
23 | grid-template-rows: auto auto auto;
24 | grid-template-areas: "main main"
25 | "main main"
26 | "sender sender";
27 | }
28 |
29 | .chat__rooms {
30 | display: none;
31 | }
32 |
33 | .chat__room-selector {
34 | display: none;
35 | }
36 | }
37 |
38 | /*
39 | Block elements
40 | */
41 | .chat__main {
42 | grid-area: main;
43 | }
44 |
45 | .chat__rooms {
46 | grid-area: rooms;
47 | }
48 |
49 | .chat__sender {
50 | grid-area: sender;
51 | }
52 |
53 | .chat__room-selector {
54 | grid-area: room-selector;
55 | }
56 |
57 | .enrolment {
58 | display: flex;
59 | justify-content: space-between;
60 | padding-bottom: 16px;
61 | padding-top: 16px;
62 | }
63 |
64 | .elronment__field {
65 | padding: 8px;
66 | margin-right: 16px;
67 | }
68 |
69 | .elronment__field-input {
70 | -moz-border-radius:2px;
71 | -webkit-border-radius:2px;
72 | border-radius:2px;
73 | border: 0.5px solid silver;
74 | }
75 |
76 | .enrolment__submit {
77 | background-color:#0dd117;
78 | -moz-border-radius:4px;
79 | -webkit-border-radius:4px;
80 | border-radius:4px;
81 | border:2px solid #0dd117;
82 | display:inline-block;
83 | cursor:pointer;
84 | color:#ffffff;
85 | font-size:9px;
86 | padding:8px 16px;
87 | text-decoration:none;
88 | /* text-shadow:0px 1px 0px #528ecc; */
89 | }
90 |
91 | .rooms-container {
92 | list-style: none;
93 | }
94 |
95 | .messages-container {
96 | border: 1px solid silver;
97 | margin-bottom: 5px;
98 | padding: 4px;
99 | border-radius: 4px;
100 | min-height: 80vh;
101 | display: flex;
102 | flex-direction: column;
103 | }
104 |
105 | .message-sender {
106 | border: 1px solid black;
107 | padding: 4px;
108 | border-radius: 4px;
109 | display: flex;
110 | justify-content: space-between;
111 | }
112 |
113 | .message-sender__text {
114 | width: 90%;
115 | margin-right: 5px;
116 | resize: none;
117 | }
118 |
119 | .message-sender__submit {
120 | background-color:#79bbff;
121 | -moz-border-radius:4px;
122 | -webkit-border-radius:4px;
123 | border-radius:4px;
124 | border:2px solid #337bc4;
125 | display:inline-block;
126 | cursor:pointer;
127 | color:#ffffff;
128 | font-size:17px;
129 | padding:16px 31px;
130 | text-decoration:none;
131 | text-shadow:0px 1px 0px #528ecc;
132 | }
133 |
134 | .message-sender__submit--hover {
135 | background-color:#378de5;
136 | }
137 |
138 | .message-sender__submit--active {
139 | position:relative;
140 | top:1px;
141 | }
142 |
--------------------------------------------------------------------------------
/src/repositories/channelRepository.js:
--------------------------------------------------------------------------------
1 | const Message = require('../models/Message');
2 | const { isNotDefineOrWhiteSpace } = require('../helpers');
3 |
4 | const channels = [];
5 |
6 | const createChannel = (channelKey) => {
7 | channels[channelKey] = {
8 | users: [],
9 | messages: [],
10 | };
11 | };
12 |
13 | exports.channelList = () => (
14 | Promise.resolve(Object.keys(channels))
15 | );
16 |
17 | exports.existChannel = (channelKey) => (
18 | new Promise((resolve, _) => {
19 | exports.channelList().then((channelList) => {
20 | resolve(
21 | !!channelList.find(c => c === channelKey)
22 | );
23 | });
24 | })
25 | );
26 |
27 | exports.createChannel = async (channelKey) => {
28 | const channelExist = await exports.existChannel(channelKey);
29 | return new Promise((resolve, reject) => {
30 | if (isNotDefineOrWhiteSpace(channelKey)) {
31 | reject('No value for channelKey.');
32 | }
33 | if (channelExist) {
34 | reject(`Channel ${channelKey} already exist.`);
35 | }
36 | createChannel(channelKey);
37 | resolve();
38 | });
39 | };
40 |
41 | exports.existUser = async (channelKey, userId) => {
42 | const existChannel = await exports.existChannel(channelKey);
43 | return new Promise((resolve, reject) => {
44 | if (!existChannel) {
45 | reject(`${channelKey} does not exist.`);
46 | };
47 | resolve(
48 | !!channels[channelKey].users.find(u => u === userId)
49 | );
50 | });
51 | };
52 |
53 | exports.addUser = async (channelKey, userId) => {
54 | const existChannel = await exports.existChannel(channelKey);
55 | const existUser = await exports.existUser(channelKey, userId);
56 | return new Promise((resolve, reject) => {
57 | if (
58 | isNotDefineOrWhiteSpace(channelKey) ||
59 | isNotDefineOrWhiteSpace(userId)
60 | ) {
61 | reject('No value for channelKey or userId');
62 | }
63 |
64 | if (!existChannel) {
65 | reject(`${channelKey} does not exist.`);
66 | }
67 | if (!existUser) {
68 | channels[channelKey].users = [...channels[channelKey].users, userId];
69 | resolve();
70 | }
71 | reject(`${userId} already exists in this channel.`);
72 | });
73 | };
74 |
75 | const canAddMessage = async (channelKey, userId) => (
76 | await exports.existChannel(channelKey) &&
77 | await exports.existUser(channelKey, userId)
78 | );
79 |
80 | exports.addMessage = async ({ channelKey, userId, text }) => {
81 | const canAddMessageResolved = await canAddMessage(channelKey, userId)
82 | return new Promise((resolve, reject) => {
83 | if (canAddMessageResolved) {
84 | channels[channelKey].messages = [
85 | ...channels[channelKey].messages,
86 | new Message(userId, text, Date.now())
87 | ];
88 | resolve();
89 | }
90 | reject(`Channel: ${channelKey} or User: ${userId} does not exist.`);
91 | });
92 | };
93 |
94 | exports.getMessages = async (channelKey) => {
95 | const existChannel = await exports.existChannel(channelKey);
96 | return new Promise((resolve, reject) => {
97 | if (existChannel) {
98 | resolve(channels[channelKey].messages);
99 | }
100 | reject(`${channelKey} does not exist.`);
101 | });
102 | };
--------------------------------------------------------------------------------
/e2e/src/app.js:
--------------------------------------------------------------------------------
1 | import { createSocket, messageFactory } from './services/channel';
2 | import {
3 | clearMessageText,
4 | addMessageText,
5 | populateRooms,
6 | changeSelectedRoom,
7 | userInputValue,
8 | selectedRoomValue
9 | } from './services/uiHelpers';
10 | import { fetchRooms, canenrollRoom } from './services/api';
11 | import { baseUrl } from './config';
12 |
13 | const socketEventsHandler = (socket) => {
14 | socket.on('connect', () => {
15 | console.log(socket.id);
16 | socket.emit('messages');
17 | });
18 | socket.on('error', (err) => console.log(err));
19 | socket.on('disconnect', () => console.log('disconnected'))
20 |
21 | socket.on('message', (msg) => {
22 | addMessageText('messages', 'p', msg);
23 | const textElement = document.getElementById('message');
24 | clearMessageText(textElement);
25 | });
26 | socket.on('messages', (msgs) => {
27 | // NOTE: Timestamp it's created on server not used on client yet.
28 | console.log('messages', msgs);
29 | msgs.map((ms) => ({
30 | user: ms.userId,
31 | text: ms.text
32 | })).forEach((mc) => {
33 | addMessageText('messages', 'p', mc);
34 | });
35 | });
36 | };
37 |
38 | const handleSelectedRoom = () => {
39 | // NOTE: Bear on mind to disconnect from socket channel.
40 | document.getElementById('rooms')
41 | .addEventListener('click', (evt) => {
42 | evt.stopPropagation();
43 | changeSelectedRoom('selectedroom', evt.target.innerHTML);
44 | });
45 | };
46 |
47 | const handleEnrolment = (callback) => {
48 | // NOTE: Check selected room.
49 | // NOTE: Check input user.
50 | document.getElementById('enrolmentsubmit')
51 | .addEventListener('click', (evt) => {
52 | evt.stopPropagation();
53 | evt.preventDefault();
54 | const room = selectedRoomValue('selectedroom');
55 | const userId = userInputValue('userid');
56 | canenrollRoom(
57 | room,
58 | userId,
59 | ).then((canenroll) => {
60 | if (canenroll) {
61 | callback(null, room, userId);
62 | } else {
63 | callback('Can not enroll to this channel');
64 | }
65 | }
66 | ).catch(e => console.log(e));
67 | });
68 | };
69 |
70 | const handleSender = (socket, room, user) => {
71 | const _messageFactory = messageFactory(room, user);
72 | document.getElementById('sendbutton')
73 | .addEventListener('click', (evt) => {
74 | evt.stopPropagation();
75 | const textElement = document.getElementById('message');
76 | const message = _messageFactory.compose(textElement.value)
77 | socket.emit('message', message);
78 | });
79 | };
80 |
81 | document.addEventListener('DOMContentLoaded', () => {
82 | fetchRooms()
83 | .then((rooms) => {
84 | populateRooms(rooms, 'rooms');
85 | handleSelectedRoom();
86 | })
87 | .catch(e => console.log(e));
88 |
89 | handleEnrolment((err, room, userId) => {
90 | if (!err) {
91 | const socketParams = {
92 | url: baseUrl,
93 | channel: room,
94 | options: {
95 | query: `user=${userId}`
96 | },
97 | };
98 | const socket = createSocket(socketParams);
99 | socketEventsHandler(socket);
100 | handleSender(socket, room, userId);
101 | }
102 | });
103 | });
104 |
105 |
--------------------------------------------------------------------------------
/test/channelRepository.test.js:
--------------------------------------------------------------------------------
1 | const expect = require('chai').expect;
2 |
3 | const channelRepository = require('../src/repositories/channelRepository');
4 |
5 | describe('channelRepository', () => {
6 | describe('adding new channel with empty channelKey', () => {
7 | it('should avoid add channel', async () => {
8 | const channelKey = '';
9 | try {
10 | await channelRepository.createChannel(channelKey);
11 | } catch (error) {
12 | expect(error).equals('No value for channelKey.');
13 | }
14 | });
15 | });
16 |
17 | describe('adding new channel with null channelKey', () => {
18 | it('should avoid add channel', async () => {
19 | const channelKey = null;
20 | try {
21 | await channelRepository.createChannel(channelKey);
22 | } catch (error) {
23 | expect(error).equals('No value for channelKey.');
24 | }
25 | });
26 | });
27 |
28 | describe('adding new channel with undefined channelKey', () => {
29 | it('should avoid add channel', async () => {
30 | let channelKey;
31 | try {
32 | await channelRepository.createChannel(channelKey);
33 | } catch (error) {
34 | expect(error).equals('No value for channelKey.');
35 | }
36 | });
37 | });
38 |
39 | describe('adding new channel with white space channelKey', () => {
40 | it('should avoid add channel', async () => {
41 | const channelKey = ' ';
42 | try {
43 | await channelRepository.createChannel(channelKey);
44 | } catch (error) {
45 | expect(error).equals('No value for channelKey.');
46 | }
47 | });
48 | });
49 |
50 | describe('adding new channel to empty repository', () => {
51 | it('should make repository to have elements', async () => {
52 | await channelRepository.createChannel('foo');
53 | const channelList = await channelRepository.channelList();
54 | expect(channelList.length).to.greaterThan(0);
55 | });
56 | });
57 |
58 | describe('adding twice a channel to repository', () => {
59 | it('should avoid duplicated channel', async () => {
60 | await channelRepository.createChannel('comics');
61 | try {
62 | await channelRepository.createChannel('comics');
63 | } catch (error) {
64 | expect(error).equals('Channel comics already exist.');
65 | }
66 | });
67 | });
68 |
69 | describe('adding a user with empty userId', () => {
70 | it('should avoid insert user', async () => {
71 | const userId = '';
72 | try {
73 | await channelRepository.addUser('comics', userId);
74 | } catch (error) {
75 | expect(error).equals('No value for channelKey or userId');
76 | }
77 | });
78 | });
79 |
80 | describe('adding a user with null userId', () => {
81 | it('should avoid insert user', async () => {
82 | const userId = null;
83 | try {
84 | await channelRepository.addUser('comics', userId);
85 | } catch (error) {
86 | expect(error).equals('No value for channelKey or userId');
87 | }
88 | });
89 | });
90 |
91 | describe('adding a user with undefined userId', () => {
92 | it('should avoid insert user', async () => {
93 | let userId;
94 | try {
95 | await channelRepository.addUser('comics', userId);
96 | } catch (error) {
97 | expect(error).equals('No value for channelKey or userId');
98 | }
99 | });
100 | });
101 |
102 | describe('adding a user with white space userId', () => {
103 | it('should avoid insert user', async () => {
104 | const userId = ' ';
105 | try {
106 | await channelRepository.addUser('comics', userId);
107 | } catch (error) {
108 | expect(error).equals('No value for channelKey or userId');
109 | }
110 | });
111 | });
112 |
113 | describe('adding twice a user to a channel repository', () => {
114 | it('should avoid duplicated user', async () => {
115 | await channelRepository.addUser('comics', 'jai');
116 | try {
117 | await channelRepository.addUser('comics', 'jai');
118 | } catch (error) {
119 | expect(error).equals('jai already exists in this channel.');
120 | }
121 | });
122 | });
123 |
124 | describe('request messages from channel with messages', () => {
125 | it('should return messages', async () => {
126 | const message = {
127 | channelKey: 'comics',
128 | userId: 'jai',
129 | text: 'batman is awesome',
130 | };
131 | await channelRepository.addMessage(message);
132 | const messages = await channelRepository.getMessages(message.channelKey);
133 | expect(messages.length).to.greaterThan(0);
134 | });
135 | });
136 |
137 | describe('add message to non existing channel', () => {
138 | it('should avoid add message', async () => {
139 | try {
140 | const message = {
141 | channelKey: 'no exists',
142 | userId: 'jai',
143 | text: 'plain text'
144 | };
145 | await channelRepository.addMessage(message);
146 | } catch (error) {
147 | expect(error).equals('Channel: no exists or User: jai does not exist.');
148 | }
149 | });
150 | });
151 |
152 | describe('not existing user adding a message to channel', () => {
153 | it('should avoid add message', async () => {
154 | try {
155 | const message = {
156 | channelKey: 'noexist',
157 | userId: 'no exists',
158 | text: 'plain text'
159 | };
160 | await channelRepository.addMessage(message);
161 | } catch (error) {
162 | expect(error).equals('Channel: noexist or User: no exists does not exist.');
163 | }
164 | });
165 | });
166 | });
--------------------------------------------------------------------------------