├── chatServer
├── .eslintignore
├── .eslintrc
├── .yo-rc.json
├── README.md
├── client
│ └── README.md
├── common
│ └── models
│ │ ├── message.js
│ │ ├── profile.js
│ │ ├── thread.js
│ │ ├── thread.json
│ │ ├── profile.json
│ │ └── message.json
├── server
│ ├── component-config.json
│ ├── middleware.development.json
│ ├── datasources.json
│ ├── boot
│ │ ├── authentication.js
│ │ └── root.js
│ ├── config.json
│ ├── middleware.json
│ ├── server.js
│ ├── model-config.json
│ └── chat-server.js
├── .editorconfig
└── package.json
├── chat
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── store
│ │ ├── reducers
│ │ │ ├── index.js
│ │ │ ├── authReducer.js
│ │ │ └── chatReducer.js
│ │ ├── actions
│ │ │ ├── authActions.js
│ │ │ └── chatActions.js
│ │ └── configureStore.js
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── App.css
│ ├── components
│ │ ├── partials
│ │ │ ├── Message.js
│ │ │ ├── ChatInput.js
│ │ │ ├── ThreadView.js
│ │ │ ├── Sidebar.js
│ │ │ ├── Login.js
│ │ │ └── Signup.js
│ │ └── pages
│ │ │ ├── Messenger.js
│ │ │ └── Auth.js
│ ├── index.js
│ ├── logo.svg
│ ├── App.js
│ ├── assets
│ │ └── css
│ │ │ └── swag.css
│ └── serviceWorker.js
├── config
│ ├── jest
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── pnpTs.js
│ ├── paths.js
│ ├── env.js
│ ├── modules.js
│ ├── webpackDevServer.config.js
│ └── webpack.config.js
├── scripts
│ ├── test.js
│ ├── start.js
│ └── build.js
└── package.json
└── README.md
/chatServer/.eslintignore:
--------------------------------------------------------------------------------
1 | /client/
--------------------------------------------------------------------------------
/chatServer/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "loopback"
3 | }
--------------------------------------------------------------------------------
/chatServer/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-loopback": {}
3 | }
--------------------------------------------------------------------------------
/chat/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/chatServer/README.md:
--------------------------------------------------------------------------------
1 | # My Application
2 |
3 | The project is generated by [LoopBack](http://loopback.io).
--------------------------------------------------------------------------------
/chatServer/client/README.md:
--------------------------------------------------------------------------------
1 | ## Client
2 |
3 | This is the place for your application front-end files.
4 |
--------------------------------------------------------------------------------
/chatServer/common/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(Message) {
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/chatServer/common/models/profile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(Profile) {
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/chatServer/common/models/thread.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(Thread) {
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/chat/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/favicon.ico
--------------------------------------------------------------------------------
/chat/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/logo192.png
--------------------------------------------------------------------------------
/chat/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/logo512.png
--------------------------------------------------------------------------------
/chatServer/server/component-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "loopback-component-explorer": {
3 | "mountPath": "/explorer",
4 | "generateOperationScopedModels": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/chat/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux';
2 | import auth from './authReducer';
3 | import chat from './chatReducer';
4 |
5 | export default combineReducers({
6 | auth,
7 | chat
8 | })
--------------------------------------------------------------------------------
/chatServer/server/middleware.development.json:
--------------------------------------------------------------------------------
1 | {
2 | "final:after": {
3 | "strong-error-handler": {
4 | "params": {
5 | "debug": true,
6 | "log": true
7 | }
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/chat/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/chat/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/chatServer/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
--------------------------------------------------------------------------------
/chatServer/server/datasources.json:
--------------------------------------------------------------------------------
1 | {
2 | "db1": {
3 | "name": "db1",
4 | "connector": "memory"
5 | },
6 | "db": {
7 | "host": "localhost",
8 | "port": 27017,
9 | "url": "",
10 | "database": "chatServer",
11 | "password": "",
12 | "name": "db",
13 | "user": "",
14 | "useNewUrlParser": true,
15 | "connector": "mongodb"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/chat/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/chatServer/server/boot/authentication.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2016. All Rights Reserved.
2 | // Node module: loopback-workspace
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | 'use strict';
7 |
8 | module.exports = function enableAuthentication(server) {
9 | // enable authentication
10 | server.enableAuth();
11 | };
12 |
--------------------------------------------------------------------------------
/chat/src/store/actions/authActions.js:
--------------------------------------------------------------------------------
1 | export const loggedIn = (data) => {
2 | return dispatch => {
3 | dispatch({
4 | type: 'LOGGEDIN',
5 | payload: data
6 | })
7 | }
8 | }
9 |
10 | export const logout = () =>{
11 | return dispatch => {
12 | dispatch ({
13 | type: 'LOGGEDOUT',
14 | payload: null
15 | })
16 | }
17 | }
--------------------------------------------------------------------------------
/chat/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/chatServer/server/boot/root.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2016. All Rights Reserved.
2 | // Node module: loopback-workspace
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | 'use strict';
7 |
8 | module.exports = function(server) {
9 | // Install a `/` route that returns server status
10 | const router = server.loopback.Router();
11 | router.get('/', server.loopback.status());
12 | server.use(router);
13 | };
14 |
--------------------------------------------------------------------------------
/chatServer/server/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "restApiRoot": "/api",
3 | "host": "0.0.0.0",
4 | "port": 3000,
5 | "remoting": {
6 | "context": false,
7 | "rest": {
8 | "handleErrors": false,
9 | "normalizeHttpPath": false,
10 | "xml": false
11 | },
12 | "json": {
13 | "strict": false,
14 | "limit": "100kb"
15 | },
16 | "urlencoded": {
17 | "extended": true,
18 | "limit": "100kb"
19 | },
20 | "cors": false
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/chatServer/common/models/thread.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Thread",
3 | "base": "PersistedModel",
4 | "idInjection": true,
5 | "options": {
6 | "validateUpsert": true
7 | },
8 | "properties": {
9 | "lastUpdated": {
10 | "type": "date"
11 | },
12 | "users": {
13 | "type": [
14 | "string"
15 | ]
16 | }
17 | },
18 | "validations": [],
19 | "relations": {
20 | "Messages" : {
21 | "type": "hasMany",
22 | "model": "Message"
23 | }
24 | },
25 | "acls": [],
26 | "methods": {}
27 | }
28 |
--------------------------------------------------------------------------------
/chatServer/common/models/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Profile",
3 | "base": "PersistedModel",
4 | "idInjection": true,
5 | "options": {
6 | "validateUpsert": true
7 | },
8 | "properties": {
9 | "name": {
10 | "type": "string"
11 | },
12 | "email": {
13 | "type": "string"
14 | },
15 | "created": {
16 | "type": "date"
17 | }
18 | },
19 | "validations": [],
20 | "relations": {
21 | "User": {
22 | "type": "belongsTo",
23 | "model": "User",
24 | "foreignKey": "userId"
25 | }
26 | },
27 | "acls": [],
28 | "methods": {}
29 | }
30 |
--------------------------------------------------------------------------------
/chat/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/chat/src/store/reducers/authReducer.js:
--------------------------------------------------------------------------------
1 | const defaultState = {
2 | token: null,
3 | user: {},
4 | }
5 |
6 | const auth = (State = defaultState, action) => {
7 | switch(action.type){
8 | case 'LOGGEDIN':
9 | return{
10 | ...State,
11 | token: action.payload.data.session.id,
12 | user: action.payload.data.user
13 | }
14 | case 'LOGGEDOUT':
15 | return{
16 | ...State,
17 | ...defaultState
18 | }
19 | default:
20 | return State
21 | }
22 | }
23 |
24 | export default auth;
--------------------------------------------------------------------------------
/chat/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import {persistStore, persistReducer} from 'redux-persist';
4 | import storage from 'redux-persist/lib/storage';
5 | import rootRoducer from './reducers';
6 |
7 | const persistConfig = {
8 | key: 'root',
9 | storage,
10 | }
11 |
12 | const persistedReducer = persistReducer(persistConfig, rootRoducer);
13 |
14 |
15 | export default () => {
16 | let store = createStore(persistedReducer, applyMiddleware(thunk));
17 | let persistor = persistStore(store);
18 | return {
19 | store,
20 | persistor
21 | }
22 | }
--------------------------------------------------------------------------------
/chat/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/chat/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/chatServer/common/models/message.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Message",
3 | "base": "PersistedModel",
4 | "idInjection": true,
5 | "options": {
6 | "validateUpsert": true
7 | },
8 | "properties": {
9 | "content": {
10 | "type": "string"
11 | },
12 | "date": {
13 | "type": "date"
14 | },
15 | "seenBy": {
16 | "type": "object"
17 | },
18 | "userId": {
19 | "type": "string"
20 | }
21 | },
22 | "validations": [],
23 | "relations": {
24 | "Thread": {
25 | "type": "belongsTo",
26 | "model": "Thread",
27 | "foreignKey": "threadId"
28 | },
29 | "User": {
30 | "type": "belongsTo",
31 | "model": "User",
32 | "foreignKey": "userId"
33 | }
34 | },
35 | "acls": [],
36 | "methods": {}
37 | }
38 |
--------------------------------------------------------------------------------
/chat/src/components/partials/Message.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 |
4 | class Message extends Component{
5 | render(){
6 | return(
7 |
8 |
9 |
10 | {this.props.msg.content}
11 |
12 |
13 | )
14 | }
15 | }
16 |
17 | const mapStateToProps = (state) => ({
18 | ...state.auth,
19 | ...state.chat
20 | })
21 |
22 | const mapDispatchToProps = (dispatch) => ({
23 |
24 | })
25 |
26 | export default connect(
27 | mapStateToProps,
28 | mapDispatchToProps
29 | )(Message);
--------------------------------------------------------------------------------
/chat/src/components/pages/Messenger.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {withRouter, Link} from 'react-router-dom';
3 | import {connect} from 'react-redux';
4 | import ThreadView from '../partials/ThreadView';
5 | import Sidebar from '../partials/Sidebar';
6 | import ChatInput from '../partials/ChatInput';
7 | class Messenger extends Component{
8 |
9 | render(){
10 | return(
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
20 | const mapStateToProps = state => ({
21 | ...state.auth,
22 | ...state.chat
23 | })
24 |
25 | const mapDispatchToProps = dispatch => ({
26 |
27 | })
28 |
29 | export default connect(
30 | mapStateToProps,
31 | mapDispatchToProps
32 | )(withRouter(Messenger));
--------------------------------------------------------------------------------
/chatServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chatServer",
3 | "version": "1.0.0",
4 | "main": "server/server.js",
5 | "engines": {
6 | "node": ">=6"
7 | },
8 | "scripts": {
9 | "lint": "eslint .",
10 | "start": "node .",
11 | "posttest": "npm run lint"
12 | },
13 | "dependencies": {
14 | "compression": "^1.0.3",
15 | "cors": "^2.5.2",
16 | "helmet": "^3.10.0",
17 | "loopback": "^3.22.0",
18 | "loopback-boot": "^2.6.5",
19 | "loopback-component-explorer": "^6.2.0",
20 | "loopback-connector-mongodb": "^4.2.0",
21 | "serve-favicon": "^2.0.1",
22 | "strong-error-handler": "^3.0.0",
23 | "ws": "^7.2.1"
24 | },
25 | "devDependencies": {
26 | "eslint": "^4.18.2",
27 | "eslint-config-loopback": "^8.0.0"
28 | },
29 | "repository": {
30 | "type": "",
31 | "url": ""
32 | },
33 | "license": "UNLICENSED",
34 | "description": "chatServer"
35 | }
36 |
--------------------------------------------------------------------------------
/chat/src/components/pages/Auth.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {withRouter, Link} from 'react-router-dom';
3 | import * as AuthActions from '../../store/actions/authActions';
4 | import Login from '../partials/Login';
5 | import Signup from '../partials/Signup';
6 | import {connect} from 'react-redux';
7 |
8 |
9 | class Auth extends Component{
10 | render(){
11 | return(
12 |
13 | {this.props.match.path === '/signup' ?
14 |
15 | :
16 |
17 | }
18 |
19 | )
20 | }
21 | }
22 |
23 | const mapStateToProps = state => ({
24 | ...state.auth
25 | })
26 |
27 | const mapDispatchToProps = dispatch => ({
28 |
29 | })
30 |
31 | export default connect(
32 | mapStateToProps,
33 | mapDispatchToProps
34 | )(withRouter(Auth));
--------------------------------------------------------------------------------
/chat/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import rootReducer from './store/reducers';
7 | import {Provider} from 'react-redux';
8 | import configureStore from './store/configureStore';
9 | import {PersistGate} from 'redux-persist/integration/react';
10 |
11 | const {store, persistor} = configureStore();
12 |
13 | ReactDOM.render(
14 |
15 |
16 |
17 |
18 |
19 |
20 | , document.getElementById('root'));
21 |
22 | // If you want your app to work offline and load faster, you can change
23 | // unregister() to register() below. Note this comes with some pitfalls.
24 | // Learn more about service workers: https://bit.ly/CRA-PWA
25 | serviceWorker.unregister();
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chat-Application-vai-Web-Socket
2 | This is a realtime-chat-application. This application covered by web-socket for Realtime messaging. Whole system developed by Bootstrap, ReacJS, Loopback API
3 |
4 | 
5 |
6 | ## Introduction
7 | This is a public code repository as open source for students and new learner about this things.
8 |
9 | I created a full Realtime Chat Application. I'm going to use React on the front end and Bootstrap, with NodeJS Loopback-API + Socket.io web socket library on the back end.
10 |
11 | # Setup:
12 | - Download Full Project
13 | - Copy all file from `src` directory from chat-application
14 | - Create new react app in your machine
15 | - Then replace new app src folder with `chat-application/src` files.
16 | - Use NodeJS + LoopBack to configure the backend. Try google to install both technology.
17 | - run ```npm i && npm start``` for both client and server side to start the development server
18 |
--------------------------------------------------------------------------------
/chatServer/server/middleware.json:
--------------------------------------------------------------------------------
1 | {
2 | "initial:before": {
3 | "loopback#favicon": {}
4 | },
5 | "initial": {
6 | "compression": {},
7 | "cors": {
8 | "params": {
9 | "origin": true,
10 | "credentials": true,
11 | "maxAge": 86400
12 | }
13 | },
14 | "helmet#xssFilter": {},
15 | "helmet#frameguard": {
16 | "params": {
17 | "action": "deny"
18 | }
19 | },
20 | "helmet#hsts": {
21 | "params": {
22 | "maxAge": 0,
23 | "includeSubDomains": true
24 | }
25 | },
26 | "helmet#hidePoweredBy": {},
27 | "helmet#ieNoOpen": {},
28 | "helmet#noSniff": {},
29 | "helmet#noCache": {
30 | "enabled": false
31 | }
32 | },
33 | "session": {},
34 | "auth": {},
35 | "parse": {},
36 | "routes": {
37 | "loopback#rest": {
38 | "paths": [
39 | "${restApiRoot}"
40 | ]
41 | }
42 | },
43 | "files": {},
44 | "final": {
45 | "loopback#urlNotFound": {}
46 | },
47 | "final:after": {
48 | "strong-error-handler": {}
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/chatServer/server/server.js:
--------------------------------------------------------------------------------
1 | // Copyright IBM Corp. 2016. All Rights Reserved.
2 | // Node module: loopback-workspace
3 | // This file is licensed under the MIT License.
4 | // License text available at https://opensource.org/licenses/MIT
5 |
6 | 'use strict';
7 |
8 | const loopback = require('loopback');
9 | const boot = require('loopback-boot');
10 |
11 | const app = module.exports = loopback();
12 |
13 | app.start = function() {
14 | // start the web server
15 | return app.listen(8080, function() {
16 | app.emit('started');
17 | const baseUrl = app.get('url').replace(/\/$/, '');
18 | console.log('Web server listening at: %s', baseUrl);
19 | if (app.get('loopback-component-explorer')) {
20 | const explorerPath = app.get('loopback-component-explorer').mountPath;
21 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
22 | }
23 | });
24 | };
25 |
26 | // Bootstrap the application, configure models, datasources and middleware.
27 | // Sub-apps like REST API are mounted via boot scripts.
28 | boot(app, __dirname, function(err) {
29 | if (err) throw err;
30 |
31 | // start the server if `$ node server.js`
32 | if (require.main === module)
33 | app.start();
34 | });
35 |
--------------------------------------------------------------------------------
/chatServer/server/model-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "sources": [
4 | "loopback/common/models",
5 | "loopback/server/models",
6 | "../common/models",
7 | "./models"
8 | ],
9 | "mixins": [
10 | "loopback/common/mixins",
11 | "loopback/server/mixins",
12 | "../common/mixins",
13 | "./mixins"
14 | ]
15 | },
16 | "User": {
17 | "dataSource": "db",
18 | "relations": {
19 | "Profile": {
20 | "type": "hasOne",
21 | "model": "Profile",
22 | "foreignKey": ""
23 | },
24 | "Messages": {
25 | "type": "hasMany",
26 | "model": "Message"
27 | }
28 | }
29 | },
30 | "AccessToken": {
31 | "dataSource": "db",
32 | "public": false
33 | },
34 | "ACL": {
35 | "dataSource": "db",
36 | "public": false
37 | },
38 | "RoleMapping": {
39 | "dataSource": "db",
40 | "public": false,
41 | "options": {
42 | "strictObjectIDCoercion": true
43 | }
44 | },
45 | "Role": {
46 | "dataSource": "db",
47 | "public": false
48 | },
49 | "Thread": {
50 | "dataSource": "db",
51 | "public": true
52 | },
53 | "Message": {
54 | "dataSource": "db",
55 | "public": true
56 | },
57 | "Profile": {
58 | "dataSource": "db",
59 | "public": true
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/chat/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const camelcase = require('camelcase');
5 |
6 | // This is a custom Jest transformer turning file imports into filenames.
7 | // http://facebook.github.io/jest/docs/en/webpack.html
8 |
9 | module.exports = {
10 | process(src, filename) {
11 | const assetFilename = JSON.stringify(path.basename(filename));
12 |
13 | if (filename.match(/\.svg$/)) {
14 | // Based on how SVGR generates a component name:
15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16 | const pascalCaseFilename = camelcase(path.parse(filename).name, {
17 | pascalCase: true,
18 | });
19 | const componentName = `Svg${pascalCaseFilename}`;
20 | return `const React = require('react');
21 | module.exports = {
22 | __esModule: true,
23 | default: ${assetFilename},
24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25 | return {
26 | $$typeof: Symbol.for('react.element'),
27 | type: 'svg',
28 | ref: ref,
29 | key: null,
30 | props: Object.assign({}, props, {
31 | children: ${assetFilename}
32 | })
33 | };
34 | }),
35 | };`;
36 | }
37 |
38 | return `module.exports = ${assetFilename};`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/chat/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--watchAll') === -1 &&
45 | argv.indexOf('--watchAll=false') === -1
46 | ) {
47 | // https://github.com/facebook/create-react-app/issues/5210
48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
49 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
50 | }
51 |
52 |
53 | jest.run(argv);
54 |
--------------------------------------------------------------------------------
/chat/src/components/partials/ChatInput.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {withRouter, Link} from 'react-router-dom';
3 | import {connect} from 'react-redux';
4 |
5 | class ChatInput extends Component{
6 | constructor(props){
7 | super(props);
8 |
9 | this.state={
10 | content: ''
11 | }
12 | }
13 |
14 | sendMessage = (e) =>{
15 | e.preventDefault();
16 |
17 | const msg = {
18 | threadId: this.props.match.params.threadId,
19 | userId: this.props.user.id,
20 | content: this.state.content,
21 | date: new Date()
22 | }
23 |
24 | this.props.socket.send(JSON.stringify({
25 | type: 'ADD_MESSAGE',
26 | threadId: msg.threadId,
27 | message: msg
28 | }));
29 |
30 | this.setState({content: ''});
31 | }
32 |
33 | render(){
34 | return(
35 |
49 | );
50 | }
51 | }
52 |
53 | const mapStateToProps = state => ({
54 | ...state.auth,
55 | ...state.chat
56 | })
57 |
58 | const mapDispatchToProps = dispatch => ({
59 |
60 | })
61 |
62 | export default connect(
63 | mapStateToProps,
64 | mapDispatchToProps
65 | )(withRouter(ChatInput));
--------------------------------------------------------------------------------
/chat/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
28 | React App
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/chat/src/components/partials/ThreadView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {withRouter, Link} from 'react-router-dom';
3 | import {connect} from 'react-redux';
4 | import Message from './Message';
5 |
6 | class ThreadView extends Component{
7 |
8 | componentDidMount(){
9 | this.init();
10 | }
11 | componentDidUpdate(props){
12 | if(props.match.params.threadId !== this.props.match.params.threadId){
13 | this.init();
14 | }
15 | }
16 |
17 | init = () => {
18 | let currentThread = this.props.threads.filter(t => t.id === this.props.match.params.threadId)[0];
19 | if(currentThread && this.props.socket.readyState){
20 | let skip = currentThread.Messages || 0;
21 | this.props.socket.send(JSON.stringify({
22 | type: 'THREAD_LOAD',
23 | data: {
24 | threadId: this.props.match.params.threadId,
25 | skip: skip
26 | }
27 | }))
28 | }
29 | }
30 |
31 | render(){
32 | return(
33 |
34 | {this.props.threads.filter(thread => thread.id === this.props.match.params.threadId).map((thread, i) => {
35 | return(
36 |
37 | {thread.Messages.map((msg, mi) => {
38 | return(
39 | p.id === msg.userId)[0]}/>
40 | )
41 | })}
42 |
43 | )
44 | })}
45 |
46 | );
47 | }
48 | }
49 |
50 | const mapStateToProps = state => ({
51 | ...state.auth,
52 | ...state.chat
53 | })
54 |
55 | const mapDispatchToProps = dispatch => ({
56 |
57 | })
58 |
59 | export default connect(
60 | mapStateToProps,
61 | mapDispatchToProps
62 | )(withRouter(ThreadView));
--------------------------------------------------------------------------------
/chat/src/store/reducers/chatReducer.js:
--------------------------------------------------------------------------------
1 | const defaultState = {
2 | socket: null,
3 | message: '',
4 | threads: [],
5 | currentThread: '',
6 | users: []
7 | }
8 |
9 | const chat = (state = defaultState, action) =>{
10 | switch(action.type){
11 | case 'SETUP_SOCKET':
12 | return {
13 | ...state,
14 | socket: action.payload
15 | }
16 | case 'GOT_USERS':
17 | console.log('Getting User', action.payload);
18 | return {
19 | ...state,
20 | users: action.payload
21 | }
22 | case 'ADD_THREAD':
23 | return {
24 | ...state,
25 | threads: state.threads.filter(t => t.id === action.payload.id).length === 0 ?
26 | state.threads.concat(action.payload)
27 | : state.threads
28 | }
29 |
30 | case 'INITIAL_THREADS':
31 | return {
32 | ...state,
33 | threads: action.payload
34 | }
35 | case 'ADD_SINGLE_MESSAGE':
36 | return{
37 | ...state,
38 | threads: state.threads.map(thread => {
39 | if(thread.id === action.payload.threadId){
40 | return {
41 | ...thread,
42 | Messages: thread.Messages.concat(action.payload.message)
43 | }
44 | }
45 | else{
46 | return thread
47 | }
48 | })
49 | }
50 | case 'ADD_MESSAGES_TO_THREAD':
51 | return {
52 | ...state,
53 | threads: state.threads.map(t => {
54 | if(t.id === action.payload.threadId){
55 | return {
56 | ...t,
57 | Messages: action.payload.messages.concat(t.Messages)
58 | }
59 | }
60 | else{
61 | return t
62 | }
63 | })
64 | }
65 | default:
66 | return state
67 | }
68 | }
69 |
70 | export default chat;
--------------------------------------------------------------------------------
/chat/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/chat/src/store/actions/chatActions.js:
--------------------------------------------------------------------------------
1 | import * as AuthActions from './authActions';
2 |
3 | export const setupSocket = (token, userId) => {
4 | return dispatch => {
5 | const socket = new WebSocket('ws://localhost:8080');
6 | socket.onopen = () => {
7 | if(token){
8 | socket.send(JSON.stringify({
9 | type: 'CONNECT_WITH_TOKEN',
10 | data: {
11 | token: token,
12 | userId: userId
13 | }
14 | }))
15 | dispatch({
16 | type: 'SETUP_SOCKET',
17 | payload: socket
18 | });
19 | }
20 | else{
21 | dispatch({
22 | type: 'SETUP_SOCKET',
23 | payload: socket
24 | });
25 | }
26 | }
27 |
28 | socket.onmessage = (message) => {
29 | console.log('Message', message);
30 | let data = JSON.parse(message.data);
31 | switch(data.type){
32 | case 'LOGGEDIN':
33 | dispatch(AuthActions.loggedIn(data));
34 | break;
35 | case 'GOT_USERS':
36 | dispatch({
37 | type: 'GOT_USERS',
38 | payload: data.data.users
39 | })
40 | break;
41 | case 'ADD_THREAD':
42 | dispatch({
43 | type: 'ADD_THREAD',
44 | payload: data.data
45 | });
46 | break;
47 | case 'INITIAL_THREADS':
48 | dispatch({
49 | type: 'INITIAL_THREADS',
50 | payload: data.data
51 | });
52 | break;
53 | case 'GOT_MESSAGES':
54 | dispatch({
55 | type: 'ADD_MESSAGES_TO_THREAD',
56 | payload: {
57 | threadId: data.threadId,
58 | messages: data.messages
59 | }
60 | })
61 | break;
62 | case 'ADD_MESSAGE_TO_THREAD':
63 | dispatch({
64 | type: 'ADD_SINGLE_MESSAGE',
65 | payload: {
66 | threadId: data.threadId,
67 | message: data.message
68 | }
69 | })
70 | document.getElementById('main-view').scrollTop = document.getElementById('main-view').scrollHeight;
71 | default:
72 | //do nothing
73 | }
74 | }
75 |
76 | }
77 | }
--------------------------------------------------------------------------------
/chat/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebook/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(inputPath, needsSlash) {
15 | const hasSlash = inputPath.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return inputPath.substr(0, inputPath.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${inputPath}/`;
20 | } else {
21 | return inputPath;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right