├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── README.md
├── config
└── connection.js
├── controllers
├── api
│ ├── index.js
│ ├── message-routes.js
│ └── user-routes.js
├── home-routes.js
└── index.js
├── db
└── schema.sql
├── models
├── Message.js
├── User.js
└── index.js
├── package-lock.json
├── package.json
├── public
└── assets
│ ├── css
│ └── style.css
│ ├── images
│ ├── logo-dark.png
│ ├── logo-dark.svg
│ ├── logo-light.png
│ ├── logo-light.svg
│ └── randm-icon.png
│ └── js
│ ├── delete-user.js
│ ├── login.js
│ ├── logout.js
│ ├── message.js
│ ├── random-chat.js
│ ├── recent.js
│ ├── register.js
│ └── socket.js
├── seeds
├── index.js
├── message-seeds.js
└── user-seeds.js
├── server.js
├── utils
├── filters.js
└── helpers.js
└── views
├── chat.handlebars
├── layouts
└── main.handlebars
├── login.handlebars
└── register.handlebars
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "commonjs": true,
6 | "node": true,
7 | "es6": true,
8 | "es2017": true
9 | },
10 | "rules": {
11 | "no-undef-init": "error",
12 | "no-duplicate-case": "error",
13 | "no-empty": "error",
14 | "no-extra-semi": "error",
15 | "no-func-assign": "error",
16 | "no-irregular-whitespace": "error",
17 | "no-unreachable": "error",
18 | "curly": "error",
19 | "dot-notation": "error",
20 | "eqeqeq": "error",
21 | "no-empty-function": "error",
22 | "no-multi-spaces": "error",
23 | "no-mixed-spaces-and-tabs": "error",
24 | "no-trailing-spaces": "error",
25 | "default-case": "error",
26 | "no-fallthrough": "error",
27 | "no-unused-vars": "error",
28 | "no-use-before-define": "error",
29 | "no-redeclare": "error",
30 | "brace-style": "error",
31 | "indent": ["error", 2],
32 | "quotes": ["error", "single"],
33 | "semi": ["error", "always"],
34 | "radix": "off"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | .env
4 | frontend-tests-NOT-PUBLIC/
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "bracketSameLine": true
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RANDM - The Random Dating App
2 |
3 | **RANDM** is a dating application that helps users find matches through randomization. While other dating apps on the market focus too much on matches based on initial judgement, with RANDM, people can start chatting with and getting to know potential dates without that initial judgement in an app free of profile pictures and “swiping”.
4 |
5 | ## Table of Contents
6 |
7 | * [User Story](#user-story)
8 | * [Screenshot](#screenshot)
9 | * [Live Deployment](#live-deployment)
10 | * [Created With](#created-with)
11 | * [Installation and Usage](#installation-and-usage)
12 | * [Contributing](#contributing)
13 | * [Tests](#tests)
14 | * [Questions](#questions)
15 |
16 | ## User Story
17 |
18 | ```
19 | AS A person who has trouble finding dating matches
20 | I WANT to use a dating app that randomly matches users
21 | SO THAT when I click on the RANDM button, a new page opens with a random potential date with whom I can start chatting.
22 | ```
23 |
24 | ```
25 | WHEN I open the app
26 | THEN I am presented with the title of the app and the login form.
27 | WHEN I choose to register
28 | THEN I'm presented with inputting my first name, last name, email, password, gender identity, sexual preferences, pronouns, birthday, and bio.
29 | WHEN I click on login
30 | THEN I'm presented with entering my email and password.
31 | WHEN I click the randomize a new chat button
32 | THEN I'm presented with a new open chat with a random user.
33 | WHEN I click on logout
34 | THEN I'm presented with the homepage screen.
35 | ```
36 |
37 | ## Screenshot
38 |
39 | [](https://ran-dm.herokuapp.com/)
40 |
41 | ## Live Deployment
42 |
43 | This application is deployed using Heroku:
44 |
45 | - [Ran-dm.Herokuapp.com](https://ran-dm.herokuapp.com/)
46 |
47 | ## Created With
48 |
49 | * Node.js + Express.js
50 | * MySQL / MySQL2
51 | * Sequelize
52 | * Handlebars
53 | * Bcrypt
54 | * Socket.io
55 |
56 | 
57 | 
58 | 
59 |
60 | ## Installation and Usage
61 |
62 | To install and run this project, please follow these steps:
63 | 1. Make sure you have [Node.js](https://nodejs.org) and [MySQL](https://dev.mysql.com/downloads/) installed.
64 | 2. Through the command line, go to the folder you wish this application's folder to be in.
65 | 3. Do `git clone` of the repository to get the application's files.
66 | 4. Run `npm run schema` to get the database.
67 | 5. To install all of the depenencies this application uses, run `npm install`.
68 | 6. Create a `.env` file containing: `DB_NAME=randm_db`, along wtih your `DB_USER`, `DB_PASSWORD`, and a secret code `SECRET_SECRET`.
69 | 7. To start the application, run `npm start`.
70 | 8. Open [localhost:3001](http://localhost:3001/) to see the local webpage.
71 |
72 | ## Contributing
73 |
74 | RANDM is a work in progress! If you would like to contribute to this project, you can do so by:
75 | 1. Forking the project. ([Learn how to fork.](https://docs.github.com/en/get-started/quickstart/fork-a-repo))
76 | 2. Creating a new feature branch, committing the changes, and pushing the branch.
77 | 3. Opening a [Pull Request](https://github.com/JColeCodes/randm/pulls).
78 |
79 | Read the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
80 |
81 | ## Tests
82 |
83 | To easily test the application with a few users already registered, you can seed the database by running the following command:
84 | ```
85 | npm run seed
86 | ```
87 |
88 | ## Questions
89 | RANDM was created by Jennifer Cole, Lex Slovik, Charlie Hua, Chuong Vo, Marielle Champagne, Ahmad Anees, Gavin Jacobsen, Rex Oliver.
90 |
91 | For inquiries regarding the project, please email Jennifer Cole at [capauldi@gmail.com](mailto:capauldi@gmail.com).
92 |
--------------------------------------------------------------------------------
/config/connection.js:
--------------------------------------------------------------------------------
1 | // Import Sequelize
2 | const Sequelize = require('sequelize');
3 | // Require .env for hiding database information
4 | require('dotenv').config();
5 |
6 | let sequelize;
7 |
8 | // If connected to JawsDB
9 | if (process.env.JAWSDB_URL) {
10 | sequelize = new Sequelize(process.env.JAWSDB_URL);
11 | } else if (process.env.MYSQL_URL) {
12 | sequelize = new Sequelize(process.env.MYSQL_URL);
13 | } else {
14 | // Else, create connection to our local database
15 | sequelize = new Sequelize(
16 | process.env.DB_NAME,
17 | process.env.DB_USER,
18 | process.env.DB_PASSWORD,
19 | {
20 | host: 'localhost',
21 | dialect: 'mysql',
22 | port: 3306
23 | }
24 | );
25 | }
26 |
27 | module.exports = sequelize;
28 |
--------------------------------------------------------------------------------
/controllers/api/index.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router();
2 |
3 | const userRoutes = require('./user-routes');
4 | const messageRoutes = require('./message-routes');
5 |
6 | router.use('/users', userRoutes);
7 | router.use('/messages', messageRoutes);
8 |
9 | module.exports = router;
--------------------------------------------------------------------------------
/controllers/api/message-routes.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router();
2 | const { User, Message } = require('../../models');
3 | const { Op } = require('sequelize');
4 | const { getUserLatest } = require('../../utils/filters');
5 |
6 | // Get all messages of user if logged in
7 | router.get('/', (req, res) => {
8 | // If not logged in, redirect to login page
9 | if (!req.session.loggedIn) {
10 | res.redirect('/login');
11 | return;
12 | }
13 | var sessionId = req.session.user_id;
14 | Message.findAll({
15 | where: {
16 | // If receiver id OR sender id = session id
17 | [Op.or]: [{ receiver_id: sessionId }, { sender_id: sessionId }]
18 | },
19 | attributes: ['id', 'message_text', 'createdAt'],
20 | include: [
21 | {
22 | // Includes message sender
23 | model: User,
24 | as: 'sender',
25 | attributes: ['id', 'first_name', 'last_name']
26 | },
27 | {
28 | // Includes message receiver
29 | model: User,
30 | as: 'receiver',
31 | attributes: ['id', 'first_name', 'last_name']
32 | }
33 | ]
34 | })
35 | .then((dbMessageData) => res.json(dbMessageData))
36 | .catch((err) => {
37 | console.log(err);
38 | res.status(500).json(err);
39 | });
40 | });
41 |
42 | // Get recent messages
43 | router.get('/recent', (req, res) => {
44 | // If not logged in, redirect to login page
45 | if (!req.session.loggedIn) {
46 | res.redirect('/login');
47 | return;
48 | }
49 | var sessionId = req.session.user_id;
50 | Message.findAll({
51 | where: {
52 | // If receiver id OR sender id = session id
53 | [Op.or]: [{ receiver_id: sessionId }, { sender_id: sessionId }]
54 | },
55 | order: [['createdAt', 'DESC']]
56 | })
57 | .then((dbMessageData) => {
58 | // Find all users
59 | User.findAll({
60 | attributes: ['id', 'first_name', 'last_name']
61 | })
62 | .then((dbUserData) => {
63 | // Map users for plain javascript of data
64 | const user = dbUserData.map((user) => user.get({ plain: true }));
65 |
66 | // Map messages for plain javascript of data
67 | const messages = dbMessageData.map((message) =>
68 | message.get({ plain: true })
69 | );
70 |
71 | // Gets latest chat message for every conversation user has
72 | let userLatest = getUserLatest(messages, user, sessionId);
73 |
74 | res.json(userLatest.latestChat);
75 | })
76 | // Error catch for User.findAll
77 | .catch((err) => {
78 | console.log(err);
79 | res.status(500).json(err);
80 | });
81 | })
82 | // Error catch for Message.findAll
83 | .catch((err) => {
84 | console.log(err);
85 | res.status(500).json(err);
86 | });
87 | });
88 |
89 | // Post new message
90 | router.post('/', (req, res) => {
91 | // Creates new message with information sent from public/assets/message.js
92 | Message.create({
93 | sender_id: req.body.sender_id,
94 | receiver_id: req.body.receiver_id,
95 | message_text: req.body.message_text
96 | })
97 | .then((dbMessageData) => res.json(dbMessageData))
98 | .catch((err) => {
99 | console.log(err);
100 | res.status(400).json(err);
101 | });
102 | });
103 |
104 | module.exports = router;
--------------------------------------------------------------------------------
/controllers/api/user-routes.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router();
2 | const { User, Message } = require('../../models');
3 |
4 | // Get all users
5 | router.get('/', (req, res) => {
6 | User.findAll({
7 | attributes: { exclude: ['password', 'email', 'birthday'] }
8 | })
9 | .then((dbUserData) => res.json(dbUserData))
10 | .catch((err) => {
11 | console.log(err);
12 | res.status(500).json(err);
13 | });
14 | });
15 |
16 | // Find one user by id
17 | router.get('/:id', (req, res) => {
18 | User.findOne({
19 | where: { id: req.params.id },
20 | attributes: { exclude: ['password', 'email', 'birthday'] }
21 | })
22 | .then((dbUserData) => {
23 | if (!dbUserData) {
24 | res.status(404).json({ message: 'No user found with this id' });
25 | return;
26 | }
27 | res.json(dbUserData);
28 | })
29 | .catch((err) => {
30 | console.log(err);
31 | res.status(500).json(err);
32 | });
33 | });
34 |
35 | // Add user to database on register
36 | router.post('/', (req, res) => {
37 | /* Expects: {
38 | "email": "myemail@email.com",
39 | "password": "password1234",
40 | "first_name": "user",
41 | "last_name": "name",
42 | "bio": "I am really interesting",
43 | "gender": "non-binary",
44 | "sexual_preference": "pansexual",
45 | "pronouns": "they/them",
46 | "birthday": "03/03/2003"
47 | } */
48 | User.create(req.body)
49 | .then((dbUserData) => {
50 | res.json(dbUserData);
51 | })
52 | .catch((err) => {
53 | console.log(err);
54 | res.status(500).json(err);
55 | });
56 | });
57 |
58 | // Login verification post route to /login endpoint
59 | router.post('/login', (req, res) => {
60 | // Expects: {"email": "myemail@email.com", "password": "password1234"}
61 | User.findOne({
62 | where: { email: req.body.email }
63 | }).then((dbUserData) => {
64 | if (!dbUserData) {
65 | res.status(400).json({ message: 'No user found with that email address' });
66 | return;
67 | }
68 |
69 | // Check password validity
70 | const validPassword = dbUserData.checkPassword(req.body.password);
71 | if (!validPassword) {
72 | res.status(400).json({ message: 'Incorrect password' });
73 | return;
74 | }
75 | // Save session data and set status to loggedIn true
76 | req.session.save(() => {
77 | req.session.user_id = dbUserData.id;
78 | req.session.email = dbUserData.email;
79 | req.session.loggedIn = true;
80 |
81 | res.json({ user: dbUserData, message: 'Login successful' });
82 | });
83 | });
84 | });
85 |
86 | // Destroy the session on logout
87 | router.post('/logout', (req, res) => {
88 | if (req.session.loggedIn) {
89 | req.session.destroy(() => {
90 | res.status(204).end();
91 | });
92 | } else {
93 | res.status(404).end();
94 | }
95 | });
96 |
97 | // Delete a user by id
98 | router.delete('/:id', (req, res) => {
99 | User.destroy({
100 | where: {
101 | id: req.params.id
102 | }
103 | })
104 | .then((dbUserData) => {
105 | if (!dbUserData) {
106 | res.status(404).json({ message: 'No user found with this id' });
107 | return;
108 | }
109 | res.json(dbUserData);
110 | })
111 | .catch((err) => {
112 | console.log(err);
113 | res.status(500).json(err);
114 | });
115 | });
116 |
117 | module.exports = router;
--------------------------------------------------------------------------------
/controllers/home-routes.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router();
2 | const { User, Message } = require('../models');
3 | const { getUserLatest } = require('../utils/filters');
4 | const { Op } = require('sequelize');
5 |
6 | var sessionId;
7 |
8 | // Redirect homepage to either login page (if not logged in) OR chat page (if logged in)
9 | router.get('/', (req, res) => {
10 | if (!req.session.loggedIn) {
11 | res.redirect('/login');
12 | return;
13 | } else {
14 | res.redirect('/chat');
15 | return;
16 | }
17 | });
18 |
19 | // Show login page if user is logged out, but show /chat page if user is logged in
20 | router.get('/login', (req, res) => {
21 | if (req.session.loggedIn) {
22 | res.redirect('/chat');
23 | return;
24 | }
25 | res.render('login');
26 | });
27 |
28 | // Show register page if user is logged out, but show /chat page if user is logged in
29 | router.get('/register', (req, res) => {
30 | if (req.session.loggedIn) {
31 | res.redirect('/chat');
32 | return;
33 | }
34 | res.render('register');
35 | });
36 |
37 | // Finds information for currently logged in user to display information properly
38 | router.get('/chat', (req, res) => {
39 | // Redirect to /login if not logged in
40 | if (!req.session.loggedIn) {
41 | res.redirect('/login');
42 | return;
43 | }
44 |
45 | sessionId = req.session.user_id; // Get session user's id
46 | User.findAll({
47 | attributes: ['id', 'first_name', 'last_name']
48 | })
49 | .then((dbUserData) => {
50 | // Map users for plain javascript of data
51 | const user = dbUserData.map((user) => user.get({ plain: true }));
52 |
53 | // Gets information for current user
54 | const userLatest = getUserLatest(null, user, sessionId);
55 |
56 | // Renders page with chat handlebars
57 | res.render('chat', {
58 | userLatest,
59 | loggedIn: req.session.loggedIn,
60 | chatHome: true // Boolean for if chat is page home or user page
61 | });
62 | })
63 | // Error catch for User.findAll
64 | .catch((err) => {
65 | console.log(err);
66 | res.status(500).json(err);
67 | });
68 | });
69 |
70 | // Find all messages between two specific users
71 | router.get('/chat/:id', (req, res) => {
72 | // Redirect to /login if not logged in
73 | if (!req.session.loggedIn) {
74 | res.redirect('/login');
75 | return;
76 | }
77 | // If user is chatting with everyone, reroute undefined to chat home
78 | if (req.params.id == 'undefined') {
79 | res.redirect('/chat');
80 | return;
81 | }
82 |
83 | sessionId = req.session.user_id;
84 | // If user goes to page with their own id, redirect them to home, they can't chat alone
85 | if (req.params.id == sessionId) {
86 | res.redirect('/chat');
87 | return;
88 | }
89 | Message.findAll({
90 | where: {
91 | [Op.or]: [
92 | // If receiver id = session id AND sender id = param id
93 | { receiver_id: sessionId, sender_id: req.params.id },
94 | // OR receiver id = param id AND sender id = session id
95 | { receiver_id: req.params.id, sender_id: sessionId }
96 | ]
97 | },
98 | // Display all messages in order by message id
99 | order: [['id']]
100 | })
101 | .then((dbMessageData) => {
102 | // Gets all users and info
103 | User.findAll({
104 | attributes: [
105 | 'id',
106 | 'first_name',
107 | 'last_name',
108 | 'pronouns',
109 | 'gender',
110 | 'sexual_preference',
111 | 'bio'
112 | ]
113 | })
114 | .then((dbUserData) => {
115 | // Map users for plain javascript of data
116 | const user = dbUserData.map((user) => user.get({ plain: true }));
117 |
118 | // Searches to see if the param id exists in the user table
119 | let userExist = false;
120 | user.forEach((user) => {
121 | if (user.id == req.params.id) {
122 | userExist = true;
123 | return;
124 | }
125 | });
126 | // If user does not exist, redirect to chat home
127 | if (!userExist) {
128 | res.redirect('/');
129 | return;
130 | }
131 |
132 | // Map messages for plain javascript of data
133 | const messages = dbMessageData.map((message) =>
134 | message.get({ plain: true })
135 | );
136 |
137 | // Gets information for current user
138 | const userLatest = getUserLatest(
139 | messages,
140 | user,
141 | sessionId,
142 | req.params.id
143 | );
144 |
145 | // Render all messages on specific chat page
146 | res.render('chat', {
147 | messages,
148 | userLatest,
149 | loggedIn: req.session.loggedIn,
150 | chatHome: false // Boolean for if chat is page home or user page
151 | });
152 | })
153 | // Error catch for User.findAll
154 | .catch((err) => {
155 | console.log(err);
156 | res.status(500).json(err);
157 | });
158 | })
159 | // Error catch for Message.findAll
160 | .catch((err) => {
161 | console.log(err);
162 | res.status(500).json(err);
163 | });
164 | });
165 |
166 | // Redirect to homepage for any page that does not exist
167 | router.get('*', (req, res) => {
168 | res.redirect('/');
169 | });
170 |
171 | module.exports = router;
--------------------------------------------------------------------------------
/controllers/index.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router();
2 | const apiRoutes = require('./api');
3 | const homeRoutes = require('./home-routes.js');
4 |
5 | router.use('/api', apiRoutes); // Routes for /api
6 | router.use('/', homeRoutes); // Routes for public files and pages
7 |
8 | router.use((req, res) => {
9 | res.status(404).end();
10 | });
11 |
12 | module.exports = router;
--------------------------------------------------------------------------------
/db/schema.sql:
--------------------------------------------------------------------------------
1 | DROP DATABASE IF EXISTS randm_db;
2 |
3 | CREATE DATABASE randm_db;
--------------------------------------------------------------------------------
/models/Message.js:
--------------------------------------------------------------------------------
1 | const { Model, DataTypes } = require('sequelize');
2 | const sequelize = require('../config/connection');
3 |
4 | class Message extends Model {}
5 |
6 | Message.init(
7 | {
8 | id: {
9 | type: DataTypes.INTEGER,
10 | allowNull: false,
11 | primaryKey: true,
12 | autoIncrement: true
13 | },
14 | sender_id: {
15 | type: DataTypes.INTEGER,
16 | allowNull: false,
17 | references: {
18 | model: 'user',
19 | key: 'id'
20 | }
21 | },
22 | receiver_id: {
23 | type: DataTypes.INTEGER,
24 | allowNull: false,
25 | references: {
26 | model: 'user',
27 | key: 'id'
28 | }
29 | },
30 | message_text: {
31 | type: DataTypes.STRING,
32 | allowNull: false
33 | }
34 | },
35 | {
36 | sequelize,
37 | freezeTableName: true,
38 | underscored: true,
39 | modelName: 'message'
40 | }
41 | );
42 |
43 | module.exports = Message;
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const { Model, DataTypes } = require('sequelize');
2 | const sequelize = require('../config/connection');
3 | const bcrypt = require('bcrypt');
4 |
5 | class User extends Model {
6 | checkPassword(loginPW) {
7 | return bcrypt.compareSync(loginPW, this.password);
8 | }
9 | }
10 |
11 | User.init(
12 | {
13 | id: {
14 | type: DataTypes.INTEGER,
15 | allowNull: false,
16 | primaryKey: true,
17 | autoIncrement: true
18 | },
19 | first_name: {
20 | type: DataTypes.STRING,
21 | allowNUll: false
22 | },
23 | last_name: {
24 | type: DataTypes.STRING,
25 | allowNUll: false
26 | },
27 | email: {
28 | type: DataTypes.STRING,
29 | allowNull: false,
30 | unique: true,
31 | validate: {
32 | isEmail: true
33 | }
34 | },
35 | password: {
36 | type: DataTypes.STRING,
37 | allowNull: false,
38 | validate: {
39 | len: [8]
40 | }
41 | },
42 | bio: {
43 | type: DataTypes.STRING,
44 | allowNull: true
45 | },
46 | gender: {
47 | type: DataTypes.STRING,
48 | allowNull: false
49 | },
50 | sexual_preference: {
51 | type: DataTypes.STRING,
52 | allowNull: false
53 | },
54 | pronouns: {
55 | type: DataTypes.STRING,
56 | allowNull: true
57 | },
58 | birthday: {
59 | type: DataTypes.STRING,
60 | allowNull: false
61 | }
62 | },
63 | {
64 | hooks: {
65 | async beforeCreate(newUserData) {
66 | newUserData.password = await bcrypt.hash(newUserData.password, 10);
67 | return newUserData;
68 | },
69 | async beforeUpdate(updatedUserData) {
70 | updatedUserData.password = await bcrypt.hash(
71 | updatedUserData.password,
72 | 10
73 | );
74 | return updatedUserData;
75 | }
76 | },
77 | sequelize,
78 | timestamps: false,
79 | freezeTableName: true,
80 | underscored: true,
81 | modelName: 'user'
82 | }
83 | );
84 |
85 | module.exports = User;
--------------------------------------------------------------------------------
/models/index.js:
--------------------------------------------------------------------------------
1 | const User = require('./User');
2 | const Message = require('./Message');
3 |
4 | User.hasMany(Message, {
5 | foreignKey: 'sender_id',
6 | as: 'sender'
7 | });
8 | Message.belongsTo(User, {
9 | foreignKey: 'sender_id',
10 | as: 'sender'
11 | });
12 |
13 | User.hasMany(Message, {
14 | foreignKey: 'receiver_id',
15 | as: 'receiver'
16 | });
17 | Message.belongsTo(User, {
18 | foreignKey: 'receiver_id',
19 | as: 'receiver'
20 | });
21 |
22 | module.exports = { User, Message };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "randm",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js",
9 | "watch": "nodemon server",
10 | "schema": "mysql -u root -p < db/schema.sql",
11 | "seed": "node seeds/index.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/JColeCodes/randm.git"
16 | },
17 | "author": "",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/JColeCodes/randm/issues"
21 | },
22 | "homepage": "https://github.com/JColeCodes/randm#readme",
23 | "dependencies": {
24 | "bcrypt": "^5.0.1",
25 | "connect-session-sequelize": "^7.1.2",
26 | "dotenv": "^16.0.0",
27 | "eslint": "^8.8.0",
28 | "eslint-config-prettier": "^8.3.0",
29 | "express": "^4.17.2",
30 | "express-handlebars": "^6.0.2",
31 | "express-session": "^1.17.2",
32 | "mysql2": "^2.3.3",
33 | "prettier": "^2.5.1",
34 | "sequelize": "^6.16.0",
35 | "socket.io": "^4.4.1"
36 | },
37 | "devDependencies": {
38 | "@types/bcrypt": "^5.0.0",
39 | "@types/express-session": "^1.17.4",
40 | "nodemon": "^2.0.15"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/assets/css/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-color: #59276d;
3 | /* Main Color */
4 | --secondary-color: #bc34ae;
5 | /* Secondary Color */
6 | --secondary-lighter: #ed67df;
7 | /* Lighter Version of Secondary Color */
8 | --lightest-color: #f6f9f9;
9 | /* Light-Gray Color */
10 | --mid-color: #afb5b6;
11 | /* Mid-Gray Color */
12 | --darkest-color: #7c8385;
13 | /* Darker-Gray Color */
14 | --sidebar-color: #edf3f3;
15 | /* Sidebar Background Color */
16 | --sidebar-highlight-color: #e0e9e9;
17 | /* Sidebar Highlight Color */
18 | }
19 |
20 | * {
21 | margin: 0;
22 | padding: 0;
23 | box-sizing: border-box;
24 | }
25 |
26 | body {
27 | font-family: 'Lato', sans-serif;
28 | font-size: 16px;
29 | line-height: 1;
30 | background-color: var(--lightest-color);
31 | color: var(--darkest-color);
32 | }
33 |
34 | a,
35 | a:hover,
36 | button,
37 | button:hover,
38 | li,
39 | li:hover {
40 | transition: all 0.5s ease-in-out;
41 | }
42 |
43 | button:hover {
44 | cursor: pointer;
45 | }
46 |
47 | a {
48 | color: var(--main-color);
49 | text-decoration: none;
50 | }
51 |
52 | a:hover {
53 | color: var(--secondary-color);
54 | }
55 |
56 | input:focus {
57 | outline: 0;
58 | }
59 |
60 | .open-toggle,
61 | .close-toggle {
62 | display: none;
63 | }
64 |
65 |
66 | /* IF NOT LOGGED IN */
67 |
68 | .unlogged-page {
69 | display: flex;
70 | flex-wrap: wrap;
71 | justify-content: space-between;
72 | align-items: center;
73 | justify-content: center;
74 | padding: 20px;
75 | min-height: calc(100vh - 40px);
76 | }
77 |
78 |
79 | /* Display logo on left */
80 |
81 | .unlogged-page .logo {
82 | width: calc(40% - 30px);
83 | margin: 0 100px 0 20px;
84 | }
85 |
86 | .unlogged-page .logo img {
87 | width: 100%;
88 | }
89 |
90 |
91 | /* Display form on right */
92 |
93 | .unlogged-page .forms {
94 | width: calc(40% - 40px);
95 | margin: 0;
96 | background-color: var(--sidebar-color);
97 | padding: 20px;
98 | font-size: 1em;
99 | }
100 |
101 | .unlogged-page .forms input,
102 | .unlogged-page .forms select,
103 | .unlogged-page .forms textarea {
104 | padding: 10px;
105 | margin: 5px 0 15px;
106 | width: 100%;
107 | font-size: 1em;
108 | background-color: white;
109 | border: 1px solid var(--sidebar-highlight-color);
110 | color: var(--darkest-color);
111 | font-family: 'Lato', sans-serif;
112 | }
113 |
114 | .unlogged-page .forms textarea {
115 | height: 120px;
116 | }
117 |
118 | .unlogged-page .forms input:focus,
119 | .unlogged-page .forms select:focus,
120 | .unlogged-page .forms textarea:focus {
121 | outline: 1px solid var(--secondary-lighter);
122 | border: 1px solid white;
123 | }
124 |
125 | .unlogged-page .forms button {
126 | padding: 10px;
127 | font-size: 1em;
128 | border: none;
129 | background-color: var(--secondary-lighter);
130 | color: white;
131 | }
132 |
133 | .unlogged-page .forms .form-message {
134 | font-size: 0.9em;
135 | font-style: italic;
136 | margin: 15px 0 0;
137 | }
138 |
139 |
140 | /* IF USER IS LOGGED IN */
141 |
142 | .logged-in {
143 | display: flex;
144 | justify-content: space-between;
145 | flex-flow: column;
146 | height: 100vh;
147 | }
148 |
149 |
150 | /* Header */
151 |
152 | .logged-in header {
153 | display: flex;
154 | flex-wrap: wrap;
155 | justify-content: space-between;
156 | align-items: center;
157 | padding: 0 20px;
158 | background-color: var(--main-color);
159 | color: white;
160 | }
161 |
162 | .logged-in header .logo {
163 | width: 10%;
164 | }
165 |
166 | .logged-in header .logo img {
167 | height: 60px;
168 | margin: 10px 0;
169 | }
170 |
171 | .logged-in header .welcome-info i {
172 | font-size: 1.5em;
173 | padding-bottom: 10px;
174 | margin-top: 10px;
175 | }
176 |
177 | .logged-in header .welcome-info .user-options {
178 | z-index: -1;
179 | position: absolute;
180 | background-color: white;
181 | border: 1px solid var(--sidebar-highlight-color);
182 | box-shadow: 0 0 1px var(--darkest-color);
183 | padding: 10px;
184 | right: 20px;
185 | opacity: 0;
186 | transition: all 0.1s ease-in-out;
187 | }
188 |
189 | .logged-in header .welcome-info:hover .user-options {
190 | z-index: 100;
191 | opacity: 1;
192 | transition: all 0.1s ease-in-out;
193 | }
194 |
195 | .logged-in header .welcome-info .user-options ul {
196 | list-style-type: none;
197 | margin: 0;
198 | }
199 |
200 | .logged-in header .welcome-info .user-options ul button {
201 | padding: 5px 15px;
202 | border: none;
203 | background-color: transparent;
204 | color: var(--darkest-color);
205 | font-size: 1em;
206 | text-align: center;
207 | }
208 |
209 | .logged-in header .welcome-info .user-options ul button:hover {
210 | color: var(--secondary-color);
211 | }
212 |
213 |
214 | /* Main Content */
215 |
216 | .logged-in main {
217 | display: flex;
218 | flex-wrap: wrap;
219 | justify-content: space-between;
220 | flex: 1;
221 | }
222 |
223 |
224 | /* Recent Chat Names on left */
225 |
226 | .recent-chat {
227 | width: 30%;
228 | background-color: var(--sidebar-color);
229 | display: flex;
230 | flex-flow: column;
231 | }
232 |
233 | .recent-chat ul {
234 | overflow-y: scroll;
235 | flex: 1 1 1px;
236 | }
237 |
238 | .recent-chat ul li {
239 | border-bottom: 2px solid var(--sidebar-highlight-color);
240 | }
241 |
242 | .recent-chat ul li.selected {
243 | border: none;
244 | background-color: var(--sidebar-highlight-color);
245 | }
246 |
247 | .recent-chat ul li div {
248 | padding: 20px;
249 | }
250 |
251 | .recent-chat ul li h3.name {
252 | color: var(--main-color);
253 | font-size: 1.3em;
254 | transition: all 0.5s ease-in-out;
255 | }
256 |
257 | .recent-chat ul li:hover h3.name {
258 | color: var(--secondary-color);
259 | transition: all 0.5s ease-in-out;
260 | }
261 |
262 | .recent-chat ul li span.latest-message {
263 | color: var(--darkest-color);
264 | font-style: italic;
265 | padding: 5px 0 0;
266 | display: block;
267 | white-space: nowrap;
268 | overflow: hidden;
269 | text-overflow: ellipsis;
270 | }
271 |
272 | .recent-chat li.error-msg {
273 | color: var(--main-color);
274 | }
275 |
276 | .recent-chat .error-btn {
277 | background-color: var(--secondary-color);
278 | color: white;
279 | border: 0;
280 | padding: 10px;
281 | border-radius: 5px;
282 | margin-top: 10px;
283 | font-size: 0.9em;
284 | display: block;
285 | }
286 |
287 | .recent-chat .error-btn:hover {
288 | background-color: var(--secondary-lighter);
289 | }
290 |
291 |
292 | /* Current Chat Box on right */
293 |
294 | .current-chat {
295 | width: 70%;
296 | display: flex;
297 | justify-content: space-between;
298 | flex-flow: column;
299 | height: 100%;
300 | }
301 |
302 |
303 | /* Chat Home */
304 |
305 | .current-chat .chat-home {
306 | width: 100%;
307 | height: 100%;
308 | display: flex;
309 | align-items: center;
310 | justify-content: center;
311 | }
312 |
313 | .current-chat .chat-home button {
314 | background-color: var(--secondary-color);
315 | color: white;
316 | border: 0;
317 | padding: 15px;
318 | margin: 10px auto;
319 | font-weight: bold;
320 | font-size: 1.1em;
321 | border-radius: 10px;
322 | }
323 |
324 | .current-chat .chat-home button:hover {
325 | background-color: var(--secondary-lighter);
326 | }
327 |
328 |
329 | /* About the chatter at top */
330 |
331 | .current-chat .user-info {
332 | margin: 15px 20px 0;
333 | padding: 0 0 10px 0;
334 | border-bottom: 2px solid var(--sidebar-highlight-color);
335 | text-align: center;
336 | }
337 |
338 | .current-chat .user-info h2.chatter-name {
339 | color: var(--main-color);
340 | font-size: 1.8em;
341 | font-weight: bold;
342 | padding: 3px;
343 | }
344 |
345 | .current-chat .user-info p {
346 | color: var(--mid-color);
347 | padding: 5px;
348 | }
349 |
350 | .current-chat .user-info p.in-chat {
351 | font-size: 1.2em;
352 | font-weight: bold;
353 | }
354 |
355 | .current-chat .user-info p.chatter-info {
356 | font-style: italic;
357 | }
358 |
359 | .current-chat .user-info p.chatter-bio {
360 | color: var(--darkest-color);
361 | }
362 |
363 |
364 | /* Send Message Input at bottom */
365 |
366 | .current-chat .send-message {
367 | display: flex;
368 | justify-content: space-between;
369 | padding: 0 10px 10px;
370 | }
371 |
372 | .current-chat .send-message input {
373 | flex: 1;
374 | background-color: white;
375 | color: var(--darkest-color);
376 | padding: 15px;
377 | font-size: 1em;
378 | border: 1px solid var(--sidebar-highlight-color);
379 | }
380 |
381 | .current-chat .send-message button {
382 | background-color: var(--secondary-lighter);
383 | color: white;
384 | padding: 10px 25px;
385 | border: none;
386 | font-size: 1.1em;
387 | font-weight: bolder;
388 | }
389 |
390 | .current-chat .send-message input::placeholder,
391 | .unlogged-page .forms textarea::placeholder {
392 | color: var(--mid-color);
393 | font-style: italic;
394 | }
395 |
396 |
397 | /* Chat box in middle */
398 |
399 | .current-chat .messages {
400 | flex: 1 1 1px;
401 | padding: 0 10px;
402 | display: flex;
403 | overflow: auto;
404 | display: flex;
405 | flex-direction: column-reverse;
406 | }
407 |
408 | .current-chat .messages ol {
409 | display: inline-block;
410 | list-style-type: none;
411 | width: 100%;
412 | }
413 |
414 | .current-chat .messages ol.short {
415 | align-self: flex-end;
416 | }
417 |
418 | .current-chat .messages ol li {
419 | display: block;
420 | margin: 10px;
421 | }
422 |
423 | .current-chat .messages ol li .message {
424 | padding: 12px 15px;
425 | border-radius: 10px;
426 | max-width: 50%;
427 | width: auto;
428 | display: inline-block;
429 | color: white;
430 | line-height: 1.3;
431 | }
432 |
433 | .current-chat .messages ol li.received {
434 | text-align: left;
435 | }
436 |
437 | .current-chat .messages ol li.sent {
438 | text-align: right;
439 | }
440 |
441 | .current-chat .messages ol li.received .message {
442 | background-color: var(--secondary-color);
443 | }
444 |
445 | .current-chat .messages ol li.sent .message {
446 | background-color: var(--main-color);
447 | }
448 |
449 | .current-chat .messages ol li p.sent-time {
450 | color: var(--mid-color);
451 | text-transform: uppercase;
452 | font-size: 0.9em;
453 | margin: 5px 5px 15px;
454 | }
455 |
456 |
457 | /* EXTRA */
458 |
459 |
460 | /* Highlight selection */
461 |
462 | ::-moz-selection {
463 | /* Code for Firefox */
464 | color: var(--lightest-color);
465 | background: var(--secondary-lighter);
466 | }
467 |
468 | ::selection {
469 | color: var(--lightest-color);
470 | background: var(--secondary-lighter);
471 | }
472 |
473 |
474 | /* Scrollbar */
475 |
476 | ::-webkit-scrollbar {
477 | width: 15px;
478 | }
479 |
480 | ::-webkit-scrollbar-track {
481 | background: #f6f9f9;
482 | }
483 |
484 | ::-webkit-scrollbar-thumb {
485 | background: #e0e9e9;
486 | border-radius: 7px;
487 | border: 2px solid #f6f9f9;
488 | }
489 |
490 | ::-webkit-scrollbar-thumb:hover {
491 | background: #afb5b6;
492 | }
493 |
494 |
495 | /* @MEDIA QUERIES FOR RESPONSIVE SITE */
496 |
497 |
498 | /* Max width: 1024 */
499 |
500 | @media screen and (max-width: 1024px) {
501 | .unlogged-page .logo {
502 | width: calc(40% - 40px);
503 | margin: 0 20px;
504 | }
505 | .unlogged-page .forms {
506 | width: calc(60% - 40px);
507 | }
508 | .unlogged-page .forms button {
509 | font-size: 1em;
510 | }
511 | }
512 |
513 |
514 | /* Max width: 768 */
515 |
516 | @media screen and (max-width: 768px) {
517 | .unlogged-page {
518 | flex-direction: column;
519 | }
520 | .unlogged-page .logo {
521 | margin: 0;
522 | width: 80%;
523 | padding: 0 30px 30px;
524 | }
525 | .unlogged-page .forms {
526 | width: 80%;
527 | margin: 0;
528 | font-size: 1.1em;
529 | align-items: center;
530 | }
531 | .unlogged-page .forms button {
532 | margin: 0;
533 | align-items: center;
534 | font-size: 1em;
535 | border: none;
536 | }
537 | .current-chat .messages ol li .message {
538 | max-width: 60%;
539 | }
540 | }
541 |
542 |
543 | /* Max width: 680 */
544 |
545 | @media screen and (max-width: 680px) {
546 | body {
547 | font-size: 18px;
548 | }
549 | .unlogged-page .logo,
550 | .unlogged-page .forms {
551 | width: 100%;
552 | }
553 | .open-toggle,
554 | .close-toggle {
555 | display: block;
556 | }
557 | .logged-in header .open-toggle {
558 | width: 100%;
559 | margin: 15px 0 25px;
560 | }
561 | .logged-in header .open-toggle a {
562 | color: white;
563 | background-color: var(--secondary-color);
564 | padding: 10px;
565 | border-radius: 5px;
566 | }
567 | .logged-in main {
568 | position: relative;
569 | }
570 | .recent-chat {
571 | width: 80%;
572 | position: absolute;
573 | top: 0;
574 | bottom: 0;
575 | left: -100%;
576 | transition: all 0.5s ease-in-out;
577 | }
578 | .recent-chat a.close-toggle {
579 | position: absolute;
580 | left: calc(100% + 15px);
581 | top: 10px;
582 | color: white;
583 | font-size: 2em;
584 | }
585 | .recent-chat a.close-toggle:hover {
586 | color: var(--sidebar-highlight-color);
587 | transform: rotate(180deg);
588 | }
589 | .recent-chat::before {
590 | content: '';
591 | width: 25%;
592 | background-color: rgba(0, 0, 0, 0.3);
593 | transition: all 0.5s ease-in-out;
594 | position: absolute;
595 | top: 0;
596 | left: 0;
597 | bottom: 0;
598 | }
599 | .recent-chat:target {
600 | left: 0;
601 | transition: all 0.5s ease-in-out;
602 | }
603 | .recent-chat:target::before {
604 | transition: all 0.5s ease-in-out;
605 | left: 100%;
606 | }
607 | .current-chat {
608 | width: 100%;
609 | }
610 | .current-chat .messages ol li .message {
611 | max-width: 70%;
612 | }
613 | }
614 |
615 |
616 | /* Max width 480 */
617 |
618 | @media screen and (max-width: 480px) {
619 | .recent-chat li.error-msg {
620 | margin: 0 50px 0 0;
621 | }
622 | .logged-in header .logo {
623 | width: 100%;
624 | padding: 10px 0;
625 | }
626 | .logged-in header .logo img {
627 | height: auto;
628 | width: 100%;
629 | margin: 0;
630 | }
631 | .logged-in header .open-toggle {
632 | width: auto;
633 | order: 2;
634 | font-size: 0.9em;
635 | }
636 | .logged-in header .welcome-info {
637 | order: 3;
638 | margin-bottom: 15px;
639 | }
640 | .recent-chat {
641 | width: 100%;
642 | }
643 | .recent-chat a.close-toggle {
644 | left: calc(100% - 38px);
645 | color: var(--secondary-lighter);
646 | }
647 | .recent-chat a.close-toggle:hover {
648 | color: var(--secondary-color);
649 | }
650 | .recent-chat::before {
651 | display: none;
652 | }
653 | .current-chat .messages ol li .message {
654 | max-width: 80%;
655 | }
656 | }
--------------------------------------------------------------------------------
/public/assets/images/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JColeCodes/randm/7371875a62abc029bb34074a35b1282c7ceb8558/public/assets/images/logo-dark.png
--------------------------------------------------------------------------------
/public/assets/images/logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JColeCodes/randm/7371875a62abc029bb34074a35b1282c7ceb8558/public/assets/images/logo-light.png
--------------------------------------------------------------------------------
/public/assets/images/randm-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JColeCodes/randm/7371875a62abc029bb34074a35b1282c7ceb8558/public/assets/images/randm-icon.png
--------------------------------------------------------------------------------
/public/assets/js/delete-user.js:
--------------------------------------------------------------------------------
1 | async function deleteUser() {
2 | const logoutBtn = document.getElementById('logout-btn');
3 |
4 | const currentUserId = parseInt(
5 | document.querySelector('#random-btn').getAttribute('data-user')
6 | );
7 |
8 | const response = await fetch('/api/users/' + currentUserId, {
9 | method: 'delete',
10 | headers: { 'Content-Type': 'application/json' }
11 | });
12 |
13 | if (response.ok) {
14 | logoutBtn.click(); // Logs user out when account is deleted
15 | } else {
16 | alert(response.statusText);
17 | }
18 | }
19 |
20 | document.querySelector('#delete-btn').addEventListener('click', deleteUser);
--------------------------------------------------------------------------------
/public/assets/js/login.js:
--------------------------------------------------------------------------------
1 | async function loginFormHandler(event) {
2 | event.preventDefault();
3 |
4 | const email = document.querySelector('#email-login').value.trim();
5 | const password = document.querySelector('#password-login').value.trim();
6 |
7 | if (email && password) {
8 | const response = await fetch('/api/users/login', {
9 | method: 'post',
10 | body: JSON.stringify({
11 | email,
12 | password
13 | }),
14 | headers: { 'Content-Type': 'application/json' }
15 | });
16 |
17 | if (response.ok) {
18 | document.location.replace('/');
19 | } else {
20 | alert(response.statusText);
21 | }
22 | }
23 | }
24 |
25 | document.querySelector('.login-form').addEventListener('submit', loginFormHandler);
--------------------------------------------------------------------------------
/public/assets/js/logout.js:
--------------------------------------------------------------------------------
1 | async function logout() {
2 | const response = await fetch('/api/users/logout', {
3 | method: 'post',
4 | headers: { 'Content-Type': 'application/json' }
5 | });
6 |
7 | if (response.ok) {
8 | document.location.replace('/');
9 | } else {
10 | alert(response.statusText);
11 | }
12 | }
13 |
14 | document.querySelector('#logout-btn').addEventListener('click', logout);
--------------------------------------------------------------------------------
/public/assets/js/message.js:
--------------------------------------------------------------------------------
1 | async function sendMessageFormHandler(event) {
2 | event.preventDefault();
3 |
4 | const message_text = document.querySelector('#message').value.trim();
5 | // Get the id of the logged in user in the data-user attribute
6 | const sender_id = document.querySelector('#send-message-btn').getAttribute('data-user');
7 | // Get the end of the URL and assign to receiver_id
8 | let receiver_id = window.location.toString().split('/');
9 | receiver_id = parseInt(receiver_id[receiver_id.length - 1].split('#')[0]);
10 |
11 | if (message_text) {
12 | const response = await fetch('/api/messages', {
13 | method: 'POST',
14 | body: JSON.stringify({
15 | sender_id,
16 | receiver_id,
17 | message_text
18 | }),
19 | headers: { 'Content-Type': 'application/json' }
20 | });
21 |
22 | if (!response.ok) {
23 | alert(response.statusText);
24 | }
25 | }
26 | }
27 |
28 | document.querySelector('.send-message').addEventListener('submit', sendMessageFormHandler);
--------------------------------------------------------------------------------
/public/assets/js/random-chat.js:
--------------------------------------------------------------------------------
1 | async function getOtherUserId() {
2 | // Get current user's id
3 | const currentUserId = parseInt(
4 | document.querySelector('#random-btn').getAttribute('data-user')
5 | );
6 |
7 | // Get all users length as maximum
8 | fetch('/api/users')
9 | .then((res) => {
10 | return res.json();
11 | })
12 | .then((totalUsers) => {
13 | const usersArray = [];
14 |
15 | // Loop through each user and add them to a list if they are not current user
16 | totalUsers.forEach((user) => {
17 | if (user.id !== currentUserId) {
18 | usersArray.push(user.id);
19 | }
20 | });
21 |
22 | checkCurrentMessages(usersArray);
23 | });
24 | }
25 |
26 | // Get recent messages and compare users against all users
27 | async function checkCurrentMessages(usersArray) {
28 | fetch('/api/messages/recent', { method: 'GET' })
29 | .then((response) => response.json())
30 | .then((data) => {
31 | if (data.length > 0) {
32 | // Iterate through recent messages to make sure user isn't already chatting
33 | data.forEach((user) => {
34 | // If user exists, remove from user array
35 | if (usersArray.includes(user.id)) {
36 | usersArray.splice(usersArray.indexOf(user.id), 1);
37 | }
38 | });
39 | }
40 | // Get random number based on number of users (0 to array length)
41 | const randomUserId = Math.floor(Math.random() * usersArray.length);
42 | // Go to url for that user
43 | document.location.replace(`/chat/${usersArray[randomUserId]}`);
44 | });
45 | }
46 |
47 | document.getElementById('random-btn').addEventListener('click', getOtherUserId);
--------------------------------------------------------------------------------
/public/assets/js/recent.js:
--------------------------------------------------------------------------------
1 | const recentList = document.querySelector('#recent-list');
2 |
3 | async function displayRecentChat() {
4 | // Get recent messages
5 | const response = await fetch('/api/messages/recent', { method: 'GET' });
6 | const data = await response.json();
7 |
8 | // If data exists
9 | if (data.length > 0) {
10 | // Split page url
11 | var pageUrl = document.location.href.split('/');
12 | pageUrl = pageUrl[pageUrl.length - 1].split('#')[0];
13 |
14 | // For each recent chatters to display, create list element
15 | data.forEach((element) => {
16 | let recentLi = document.createElement('li');
17 | recentLi.setAttribute('id', `user-${element.id}`);
18 |
19 | // If currently on the page of, show it as selected
20 | if (element.id == pageUrl) {
21 | recentLi.className = 'selected';
22 | }
23 |
24 | // Link and text for display
25 | recentLi.innerHTML = ` ${
27 | element.first_name.charAt(0).toUpperCase() +
28 | element.first_name.slice(1)
29 | } ${element.last_name.charAt(0).toUpperCase()}.
30 |
31 |
${msgTime()}
`; 119 | 120 | // Append to chat messages list 121 | document.querySelector('#chat-messages').appendChild(messageLi); 122 | 123 | // Clear message input text box 124 | document.querySelector('#message').value = ''; 125 | } 126 | // Reset currId and pageId once message is sent and page displays message 127 | currId = 0; 128 | pageId = 0; 129 | }); -------------------------------------------------------------------------------- /seeds/index.js: -------------------------------------------------------------------------------- 1 | const seedUsers = require('./user-seeds'); 2 | const seedMessages = require('./message-seeds'); 3 | 4 | const sequelize = require('../config/connection'); 5 | 6 | const seedAll = async() => { 7 | await sequelize.sync({ force: true }); 8 | console.log('\n----- DATABASE SYNCED -----\n'); 9 | 10 | await seedUsers(); 11 | console.log('\n----- USERS SEEDED -----\n'); 12 | 13 | await seedMessages(); 14 | console.log('\n----- MESSAGES SEEDED -----\n'); 15 | 16 | process.exit(0); 17 | }; 18 | 19 | seedAll(); -------------------------------------------------------------------------------- /seeds/message-seeds.js: -------------------------------------------------------------------------------- 1 | const { Message } = require('../models'); 2 | 3 | const messageData = [{ 4 | sender_id: 1, 5 | receiver_id: 2, 6 | message_text: 'Hi, how are you?' 7 | }, 8 | { 9 | sender_id: 2, 10 | receiver_id: 1, 11 | message_text: 'Hi there - good, how about you? What kind of music do you like?' 12 | }, 13 | { 14 | sender_id: 1, 15 | receiver_id: 2, 16 | message_text: 'I love basically everything, been listening to jazz a lot today. What about you?' 17 | }, 18 | { 19 | sender_id: 2, 20 | receiver_id: 1, 21 | message_text: 'Nice, I like jazz and hip hop a lot!' 22 | }, 23 | { 24 | sender_id: 2, 25 | receiver_id: 3, 26 | message_text: 'Hi, what is your favorite food?' 27 | }, 28 | { 29 | sender_id: 3, 30 | receiver_id: 2, 31 | message_text: 'Hi! I love Thai food. What about you?' 32 | }, 33 | { 34 | sender_id: 2, 35 | receiver_id: 3, 36 | message_text: 'Ooh I love Thai but my favorite is Italian' 37 | } 38 | ]; 39 | 40 | const seedMessages = () => Message.bulkCreate(messageData); 41 | 42 | module.exports = seedMessages; -------------------------------------------------------------------------------- /seeds/user-seeds.js: -------------------------------------------------------------------------------- 1 | const { User } = require('../models'); 2 | 3 | const userData = [ 4 | { 5 | first_name: 'Sam', 6 | last_name: 'Smith', 7 | email: 'samsmith@email.com', 8 | password: 'password1', 9 | bio: 'I listen to all kinds of music every day', 10 | gender: 'non-binary', 11 | sexual_preference: 'pansexual', 12 | pronouns: 'they/them', 13 | birthday: 03 / 03 / 2003, 14 | }, 15 | { 16 | first_name: 'Laura', 17 | last_name: 'Lee', 18 | email: 'lauralee@email.com', 19 | password: 'password1', 20 | bio: 'I have over 50 plants', 21 | gender: 'female', 22 | sexual_preference: 'bisexual', 23 | pronouns: 'she/her', 24 | birthday: 01 / 01 / 2000, 25 | }, 26 | { 27 | first_name: 'Frankie', 28 | last_name: 'Gray', 29 | email: 'frankie@email.com', 30 | password: 'password1', 31 | bio: 'I love hiking and travelling', 32 | gender: 'male', 33 | sexual_preference: 'homosexual', 34 | pronouns: 'he/they', 35 | birthday: 07 / 07 / 1997, 36 | }, 37 | ]; 38 | 39 | const seedUsers = () => User.bulkCreate(userData, { individualHooks: true }); 40 | 41 | module.exports = seedUsers; 42 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const routes = require('./controllers'); 4 | const sequelize = require('./config/connection'); 5 | const session = require('express-session'); 6 | const exphbs = require('express-handlebars'); 7 | const helpers = require('./utils/helpers'); 8 | const hbs = exphbs.create({ helpers }); 9 | require('dotenv').config(); 10 | 11 | const app = express(); 12 | const PORT = process.env.PORT || 3001; 13 | 14 | const server = require('http').createServer(app); 15 | const io = require('socket.io')(server); 16 | 17 | const SequelizeStore = require('connect-session-sequelize')(session.Store); 18 | // Setup for cookies use 19 | const sess = { 20 | secret: process.env.SECRET_SECRET, 21 | cookie: {}, 22 | resave: false, 23 | saveUninitialized: true, 24 | store: new SequelizeStore({ 25 | db: sequelize 26 | }) 27 | }; 28 | 29 | app.use(session(sess)); 30 | app.use(express.json()); 31 | app.use(express.urlencoded({ extended: true })); 32 | app.use(express.static(path.join(__dirname, 'public'))); 33 | 34 | // Turn on routes 35 | app.use(routes); 36 | 37 | // Handlebars 38 | app.engine('handlebars', hbs.engine); 39 | app.set('view engine', 'handlebars'); 40 | 41 | // Socket.io 42 | io.on('connection', (socket) => { 43 | socket.on('new message', (message) => { 44 | io.emit('new message', message); 45 | }); 46 | }); 47 | 48 | // Turn on connection to db and server 49 | sequelize.sync({ force: false }).then(() => { 50 | server.listen(PORT, () => console.log(`Now listening on port ${PORT}`)); 51 | }); -------------------------------------------------------------------------------- /utils/filters.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getUserLatest: (messages, user, sessionId, paramId) => { 3 | // Filters for the current user via session id 4 | var currentUser = user.filter((user) => user.id == sessionId); 5 | currentUser = currentUser[0]; 6 | 7 | // Filters for current chatter via param id 8 | var currentChatter = user.filter((user) => user.id == paramId); 9 | currentChatter = currentChatter[0]; 10 | if (!paramId) { 11 | currentChatter = null; // Returns null if on home /chat page 12 | } 13 | 14 | // Filters for an array of most recent chat messages per chatter 15 | let latestChat = []; 16 | if (messages) { 17 | messages.forEach((message) => { 18 | user.forEach((user) => { 19 | if ( 20 | // If sender is user and user id is sender id 21 | (message.sender_id !== sessionId && user.id === message.sender_id) || 22 | // OR if receiver is user and user id is sender id 23 | (message.receiver_id !== sessionId && user.id === message.receiver_id) 24 | ) { 25 | // Only do if latestChat array does not already include user 26 | if (!latestChat.includes(user)) { 27 | // Set latest message and push to array 28 | user.latest_message = message.message_text; 29 | latestChat.push(user); 30 | } 31 | } 32 | }); 33 | }); 34 | } 35 | 36 | return { currentUser, currentChatter, latestChat }; 37 | } 38 | }; -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get_chat_class: (senderId, currentId) => { 3 | if (senderId !== currentId) { 4 | return 'received'; 5 | } 6 | return 'sent'; 7 | }, 8 | format_time: (time) => { 9 | return new Date(time).toLocaleString('en-US', { 10 | hour12: true, 11 | hourCycle: 'h12', 12 | hour: 'numeric', 13 | minute: '2-digit' 14 | }); 15 | }, 16 | capitalize_first_name: (string) => { 17 | return string.charAt(0).toUpperCase() + string.slice(1); 18 | }, 19 | last_name_initial: (string) => { 20 | return string.charAt(0).toUpperCase() + '.'; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /views/chat.handlebars: -------------------------------------------------------------------------------- 1 |