├── frontend
├── .env.development
├── src
│ ├── hooks
│ │ ├── index.ts
│ │ └── useDebounce.ts
│ ├── layouts
│ │ ├── index.ts
│ │ ├── auth.tsx
│ │ └── dashboard.tsx
│ ├── pages
│ │ ├── auth
│ │ │ ├── index.ts
│ │ │ ├── sign-in.tsx
│ │ │ └── sign-up.tsx
│ │ └── dashboard
│ │ │ ├── index.ts
│ │ │ ├── notification.tsx
│ │ │ ├── sidebar.tsx
│ │ │ └── home.tsx
│ ├── components
│ │ ├── index.ts
│ │ ├── wrapper.tsx
│ │ ├── Image.tsx
│ │ └── Account.tsx
│ ├── services
│ │ ├── index.ts
│ │ ├── auth.ts
│ │ ├── instance.ts
│ │ └── dashboard.ts
│ ├── redux
│ │ ├── actions
│ │ │ ├── index.ts
│ │ │ ├── visibility.ts
│ │ │ ├── listMessage.ts
│ │ │ └── currentUser.ts
│ │ ├── store.ts
│ │ ├── initState.ts
│ │ └── reducers
│ │ │ ├── rootReducer.ts
│ │ │ ├── visibility.ts
│ │ │ ├── listMessage.ts
│ │ │ └── currentUser.ts
│ ├── socket.ts
│ ├── setupTests.ts
│ ├── index.tsx
│ ├── reportWebVitals.ts
│ ├── App.tsx
│ ├── routes.tsx
│ ├── types.ts
│ └── style.css
├── .env.production
├── public
│ ├── robots.txt
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── logo.svg
│ └── index.html
├── postcss.config.js
├── .gitignore
├── vercel.json
├── tsconfig.json
├── tailwind.config.js
└── package.json
├── backend
├── nodemon.json
├── src
│ ├── utils
│ │ └── types.ts
│ ├── routes
│ │ ├── authRoute.ts
│ │ ├── messageRoute.ts
│ │ ├── index.ts
│ │ └── userRoute.ts
│ ├── configs
│ │ └── db.ts
│ ├── models
│ │ ├── MessageModel.ts
│ │ └── UserModel.ts
│ ├── server.ts
│ ├── middleware
│ │ ├── token.ts
│ │ └── auth.ts
│ ├── socket.ts
│ └── controllers
│ │ ├── userController.ts
│ │ └── messageController.ts
├── .env
├── build
│ ├── utils
│ │ └── types.js
│ ├── models
│ │ ├── MessageModel.js
│ │ └── UserModel.js
│ ├── routes
│ │ ├── index.js
│ │ ├── authRoute.js
│ │ ├── messageRoute.js
│ │ └── userRoute.js
│ ├── server.js
│ ├── configs
│ │ └── db.js
│ ├── middleware
│ │ ├── token.js
│ │ └── auth.js
│ ├── socket.js
│ └── controllers
│ │ ├── userController.js
│ │ └── messageController.js
├── package.json
└── tsconfig.json
├── .gitignore
├── restAPI.http
└── README.md
/frontend/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL=http://localhost:3001/api
2 |
--------------------------------------------------------------------------------
/frontend/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export {default as useDebounce } from './useDebounce';
--------------------------------------------------------------------------------
/frontend/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL=https://chat-app-u9j0.onrender.com/api
2 |
--------------------------------------------------------------------------------
/frontend/src/layouts/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./auth";
2 | export * from "./dashboard";
3 |
--------------------------------------------------------------------------------
/frontend/src/pages/auth/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./sign-in";
2 | export * from "./sign-up";
3 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vo-Huy-Khoa/chat-app-mern/HEAD/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Vo-Huy-Khoa/chat-app-mern/HEAD/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Account";
2 | export * from "./Image";
3 | export * from "./wrapper";
4 |
--------------------------------------------------------------------------------
/frontend/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./instance";
2 | export * from "./auth";
3 | export * from "./dashboard";
4 |
--------------------------------------------------------------------------------
/frontend/src/pages/dashboard/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./home";
2 | export * from "./notification";
3 | export * from "./sidebar";
4 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./currentUser";
2 | export * from "./listMessage";
3 | export * from "./visibility";
4 |
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | // postcss.config.js
2 | module.exports = {
3 | plugins: [require("tailwindcss"), require("autoprefixer")],
4 | };
5 |
--------------------------------------------------------------------------------
/backend/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["backend/src"],
3 | "ext": ".ts,.js",
4 | "ignore": [],
5 | "exec": "npx ts-node ./src/server.ts"
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/src/components/wrapper.tsx:
--------------------------------------------------------------------------------
1 | export const Wrapper = ({ children }: any) => {
2 | return
{children}
;
3 | };
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /backend/node_modules
5 | /frontend/node_modules
6 |
--------------------------------------------------------------------------------
/frontend/src/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import rootReducer from "./reducers/rootReducer";
3 |
4 | const store = createStore(rootReducer);
5 |
6 | export default store;
7 |
--------------------------------------------------------------------------------
/frontend/src/socket.ts:
--------------------------------------------------------------------------------
1 | import io from "socket.io-client";
2 |
3 | const url = "https://chat-app-u9j0.onrender.com";
4 | const socket = io(url, {
5 | transports: ["websocket"],
6 | });
7 |
8 | export default socket;
9 |
--------------------------------------------------------------------------------
/backend/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | export const URL_LOCALHOST =
2 | process.env.URL_LOCALHOST || "http://localhost:3000";
3 | export const URL_PRODUCTION =
4 | process.env.URL_PRODUCTION || "https://chatapp-vo-huy-khoa.vercel.app";
5 | export const PORT = process.env.PORT || "3001";
6 |
--------------------------------------------------------------------------------
/frontend/src/setupTests.ts:
--------------------------------------------------------------------------------
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';
6 |
--------------------------------------------------------------------------------
/backend/.env:
--------------------------------------------------------------------------------
1 | PORT = 3001
2 | MONGO_URL=mongodb+srv://vohuykhoa:vohuykhoa@cluster0.nppkasl.mongodb.net/?retryWrites=true&w=majority
3 | JWT_SECRET=jwttoken
4 | REFRESH_TOKEN_SECRET=refreshtoken
5 | URL_LOCALHOST=http://localhost:3000
6 | URL_PRODUCTION=https://chatapp-vo-huy-khoa.vercel.app
7 |
--------------------------------------------------------------------------------
/frontend/src/components/Image.tsx:
--------------------------------------------------------------------------------
1 | export const Image = ({ src, width = "32px", height = "32px" }: any) => {
2 | return (
3 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/backend/src/routes/authRoute.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import * as Auth from "../middleware/auth";
3 |
4 | const router = Router();
5 |
6 | router.post("/login", Auth.Login);
7 | router.post("/logout", Auth.Logout);
8 | router.post("/refreshToken", Auth.RefreshToken);
9 | router.post("/register", Auth.Register);
10 |
11 | export default router;
12 |
--------------------------------------------------------------------------------
/backend/build/utils/types.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.PORT = exports.URL_PRODUCTION = exports.URL_LOCALHOST = void 0;
4 | exports.URL_LOCALHOST = process.env.URL_LOCALHOST || "http://localhost:3000";
5 | exports.URL_PRODUCTION = process.env.URL_PRODUCTION || "https://chatapp-vo-huy-khoa.vercel.app";
6 | exports.PORT = process.env.PORT || "3001";
7 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/backend/src/configs/db.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | mongoose.set("strictQuery", false);
3 | const connect = async () => {
4 | const url = process.env.MONGO_URL || "";
5 | return await mongoose
6 | .connect(url)
7 | .then(() => {
8 | console.log("Database connect successfully");
9 | })
10 | .catch(() => {
11 | console.log("Database connect fail");
12 | });
13 | };
14 |
15 | export default connect;
16 |
--------------------------------------------------------------------------------
/frontend/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { Provider } from "react-redux";
4 | import App from "./App";
5 | import store from "./redux/store";
6 | const root = ReactDOM.createRoot(
7 | document.getElementById("root") as HTMLElement
8 | );
9 | root.render(
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/frontend/src/redux/initState.ts:
--------------------------------------------------------------------------------
1 | import { IUser, selectMessageType } from "../types";
2 |
3 | export const Visibility = "sidebar";
4 | export const initialState: IUser = {
5 | _id: "",
6 | avatar:
7 | "https://cdn.shopify.com/shopifycloud/shopify/assets/no-image-2048-5e88c1b20e087fb7bbe9a3771824e743c244f437e4f8ba93bbf7b11b53f7824c_grande.gif",
8 | fullname: "",
9 | username: "",
10 | };
11 | export const initMessage: selectMessageType = [];
12 |
--------------------------------------------------------------------------------
/backend/src/routes/messageRoute.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import * as Token from "../middleware/token";
3 | import messageController from "../controllers/messageController";
4 |
5 | const router = Router();
6 |
7 | router.post("/list", Token.authToken, messageController.list);
8 | router.post("/find", Token.authToken, messageController.find);
9 | router.post("/create", Token.authToken, messageController.create);
10 |
11 | export default router;
12 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const useDebounce = (value: string, delay: number) => {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => setDebouncedValue(value), delay);
8 | return () => clearTimeout(handler);
9 | }, [value, delay]);
10 |
11 | return debouncedValue;
12 | };
13 |
14 | export default useDebounce;
15 |
--------------------------------------------------------------------------------
/frontend/src/layouts/auth.tsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import { routes } from "../routes";
3 |
4 | export const Auth = () => {
5 | return (
6 |
7 |
8 | {routes.map(
9 | ({ layout, pages }) =>
10 | layout === "auth" &&
11 | pages.map(({ path, element }) => (
12 |
13 | ))
14 | )}
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/backend/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import Router from "express";
3 | import authRoute from "./authRoute";
4 | import userRoute from "./userRoute";
5 | import messageRoute from "./messageRoute";
6 |
7 | const router = Router();
8 | const routes = (app: express.Application) => {
9 | router.use("/auth", authRoute);
10 | router.use("/user", userRoute);
11 | router.use("/message", messageRoute);
12 |
13 | return app.use("/api", router);
14 | };
15 |
16 | export default routes;
17 |
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/visibility.ts:
--------------------------------------------------------------------------------
1 | import { SelectVisibilityAction } from "../../types";
2 |
3 | export enum VisibilityActionTypes {
4 | SET_VISIBILITY = "SET_VISIBILITY",
5 | CLEAR_VISIBILITY = "CLEAR_VISIBILITY",
6 | }
7 |
8 | export const setVisibility = (visibility: string): SelectVisibilityAction => ({
9 | type: VisibilityActionTypes.SET_VISIBILITY,
10 | payload: visibility,
11 | });
12 |
13 | export const clearVisibility = (): SelectVisibilityAction => ({
14 | type: VisibilityActionTypes.CLEAR_VISIBILITY,
15 | });
16 |
--------------------------------------------------------------------------------
/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Routes, Route, Navigate, BrowserRouter } from "react-router-dom";
2 | import { Auth, Dashboard } from "./layouts";
3 | import "./style.css";
4 |
5 | function App() {
6 | return (
7 |
8 |
9 | } />
10 | } />
11 | } />
12 |
13 |
14 | );
15 | }
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/listMessage.ts:
--------------------------------------------------------------------------------
1 | import { ClearSelectMessageAction, SetSelectMessageAction } from "../../types";
2 |
3 | export enum SelectMessageActionTypes {
4 | SET_MESSAGE = "SET_MESSAGE",
5 | CLEAR_MESSAGE = "CLEAR_MESSAGE",
6 | }
7 |
8 | export const setSelectMessage = (message: any): SetSelectMessageAction => ({
9 | type: SelectMessageActionTypes.SET_MESSAGE,
10 | payload: message,
11 | });
12 |
13 | export const clearSelectMessage = (): ClearSelectMessageAction => ({
14 | type: SelectMessageActionTypes.CLEAR_MESSAGE,
15 | });
16 |
--------------------------------------------------------------------------------
/frontend/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "headers": [
3 | {
4 | "source": "/api/(.*)",
5 | "headers": [
6 | { "key": "Access-Control-Allow-Credentials", "value": "true" },
7 | { "key": "Access-Control-Allow-Origin", "value": "*" },
8 | { "key": "Access-Control-Allow-Methods", "value": "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
9 | { "key": "Access-Control-Allow-Headers", "value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }
10 | ]
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { currentUserReducer, currentReceiverReducer } from "./currentUser";
3 | import currentMessageReducer from "./listMessage";
4 | import currentVisibilityReducer from "./visibility";
5 |
6 | const rootReducer = combineReducers({
7 | currentUser: currentUserReducer,
8 | currentMessage: currentMessageReducer,
9 | currentVisibility: currentVisibilityReducer,
10 | currentReceiver: currentReceiverReducer,
11 | });
12 |
13 | export type RootState = ReturnType;
14 |
15 | export default rootReducer;
16 |
--------------------------------------------------------------------------------
/backend/build/models/MessageModel.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const mongoose_1 = require("mongoose");
4 | const messageSchema = new mongoose_1.Schema({
5 | senderID: {
6 | type: mongoose_1.Schema.Types.ObjectId,
7 | ref: "User",
8 | },
9 | receiverID: {
10 | type: mongoose_1.Schema.Types.ObjectId,
11 | ref: "User",
12 | },
13 | message: { type: String },
14 | }, {
15 | timestamps: true,
16 | });
17 | const MessageModel = (0, mongoose_1.model)("Message", messageSchema);
18 | exports.default = MessageModel;
19 |
--------------------------------------------------------------------------------
/frontend/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 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/visibility.ts:
--------------------------------------------------------------------------------
1 | import { SelectVisibilityAction } from "../../types";
2 | import { VisibilityActionTypes } from "../actions/visibility";
3 | import { Visibility } from "../initState";
4 |
5 | const currentVisibilityReducer = (
6 | state = Visibility,
7 | action: SelectVisibilityAction
8 | ) => {
9 | switch (action.type) {
10 | case VisibilityActionTypes.SET_VISIBILITY:
11 | return action.payload;
12 | case VisibilityActionTypes.CLEAR_VISIBILITY:
13 | return Visibility;
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | export default currentVisibilityReducer;
20 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/listMessage.ts:
--------------------------------------------------------------------------------
1 | import { SelectMessageAction } from "../../types";
2 | import { SelectMessageActionTypes } from "../actions/listMessage";
3 | import { initMessage } from "../initState";
4 |
5 | const currentMessageReducer = (
6 | state = initMessage,
7 | action: SelectMessageAction
8 | ) => {
9 | switch (action.type) {
10 | case SelectMessageActionTypes.SET_MESSAGE:
11 | return [...action.payload];
12 | case SelectMessageActionTypes.CLEAR_MESSAGE:
13 | return [...initMessage];
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | export default currentMessageReducer;
20 |
--------------------------------------------------------------------------------
/backend/src/routes/userRoute.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import * as Token from "../middleware/token";
3 | import userController from "../controllers/userController";
4 |
5 | const router = Router();
6 |
7 | router.post("/search", Token.authToken, userController.search);
8 | router.get("/profile/:id", Token.authToken, userController.profile);
9 | router.put("/update/:id", Token.authToken, userController.update);
10 | router.delete("/delete/:id", Token.authToken, userController.destroy);
11 | router.delete("/destroy", Token.authToken, userController.destroyAll);
12 | router.get("/", Token.authToken, userController.list);
13 |
14 | export default router;
15 |
--------------------------------------------------------------------------------
/backend/src/models/MessageModel.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model } from "mongoose";
2 |
3 | interface IMessage {
4 | senderID: {
5 | type: Schema.Types.ObjectId;
6 | ref: "User";
7 | };
8 | receiverID: {
9 | type: Schema.Types.ObjectId;
10 | ref: "User";
11 | };
12 | message: string;
13 | }
14 |
15 | const messageSchema = new Schema(
16 | {
17 | senderID: {
18 | type: Schema.Types.ObjectId,
19 | ref: "User",
20 | },
21 | receiverID: {
22 | type: Schema.Types.ObjectId,
23 | ref: "User",
24 | },
25 | message: { type: String },
26 | },
27 | {
28 | timestamps: true,
29 | }
30 | );
31 |
32 | const MessageModel = model("Message", messageSchema);
33 |
34 | export default MessageModel;
35 |
--------------------------------------------------------------------------------
/backend/build/routes/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const authRoute_1 = __importDefault(require("./authRoute"));
8 | const userRoute_1 = __importDefault(require("./userRoute"));
9 | const messageRoute_1 = __importDefault(require("./messageRoute"));
10 | const router = (0, express_1.default)();
11 | const routes = (app) => {
12 | router.use("/auth", authRoute_1.default);
13 | router.use("/user", userRoute_1.default);
14 | router.use("/message", messageRoute_1.default);
15 | return app.use("/api", router);
16 | };
17 | exports.default = routes;
18 |
--------------------------------------------------------------------------------
/frontend/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import { Home } from "./pages/dashboard";
2 | import { Login, Register } from "./pages/auth";
3 |
4 | export const routes = [
5 | {
6 | layout: "auth",
7 | pages: [
8 | {
9 | path: "/sign-in",
10 | element: ,
11 | },
12 | {
13 | path: "/sign-up",
14 | element: ,
15 | },
16 | ],
17 | },
18 | {
19 | layout: "dashboard",
20 | pages: [
21 | {
22 | path: "/home",
23 | element: ,
24 | },
25 | ],
26 | },
27 | ];
28 |
29 | export const API_URL = {
30 | SIGN_IN: "auth/login",
31 | SIGN_UP: "auth/register",
32 | LOGOUT: "auth/logout",
33 | REFRESH_TOKEN: "auth/refreshToken",
34 | PROFILE: "user/profile/",
35 | SEARCH: "user/search",
36 | USER: "user/",
37 | MESSAGES: "message/list",
38 | MESSAGE: "message/find",
39 | CREATE_MESSAGE: "message/create",
40 | };
41 |
--------------------------------------------------------------------------------
/backend/src/server.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import cors from "cors";
3 | import connect from "./configs/db";
4 | import dotenv from "dotenv";
5 | import routes from "./routes";
6 | import http from "http";
7 | import { handleSocket } from "./socket";
8 | import { PORT, URL_LOCALHOST, URL_PRODUCTION } from "./utils/types";
9 |
10 | dotenv.config();
11 |
12 | const app = express();
13 |
14 | const server = http.createServer(app);
15 |
16 | const allowedOrigins: string[] = [URL_LOCALHOST, URL_PRODUCTION];
17 |
18 | const corsOptions: cors.CorsOptions = {
19 | origin: allowedOrigins,
20 | credentials: true,
21 | };
22 |
23 | app.use(cors(corsOptions));
24 |
25 | app.use(express.json());
26 |
27 | app.use(express.urlencoded({ extended: true }));
28 |
29 | routes(app);
30 |
31 | connect();
32 |
33 | handleSocket(server);
34 |
35 | server.listen(PORT, () => {
36 | console.log(`Server listing at port: ${PORT}`);
37 | });
38 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | // tailwind.config.js
2 | module.exports = {
3 | mode: "jit",
4 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
5 | darkMode: false, // or 'media' or 'class'
6 | theme: {
7 | extend: {
8 | height: {
9 | 120: "120px",
10 | 240: "240px",
11 | },
12 | },
13 | colors: {
14 | primary: "#2a2731",
15 | black: "rgb(10, 23, 33)",
16 | white: "#ffffff",
17 | gray: "#656566",
18 | blue: "#76a9ff",
19 | dark: "#252f3c",
20 | red: "#DC143C",
21 | green:"#008000"
22 | },
23 | screens: {
24 | sm: { min: "320px", max: "639px" },
25 | md: { min: "640px", max: "767px" },
26 | lg: { min: "768px", max: "1023px" },
27 | xl: { min: "1024px", max: "1279px" },
28 | "2xl": { min: "1280px" },
29 | },
30 | left: {
31 | "1/5": "20%",
32 | },
33 | },
34 | variants: {
35 | extend: {},
36 | },
37 | plugins: [],
38 | };
39 |
--------------------------------------------------------------------------------
/backend/src/models/UserModel.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model } from "mongoose";
2 | import bcrypt from "bcrypt";
3 |
4 | interface IUser {
5 | fullname: string;
6 | email: string;
7 | password: string;
8 | avatar: string;
9 | refreshToken: string;
10 | socketID: string;
11 | }
12 |
13 | const userSchema = new Schema(
14 | {
15 | fullname: { type: String },
16 | email: { type: String },
17 | password: { type: String },
18 | avatar: { type: String },
19 | refreshToken: { type: String },
20 | socketID: { type: String },
21 | },
22 | {
23 | timestamps: true,
24 | }
25 | );
26 |
27 | userSchema.methods.encryptPassword = function (password: string) {
28 | return bcrypt.hashSync(password, bcrypt.genSaltSync(5));
29 | };
30 |
31 | userSchema.methods.validPassword = function (password: string) {
32 | return bcrypt.compareSync(password, this.password);
33 | };
34 |
35 | const UserModel = model("User", userSchema);
36 |
37 | export default UserModel;
38 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "npx nodemon",
9 | "build": "rimraf ./build && tsc",
10 | "start": "npm run build && node ./build/server.js",
11 | "product": "node ./build/server.js"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "@types/cors": "^2.8.13",
18 | "bcrypt": "^5.1.0",
19 | "body-parser": "^1.20.1",
20 | "dotenv": "^16.0.3",
21 | "express": "^4.18.2",
22 | "jsonwebtoken": "^9.0.0",
23 | "mongoose": "^6.8.0",
24 | "morgan": "^1.10.0",
25 | "typescript": "^4.9.4",
26 | "@types/socket.io": "^3.0.2"
27 | },
28 | "devDependencies": {
29 | "@types/bcrypt": "^5.0.0",
30 | "@types/express": "^4.17.15",
31 | "@types/jsonwebtoken": "^8.5.9",
32 | "@types/node": "^18.11.17",
33 | "rimraf": "^3.0.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/backend/build/models/UserModel.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const mongoose_1 = require("mongoose");
7 | const bcrypt_1 = __importDefault(require("bcrypt"));
8 | const userSchema = new mongoose_1.Schema({
9 | fullname: { type: String },
10 | email: { type: String },
11 | password: { type: String },
12 | avatar: { type: String },
13 | refreshToken: { type: String },
14 | socketID: { type: String },
15 | }, {
16 | timestamps: true,
17 | });
18 | userSchema.methods.encryptPassword = function (password) {
19 | return bcrypt_1.default.hashSync(password, bcrypt_1.default.genSaltSync(5));
20 | };
21 | userSchema.methods.validPassword = function (password) {
22 | return bcrypt_1.default.compareSync(password, this.password);
23 | };
24 | const UserModel = (0, mongoose_1.model)("User", userSchema);
25 | exports.default = UserModel;
26 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/currentUser.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ClearCurrentReceiverAction,
3 | ClearCurrentUserAction,
4 | IUser,
5 | SetCurrentReceiverAction,
6 | SetCurrentUserAction,
7 | } from "../../types";
8 |
9 | export enum CurrentUserActionTypes {
10 | SET_CURRENT_USER = "SET_CURRENT_USER",
11 | CLEAR_CURRENT_USER = "CLEAR_CURRENT_USER",
12 | SET_CURRENT_RECEIVER = "SET_CURRENT_RECEIVER",
13 | CLEAR_CURRENT_RECEIVER = "CLEAR_CURRENT_RECEIVER",
14 | }
15 |
16 | export const setCurrentUser = (user: IUser): SetCurrentUserAction => ({
17 | type: CurrentUserActionTypes.SET_CURRENT_USER,
18 | payload: user,
19 | });
20 |
21 | export const clearCurrentUser = (): ClearCurrentUserAction => ({
22 | type: CurrentUserActionTypes.CLEAR_CURRENT_USER,
23 | });
24 |
25 | export const setCurrentReceiver = (user: IUser): SetCurrentReceiverAction => ({
26 | type: CurrentUserActionTypes.SET_CURRENT_RECEIVER,
27 | payload: user,
28 | });
29 |
30 | export const clearCurrentReceiver = (): ClearCurrentReceiverAction => ({
31 | type: CurrentUserActionTypes.CLEAR_CURRENT_RECEIVER,
32 | });
33 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/currentUser.ts:
--------------------------------------------------------------------------------
1 | import { initialState } from "../initState";
2 | import { CurrentUserActionTypes } from "../actions/currentUser";
3 | import { CurrentReceiverAction, CurrentUserAction, IUser } from "../../types";
4 |
5 | const currentUserReducer = (
6 | state = initialState,
7 | action: CurrentUserAction
8 | ): IUser => {
9 | switch (action.type) {
10 | case CurrentUserActionTypes.SET_CURRENT_USER:
11 | return {
12 | ...state,
13 | ...action.payload,
14 | };
15 | case CurrentUserActionTypes.CLEAR_CURRENT_USER:
16 | return {
17 | ...initialState,
18 | };
19 | default:
20 | return state;
21 | }
22 | };
23 |
24 | const currentReceiverReducer = (
25 | state = initialState,
26 | action: CurrentReceiverAction
27 | ): IUser => {
28 | switch (action.type) {
29 | case CurrentUserActionTypes.SET_CURRENT_RECEIVER:
30 | return {
31 | ...state,
32 | ...action.payload,
33 | };
34 | case CurrentUserActionTypes.CLEAR_CURRENT_RECEIVER:
35 | return {
36 | ...initialState,
37 | };
38 | default:
39 | return state;
40 | }
41 | };
42 |
43 | export { currentUserReducer, currentReceiverReducer };
44 |
--------------------------------------------------------------------------------
/backend/build/server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const cors_1 = __importDefault(require("cors"));
8 | const db_1 = __importDefault(require("./configs/db"));
9 | const dotenv_1 = __importDefault(require("dotenv"));
10 | const routes_1 = __importDefault(require("./routes"));
11 | const http_1 = __importDefault(require("http"));
12 | const socket_1 = require("./socket");
13 | const types_1 = require("./utils/types");
14 | dotenv_1.default.config();
15 | const app = (0, express_1.default)();
16 | const server = http_1.default.createServer(app);
17 | const allowedOrigins = [types_1.URL_LOCALHOST, types_1.URL_PRODUCTION];
18 | const corsOptions = {
19 | origin: allowedOrigins,
20 | credentials: true,
21 | };
22 | app.use((0, cors_1.default)(corsOptions));
23 | app.use(express_1.default.json());
24 | app.use(express_1.default.urlencoded({ extended: true }));
25 | (0, routes_1.default)(app);
26 | (0, db_1.default)();
27 | (0, socket_1.handleSocket)(server);
28 | server.listen(types_1.PORT, () => {
29 | console.log(`Server listing at port: ${types_1.PORT}`);
30 | });
31 |
--------------------------------------------------------------------------------
/frontend/src/layouts/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import { routes } from "../routes";
3 | import { Sidebar, Notification } from "../pages/dashboard";
4 | import { useSelector } from "react-redux";
5 | import { RootState } from "../redux/reducers/rootReducer";
6 |
7 | const Dashboard = () => {
8 | const isVisible = useSelector((state: RootState) => state.currentVisibility);
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {routes.map(
19 | ({ layout, pages }) =>
20 | layout === "dashboard" &&
21 | pages.map(({ path, element }) => (
22 |
23 | ))
24 | )}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export { Dashboard };
36 |
--------------------------------------------------------------------------------
/backend/src/middleware/token.ts:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import { Request, Response, NextFunction } from "express";
3 |
4 | const createToken = (user: any) => {
5 | const JWT_SECRET = process.env.JWT_SECRET || "";
6 | const payload = { id: user.id, username: user.username };
7 | let token = null;
8 | try {
9 | token = jwt.sign(payload, JWT_SECRET, { expiresIn: "120s" });
10 | } catch (error) {
11 | console.error(error);
12 | }
13 |
14 | return token;
15 | };
16 |
17 | const refreshToken = (data: any, token: string) => {
18 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || "";
19 | let payload = { username: data.username };
20 | try {
21 | token = jwt.sign(payload, REFRESH_TOKEN_SECRET);
22 | } catch (error) {
23 | console.log(error);
24 | }
25 |
26 | return token;
27 | };
28 |
29 | const authToken = (req: Request, res: Response, next: NextFunction) => {
30 | const authorizationHeader = req.headers["authorization"];
31 | const token = authorizationHeader?.split(" ")[1] || "";
32 | if (!token) {
33 | res.status(401).json({ message: "Token is not provided" });
34 | }
35 |
36 | try {
37 | const key = process.env.JWT_SECRET || "";
38 | let isVeriToken = jwt.verify(token, key);
39 | if (isVeriToken) {
40 | next();
41 | }
42 | } catch (error) {
43 | res.status(403).json(error);
44 | }
45 | };
46 |
47 | export { createToken, authToken, refreshToken };
48 |
--------------------------------------------------------------------------------
/backend/build/configs/db.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | Object.defineProperty(exports, "__esModule", { value: true });
15 | const mongoose_1 = __importDefault(require("mongoose"));
16 | mongoose_1.default.set("strictQuery", false);
17 | const connect = () => __awaiter(void 0, void 0, void 0, function* () {
18 | const url = process.env.MONGO_URL || "";
19 | return yield mongoose_1.default
20 | .connect(url)
21 | .then(() => {
22 | console.log("Database connect successfully");
23 | })
24 | .catch(() => {
25 | console.log("Database connect fail");
26 | });
27 | });
28 | exports.default = connect;
29 |
--------------------------------------------------------------------------------
/backend/build/routes/authRoute.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const express_1 = require("express");
27 | const Auth = __importStar(require("../middleware/auth"));
28 | const router = (0, express_1.Router)();
29 | router.post("/login", Auth.Login);
30 | router.post("/logout", Auth.Logout);
31 | router.post("/refreshToken", Auth.RefreshToken);
32 | router.post("/register", Auth.Register);
33 | exports.default = router;
34 |
--------------------------------------------------------------------------------
/frontend/src/services/auth.ts:
--------------------------------------------------------------------------------
1 | import { API_URL } from "../routes";
2 | import socket from "../socket";
3 | import { instanceAxios } from "./instance";
4 |
5 | const refreshToken = () => {
6 | const userID = JSON.parse(sessionStorage.getItem("user") || "")?.id;
7 | const refreshToken = sessionStorage.getItem("refreshToken");
8 | const body = {
9 | id: userID,
10 | token: refreshToken,
11 | };
12 |
13 | instanceAxios.post(API_URL.REFRESH_TOKEN, JSON.stringify(body));
14 | };
15 |
16 | const handleRegister = async (body: any) => {
17 | const response = await instanceAxios.post(API_URL.SIGN_UP, JSON.stringify(body));
18 | return response;
19 | };
20 |
21 | const handleLogin = async (body: any) => {
22 | const response = await instanceAxios
23 | .post(API_URL.SIGN_IN, JSON.stringify(body))
24 | .then((response) => {
25 | sessionStorage.setItem("token", response.data.token);
26 | sessionStorage.setItem("refreshToken", response.data.refreshToken);
27 | sessionStorage.setItem("user", JSON.stringify(response.data.user));
28 | socket.emit("login", response.data.user.id);
29 | });
30 | return response;
31 | };
32 |
33 | const handleLogout = async () => {
34 | try {
35 | const userID = JSON.parse(sessionStorage.getItem("user") || "")?.id;
36 | const body = {
37 | id: userID,
38 | };
39 | socket.emit(API_URL.LOGOUT, userID);
40 | sessionStorage.clear();
41 | await instanceAxios.post(API_URL.LOGOUT, JSON.stringify(body));
42 | } catch (error) {
43 | console.log(error);
44 | }
45 | };
46 |
47 | const getToken = () => {
48 | const token = sessionStorage.getItem("token") || null;
49 | return token;
50 | };
51 |
52 | export { getToken, refreshToken, handleLogout, handleLogin, handleRegister };
53 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-svg-core": "^6.2.1",
7 | "@fortawesome/free-solid-svg-icons": "^6.2.1",
8 | "@fortawesome/react-fontawesome": "^0.2.0",
9 | "@reduxjs/toolkit": "^1.9.2",
10 | "@testing-library/jest-dom": "^5.16.5",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "@tippyjs/react": "^4.2.6",
14 | "@types/jest": "^27.5.2",
15 | "@types/node": "^16.18.10",
16 | "@types/react": "^18.0.26",
17 | "@types/react-dom": "^18.0.9",
18 | "@types/socket.io": "^3.0.2",
19 | "autoprefixer": "^10.4.14",
20 | "axios": "^1.2.1",
21 | "classnames": "^2.3.2",
22 | "moment": "^2.29.4",
23 | "nodemon": "^2.0.20",
24 | "postcss": "^8.4.22",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "react-redux": "^8.0.5",
28 | "react-scripts": "5.0.1",
29 | "sass": "^1.57.0",
30 | "socket.io-client": "^4.6.0",
31 | "typescript": "^4.9.4",
32 | "web-vitals": "^2.1.4"
33 | },
34 | "scripts": {
35 | "start": "react-scripts start",
36 | "build": "react-scripts build",
37 | "test": "react-scripts test",
38 | "eject": "react-scripts eject"
39 | },
40 | "eslintConfig": {
41 | "extends": [
42 | "react-app",
43 | "react-app/jest"
44 | ]
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | },
58 | "devDependencies": {
59 | "file-loader": "^6.2.0",
60 | "react-router-dom": "^6.5.0",
61 | "tailwindcss": "^3.3.1"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/backend/build/routes/messageRoute.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | var __importDefault = (this && this.__importDefault) || function (mod) {
26 | return (mod && mod.__esModule) ? mod : { "default": mod };
27 | };
28 | Object.defineProperty(exports, "__esModule", { value: true });
29 | const express_1 = require("express");
30 | const Token = __importStar(require("../middleware/token"));
31 | const messageController_1 = __importDefault(require("../controllers/messageController"));
32 | const router = (0, express_1.Router)();
33 | router.post("/list", Token.authToken, messageController_1.default.list);
34 | router.post("/find", Token.authToken, messageController_1.default.find);
35 | router.post("/create", Token.authToken, messageController_1.default.create);
36 | exports.default = router;
37 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
18 |
19 |
28 | Chat App
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/backend/build/middleware/token.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.refreshToken = exports.authToken = exports.createToken = void 0;
7 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8 | const createToken = (user) => {
9 | const JWT_SECRET = process.env.JWT_SECRET || "";
10 | const payload = { id: user.id, username: user.username };
11 | let token = null;
12 | try {
13 | token = jsonwebtoken_1.default.sign(payload, JWT_SECRET, { expiresIn: "120s" });
14 | }
15 | catch (error) {
16 | console.error(error);
17 | }
18 | return token;
19 | };
20 | exports.createToken = createToken;
21 | const refreshToken = (data, token) => {
22 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || "";
23 | let payload = { username: data.username };
24 | try {
25 | token = jsonwebtoken_1.default.sign(payload, REFRESH_TOKEN_SECRET);
26 | }
27 | catch (error) {
28 | console.log(error);
29 | }
30 | return token;
31 | };
32 | exports.refreshToken = refreshToken;
33 | const authToken = (req, res, next) => {
34 | const authorizationHeader = req.headers["authorization"];
35 | const token = (authorizationHeader === null || authorizationHeader === void 0 ? void 0 : authorizationHeader.split(" ")[1]) || "";
36 | if (!token) {
37 | res.status(401).json({ message: "Token is not provided" });
38 | }
39 | try {
40 | const key = process.env.JWT_SECRET || "";
41 | let isVeriToken = jsonwebtoken_1.default.verify(token, key);
42 | if (isVeriToken) {
43 | next();
44 | }
45 | }
46 | catch (error) {
47 | res.status(403).json(error);
48 | }
49 | };
50 | exports.authToken = authToken;
51 |
--------------------------------------------------------------------------------
/backend/src/socket.ts:
--------------------------------------------------------------------------------
1 | import { Server } from "socket.io";
2 | import http from "http";
3 | import UserModel from "./models/UserModel";
4 | import messageController from "./controllers/messageController";
5 | import { Socket } from "socket.io";
6 | import { URL_LOCALHOST, URL_PRODUCTION } from "./utils/types";
7 |
8 | const listSocketID: any = [];
9 |
10 | const handleLogin = async (socket: Socket, userId: string) => {
11 | listSocketID.push(socket.id);
12 | await UserModel.findOneAndUpdate({ _id: userId }, { socketID: socket.id });
13 | socket.emit("login_success", { userId, socketId: socket.id });
14 | };
15 |
16 | const handleLogout = async (socket: Socket, userId: string) => {
17 | const index = listSocketID.indexOf(socket.id);
18 | if (index !== -1) {
19 | listSocketID.splice(index, 1);
20 | }
21 | await UserModel.findOneAndUpdate({ _id: userId }, { socketID: "" });
22 | socket.emit("logout_success", { userId, socketId: socket.id });
23 | };
24 |
25 | const handleMessage = async (socket: Socket, io: any, data: any) => {
26 | await messageController.create(data, io, socket, listSocketID);
27 | };
28 |
29 | const handleGetMessage = async (socket: Socket, io: any, data: any) => {
30 | await messageController.find(data, io, socket, listSocketID);
31 | };
32 |
33 | export const handleSocket = (server: http.Server) => {
34 | const io = new Server(server, {
35 | cors: {
36 | origin: [URL_LOCALHOST, URL_PRODUCTION],
37 | credentials: true,
38 | },
39 | });
40 |
41 | io.on("connection", (socket: Socket) => {
42 | socket.on("login", async (userId: string) => {
43 | await handleLogin(socket, userId);
44 | });
45 |
46 | socket.on("logout", async (userId: string) => {
47 | await handleLogout(socket, userId);
48 | });
49 |
50 | socket.on("message", async (data: any) => {
51 | await handleMessage(socket, io, data);
52 | });
53 |
54 | socket.on("get-message", async (data: any) => {
55 | await handleGetMessage(socket, io, data);
56 | });
57 | });
58 |
59 | return io;
60 | };
61 |
--------------------------------------------------------------------------------
/backend/src/controllers/userController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from "express";
2 | import UserModel from "../models/UserModel";
3 |
4 | class userController {
5 | async list(req: Request, res: Response) {
6 | try {
7 | const userList = await UserModel.find();
8 | res.status(200).json(userList);
9 | } catch (error) {
10 | res.status(400).json(error);
11 | }
12 | }
13 |
14 | async profile(req: Request, res: Response) {
15 | try {
16 | const user = await UserModel.findById(req.params.id);
17 | res.status(200).json(user);
18 | } catch (error) {
19 | res.status(400).json(error);
20 | }
21 | }
22 |
23 | async update(req: Request, res: Response) {
24 | try {
25 | const updatedUser = await UserModel.findOneAndUpdate(
26 | { _id: req.body.params },
27 | {
28 | username: req.body.username,
29 | password: req.body.password,
30 | },
31 | { new: true } // return the updated document
32 | );
33 | res.status(200).json(updatedUser);
34 | } catch (error) {
35 | res.status(400).json(error);
36 | }
37 | }
38 |
39 | async search(req: Request, res: Response) {
40 | try {
41 | const regex = new RegExp(req.body.fullname, "i");
42 | const user = await UserModel.find({
43 | fullname: regex,
44 | });
45 | res.status(200).json(user);
46 | } catch (error) {
47 | res.status(400).json(error);
48 | }
49 | }
50 |
51 | async destroy(req: Request, res: Response) {
52 | try {
53 | const user = await UserModel.deleteOne({ _id: req.body.params });
54 | res.status(200).json(user);
55 | } catch (error) {
56 | res.status(400).json(error);
57 | }
58 | }
59 |
60 | async destroyAll(req: Request, res: Response) {
61 | try {
62 | const deletedUser = await UserModel.deleteMany({});
63 | res.status(200).json(deletedUser);
64 | } catch (error) {
65 | res.status(400).json(error);
66 | }
67 | }
68 | }
69 |
70 | export default new userController();
71 |
--------------------------------------------------------------------------------
/frontend/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CurrentUserActionTypes,
3 | SelectMessageActionTypes,
4 | VisibilityActionTypes,
5 | } from "./redux/actions";
6 |
7 | export interface IUser {
8 | _id: string;
9 | avatar: string;
10 | fullname: string;
11 | username: string;
12 | }
13 |
14 | export interface IMessage {
15 | _id: string;
16 | senderID: IUser;
17 | receiverID: IUser;
18 | message: string;
19 | createdAt: string;
20 | }
21 |
22 | export interface selectMessageType extends Array {}
23 |
24 | export interface MessageContextValue {
25 | selectMessage: selectMessageType;
26 | getSelectMessage: React.Dispatch>;
27 | }
28 |
29 | export interface SetCurrentUserAction {
30 | type: CurrentUserActionTypes.SET_CURRENT_USER;
31 | payload: IUser;
32 | }
33 |
34 | export interface ClearCurrentUserAction {
35 | type: CurrentUserActionTypes.CLEAR_CURRENT_USER;
36 | }
37 |
38 | export interface SetCurrentReceiverAction {
39 | type: CurrentUserActionTypes.SET_CURRENT_RECEIVER;
40 | payload: IUser;
41 | }
42 |
43 | export interface ClearCurrentReceiverAction {
44 | type: CurrentUserActionTypes.CLEAR_CURRENT_RECEIVER;
45 | }
46 |
47 | export type CurrentUserAction = SetCurrentUserAction | ClearCurrentUserAction;
48 |
49 | export type CurrentReceiverAction =
50 | | SetCurrentReceiverAction
51 | | ClearCurrentReceiverAction;
52 |
53 | export interface SetSelectMessageAction {
54 | type: SelectMessageActionTypes.SET_MESSAGE;
55 | payload: [];
56 | }
57 |
58 | export interface ClearSelectMessageAction {
59 | type: SelectMessageActionTypes.CLEAR_MESSAGE;
60 | }
61 |
62 | export type SelectMessageAction =
63 | | SetSelectMessageAction
64 | | ClearSelectMessageAction;
65 |
66 | export interface SetVisibilityAction {
67 | type: VisibilityActionTypes.SET_VISIBILITY;
68 | payload: string;
69 | }
70 |
71 | export interface ClearVisibilityAction {
72 | type: VisibilityActionTypes.CLEAR_VISIBILITY;
73 | }
74 |
75 | export type SelectVisibilityAction =
76 | | SetVisibilityAction
77 | | ClearVisibilityAction;
78 |
--------------------------------------------------------------------------------
/backend/build/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | var __importDefault = (this && this.__importDefault) || function (mod) {
26 | return (mod && mod.__esModule) ? mod : { "default": mod };
27 | };
28 | Object.defineProperty(exports, "__esModule", { value: true });
29 | const express_1 = require("express");
30 | const Token = __importStar(require("../middleware/token"));
31 | const userController_1 = __importDefault(require("../controllers/userController"));
32 | const router = (0, express_1.Router)();
33 | router.post("/search", Token.authToken, userController_1.default.search);
34 | router.get("/profile/:id", Token.authToken, userController_1.default.profile);
35 | router.put("/update/:id", Token.authToken, userController_1.default.update);
36 | router.delete("/delete/:id", Token.authToken, userController_1.default.destroy);
37 | router.delete("/destroy", Token.authToken, userController_1.default.destroyAll);
38 | router.get("/", Token.authToken, userController_1.default.list);
39 | exports.default = router;
40 |
--------------------------------------------------------------------------------
/frontend/src/services/instance.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { API_URL } from "../routes";
3 |
4 | const API_URLUrl = process.env.REACT_APP_API_URL;
5 |
6 | const getRefreshToken = async () => {
7 | const id = JSON.parse(sessionStorage.getItem("user") || "")?.id;
8 | const token = sessionStorage.getItem("refreshToken") || "";
9 |
10 | const body = {
11 | id,
12 | token,
13 | };
14 | return await instanceAxios
15 | .post(API_URL.REFRESH_TOKEN, JSON.stringify(body))
16 | .then((response) => {
17 | localStorage.removeItem("token");
18 | localStorage.removeItem("refreshToken");
19 | sessionStorage.setItem("token", response.data.token);
20 | });
21 | };
22 |
23 |
24 | const instanceAxios = axios.create({
25 | baseURL: API_URLUrl,
26 | headers: {
27 | "Content-Type": "application/json",
28 | "Access-Control-Allow-Origin": "*",
29 | },
30 | });
31 |
32 | // Add a request interceptor
33 | instanceAxios.interceptors.request.use(
34 | function (config) {
35 | // Do something before request is sent
36 | config.withCredentials = true;
37 | const token = sessionStorage.getItem("token") || "";
38 | if (token !== "") {
39 | config.headers = {
40 | ...config.headers,
41 | Authorization: `Bearer ${token}`,
42 | };
43 | }
44 | return config;
45 | },
46 | function (error) {
47 | // Do something with request error
48 | return Promise.reject(error);
49 | }
50 | );
51 |
52 | // Add a response interceptor
53 | instanceAxios.interceptors.response.use(
54 | function (response) {
55 | return response;
56 | },
57 | async function (error) {
58 | const originalRequest = error.config;
59 | if (error.response.status === 403 && !originalRequest._retry) {
60 | originalRequest._retry = true;
61 | const access_token = await getRefreshToken();
62 | instanceAxios.defaults.headers.common["Authorization"] =
63 | "Bearer " + access_token;
64 | return instanceAxios(originalRequest);
65 | }
66 | // Any status codes that falls outside the range of 2xx cause this function to trigger
67 | // Do something with response error
68 | return Promise.reject(error);
69 | }
70 | );
71 |
72 | export { instanceAxios };
73 |
--------------------------------------------------------------------------------
/frontend/src/services/dashboard.ts:
--------------------------------------------------------------------------------
1 | import { API_URL } from "../routes";
2 | import { IMessage, IUser, selectMessageType } from "../types";
3 | import { instanceAxios } from "./instance";
4 |
5 | const getProfile = async () => {
6 | let id: null = null;
7 | const user = sessionStorage.getItem("user") || "";
8 | if (user !== "") {
9 | id = JSON.parse(user).id;
10 | }
11 |
12 | const response = await instanceAxios.get(`${API_URL.PROFILE}${id}`);
13 | return response;
14 | };
15 |
16 | const handleSearch = async (fullname: string) => {
17 | const response = await instanceAxios.post(
18 | API_URL.SEARCH,
19 | JSON.stringify({ fullname: fullname })
20 | );
21 | return response;
22 | };
23 |
24 | const getListUser = async () => {
25 | const response = await instanceAxios.get(API_URL.USER);
26 | return response;
27 | };
28 |
29 | const getListMessage = async () => {
30 | let userId: null = null;
31 | const user = sessionStorage.getItem("user") || "";
32 | if (user !== "") userId = JSON.parse(user).id;
33 |
34 | const response = await instanceAxios.post(
35 | API_URL.MESSAGES,
36 | JSON.stringify({ senderID: userId, receiverID: userId })
37 | );
38 | return response;
39 | };
40 |
41 | const getMessage = async (senderID: string, receiverID: string) => {
42 | const response = await instanceAxios.post(
43 | API_URL.MESSAGE,
44 | JSON.stringify({ senderID, receiverID })
45 | );
46 | return response;
47 | };
48 |
49 | const createMessage = async (
50 | senderID: string,
51 | receiverID: string,
52 | message: string
53 | ) => {
54 | const response = await instanceAxios.post(
55 | API_URL.CREATE_MESSAGE,
56 | JSON.stringify({ senderID, receiverID, message })
57 | );
58 | return response;
59 | };
60 |
61 | const handleFilterMessage = (
62 | listMessage: selectMessageType,
63 | currentUser: IUser
64 | ): selectMessageType => {
65 | const uniqueMessage = Array.from(
66 | new Map(
67 | listMessage.map((message: IMessage) => [
68 | message?.senderID?._id,
69 | message,
70 | ]) &&
71 | listMessage.map((message: IMessage) => [
72 | message?.receiverID?._id,
73 | message,
74 | ])
75 | ).values()
76 | );
77 |
78 | const filterArray = uniqueMessage.filter((message) => {
79 | return message.receiverID._id !== currentUser._id;
80 | });
81 |
82 | return filterArray;
83 | };
84 |
85 | export {
86 | getProfile,
87 | handleSearch,
88 | getListMessage,
89 | getMessage,
90 | createMessage,
91 | getListUser,
92 | handleFilterMessage,
93 | };
94 |
--------------------------------------------------------------------------------
/restAPI.http:
--------------------------------------------------------------------------------
1 |
2 | # url localhost
3 | @url = http://localhost:3001/api
4 | # url product
5 | @token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzYjkzYTdhNGIxYWRmMWU2OWI1NzQyZCIsInVzZXJuYW1lIjoiYW5odGh5IiwiZW1haWwiOiJhbmh0aHlAZ21haWwuY29tIiwiaWF0IjoxNjc2MDk2Njg4LCJleHAiOjE2NzYwOTY4MDh9.kRWaC_2Kqdki66XN-L4E6-Tn6pNZj7iuMh5J6UiCtLI
6 | @refreshToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRoYW5odGh1eSIsImlhdCI6MTY3NDcyNTI2MX0.WttXYRfnVlQuZTPqlE9JSdpwsu91t_xYvdYOyQzI7wo
7 |
8 | # =============AUTH==================
9 |
10 | ### Register
11 | POST {{url}}/auth/register
12 | Content-Type: application/json
13 |
14 | {
15 | "username": "khoavh",
16 | "password": "1",
17 | "avatar": "https://cdn.diemnhangroup.com/seoulcenter/2022/11/gai-xinh-2.jpg"
18 | }
19 |
20 | ### Login
21 | POST {{url}}/auth/login
22 | Content-Type: application/json
23 |
24 | {
25 | "email": "khoavh@gmail.com",
26 | "password": "1"
27 | }
28 |
29 | ### Logout
30 | POST {{url}}/auth/logout
31 | Content-Type: application/json
32 |
33 | {
34 | "id": "63b93a7a4b1adf1e69b5742d"
35 | }
36 |
37 | ### refreshToken
38 | POST {{url}}/auth/refreshToken
39 | Content-Type: application/json
40 |
41 | {
42 | "id": "63b93a7a4b1adf1e69b5742d",
43 | "token": "{{refreshToken}}"
44 | }
45 |
46 | # ==================USER=========================
47 |
48 | ### List All USer
49 | GET {{url}}/user
50 | Authorization: Beaer {{token}}
51 |
52 | ### search
53 | POST {{url}}/user/search
54 | Authorization: Beaer {{token}}
55 | Content-Type: application/json
56 |
57 | {
58 | "username": "khoavh"
59 | }
60 |
61 | ### Destroy All
62 | DELETE {{url}}/user/destroy
63 | Authorization: Beaer {{token}}
64 |
65 | ### profile
66 | GET {{url}}/user/profile/63b673f6f878cb9c68675fe6
67 | Authorization: Beaer {{token}}
68 |
69 | ### delete a users
70 | DELETE {{url}}/users/delete/63baa329b593996a69735666
71 | Authorization: Beaer {{token}}
72 |
73 |
74 | # ======================MESSAGE=========================
75 |
76 | ### List All Message
77 | POST {{url}}/message/list
78 | Authorization: Beaer {{token}}
79 | Content-Type: application/json
80 |
81 | {
82 | "senderID": "63b93a7a4b1adf1e69b5742d"
83 | }
84 |
85 | ### Message
86 | POST {{url}}/message/find
87 | Authorization: Beaer {{token}}
88 | Content-Type: application/json
89 |
90 | {
91 | "senderID": "63b93a7a4b1adf1e69b5742d",
92 | "receiverID": "63d5360bbfe103866f4ee972"
93 | }
94 |
95 | ### Create Message Thy
96 | POST {{url}}/message/create
97 | Authorization: Beaer {{token}}
98 | Content-Type: application/json
99 |
100 | {
101 | "message": "How are you?",
102 | "senderID": "63b93a7a4b1adf1e69b5742d",
103 | "receiverID": "63d5360bbfe103866f4ee972"
104 | }
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/backend/src/middleware/auth.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from "express";
2 | import UserModel from "../models/UserModel";
3 | import bcrypt from "bcrypt";
4 | import * as Token from "./token";
5 | import jwt from "jsonwebtoken";
6 |
7 | const Register = async (req: Request, res: Response) => {
8 | try {
9 | const { email, fullname, avatar, password } = req.body;
10 | const hashedPassword = bcrypt.hashSync(password, bcrypt.genSaltSync(10));
11 | const newUser = new UserModel({
12 | email,
13 | fullname,
14 | avatar,
15 | password: hashedPassword,
16 | });
17 | await newUser.save();
18 | res.status(201).json(newUser);
19 | } catch (error) {
20 | console.error(error);
21 | res.status(400).json({ error: "Failed to register user" });
22 | }
23 | };
24 |
25 | const Login = async (req: Request, res: Response) => {
26 | try {
27 | const { email, password } = req.body;
28 | const user = await UserModel.findOne({ email });
29 |
30 | if (!user || !bcrypt.compareSync(password, user.password)) {
31 | return res.status(400).json("Login Fail!");
32 | }
33 |
34 | const token = Token.createToken(user) || "";
35 | const refreshToken = Token.refreshToken(user, token);
36 |
37 | await UserModel.updateOne({ email }, { refreshToken });
38 |
39 | return res.status(200).json({
40 | user: { id: user.id },
41 | token,
42 | refreshToken,
43 | });
44 | } catch (err) {
45 | console.error(err);
46 | return res.status(500).json("Internal Server Error");
47 | }
48 | };
49 |
50 | const RefreshToken = async (req: Request, res: Response) => {
51 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || "";
52 | const JWT_SECRET = process.env.JWT_SECRET || "";
53 | const { token: refreshToken, id: userId } = req.body;
54 | if (!refreshToken || !userId) {
55 | return res.sendStatus(401);
56 | }
57 | try {
58 | const user = await UserModel.findById(userId);
59 | if (!user || user.refreshToken !== refreshToken) {
60 | return res.sendStatus(403);
61 | }
62 | jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err: any, decoded: any) => {
63 | if (err) {
64 | return res.sendStatus(403);
65 | }
66 | const accessToken = jwt.sign(
67 | { id: userId, username: decoded.username },
68 | JWT_SECRET,
69 | { expiresIn: "3600s" }
70 | );
71 | return res.status(201).json({ token: accessToken });
72 | });
73 | } catch (error) {
74 | console.error(error);
75 | return res.sendStatus(500);
76 | }
77 | };
78 |
79 | const Logout = async (req: Request, res: Response) => {
80 | const userId = req.body.id;
81 | await UserModel.updateOne({ _id: userId }, { refreshToken: "" }).then(() => {
82 | res.sendStatus(200);
83 | });
84 | };
85 | export { Register, Login, RefreshToken, Logout };
86 |
--------------------------------------------------------------------------------
/frontend/src/pages/auth/sign-in.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink, useNavigate } from "react-router-dom";
2 | import { useRef, useState } from "react";
3 | import { handleLogin } from "../../services/auth";
4 | import { useDispatch } from "react-redux";
5 | import { setVisibility } from "../../redux/actions";
6 |
7 | const Login = () => {
8 | const navigate = useNavigate();
9 | const dispatch = useDispatch();
10 | const emailRef = useRef(null);
11 | const passwordRef = useRef(null);
12 | const [validate, setValidate] = useState(false);
13 |
14 | const handleSubmit = async (e: React.FormEvent) => {
15 | const body = {
16 | email: emailRef.current?.value,
17 | password: passwordRef.current?.value,
18 | };
19 | e.preventDefault();
20 | try {
21 | await handleLogin(body);
22 | dispatch(setVisibility("home"));
23 | navigate("/dashboard/home");
24 | } catch (error) {
25 | console.error(error);
26 | setValidate(true);
27 | }
28 | };
29 |
30 | return (
31 |
32 |
33 |
Login
34 |
72 |
73 | Sign Up?
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export { Login };
81 |
--------------------------------------------------------------------------------
/backend/src/controllers/messageController.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from "express";
2 | import MessageModel from "../models/MessageModel";
3 | import UserModel from "../models/UserModel";
4 |
5 | class MessageController {
6 | async list(req: Request, res: Response) {
7 | const { senderID, receiverID } = req.body;
8 | try {
9 | const listMessage = await MessageModel.find({
10 | $or: [{ senderID }, { receiverID }],
11 | })
12 | .populate("senderID")
13 | .populate("receiverID");
14 | res.status(200).json(listMessage);
15 | } catch (error) {
16 | res.status(400).json(error);
17 | }
18 | }
19 | async create(data: any, io: any, socket: any, listSocketID: any) {
20 | const { senderID, receiverID, message } = data;
21 | const newMessage = new MessageModel({ senderID, receiverID, message });
22 |
23 | try {
24 | await newMessage.save();
25 |
26 | const [messagesFromSender, messagesFromReceiver, receiver] =
27 | await Promise.all([
28 | MessageModel.find({ senderID, receiverID })
29 | .populate("senderID")
30 | .populate("receiverID"),
31 | MessageModel.find({ senderID: receiverID, receiverID: senderID })
32 | .populate("senderID")
33 | .populate("receiverID"),
34 | UserModel.findById(receiverID),
35 | ]);
36 |
37 | const messages = [...messagesFromSender, ...messagesFromReceiver];
38 |
39 | if (receiver?.socketID && listSocketID.includes(receiver.socketID)) {
40 | io.to(receiver.socketID).emit("message", messages);
41 | }
42 |
43 | socket.emit("message", messages);
44 |
45 | const listMessage = await MessageModel.find({
46 | $or: [{ senderID: senderID }, { receiverID: senderID }],
47 | })
48 | .populate("senderID")
49 | .populate("receiverID");
50 |
51 | socket.emit("listMessage", listMessage);
52 | } catch (error) {
53 | // res.status(400).json(error);
54 | }
55 | }
56 |
57 | async find(data: any, io: any, socket: any, listSocketID: any) {
58 | const { senderID, receiverID } = data;
59 |
60 | try {
61 | const [messageSender, messageReceiver, user] = await Promise.all([
62 | MessageModel.find({ senderID, receiverID })
63 | .populate("senderID")
64 | .populate("receiverID"),
65 | MessageModel.find({ senderID: receiverID, receiverID: senderID })
66 | .populate("senderID")
67 | .populate("receiverID"),
68 | UserModel.findById(receiverID),
69 | ]);
70 |
71 | const messages = [...messageSender, ...messageReceiver];
72 |
73 | if (user?.socketID && listSocketID.includes(user.socketID)) {
74 | io.to(user.socketID).emit("message", messages);
75 | }
76 |
77 | socket.emit("message", messages);
78 | const listMessage = await MessageModel.find({
79 | $or: [{ senderID: senderID }, { receiverID: senderID }],
80 | })
81 | .populate("senderID")
82 | .populate("receiverID");
83 | socket.emit("listMessage", listMessage);
84 | } catch (error) {
85 | // res.status(400).json(error);
86 | }
87 | }
88 | }
89 |
90 | export default new MessageController();
91 |
--------------------------------------------------------------------------------
/backend/build/socket.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | Object.defineProperty(exports, "__esModule", { value: true });
15 | exports.handleSocket = void 0;
16 | const socket_io_1 = require("socket.io");
17 | const UserModel_1 = __importDefault(require("./models/UserModel"));
18 | const messageController_1 = __importDefault(require("./controllers/messageController"));
19 | const types_1 = require("./utils/types");
20 | const listSocketID = [];
21 | const handleLogin = (socket, userId) => __awaiter(void 0, void 0, void 0, function* () {
22 | listSocketID.push(socket.id);
23 | yield UserModel_1.default.findOneAndUpdate({ _id: userId }, { socketID: socket.id });
24 | socket.emit("login_success", { userId, socketId: socket.id });
25 | });
26 | const handleLogout = (socket, userId) => __awaiter(void 0, void 0, void 0, function* () {
27 | const index = listSocketID.indexOf(socket.id);
28 | if (index !== -1) {
29 | listSocketID.splice(index, 1);
30 | }
31 | yield UserModel_1.default.findOneAndUpdate({ _id: userId }, { socketID: "" });
32 | socket.emit("logout_success", { userId, socketId: socket.id });
33 | });
34 | const handleMessage = (socket, io, data) => __awaiter(void 0, void 0, void 0, function* () {
35 | yield messageController_1.default.create(data, io, socket, listSocketID);
36 | });
37 | const handleGetMessage = (socket, io, data) => __awaiter(void 0, void 0, void 0, function* () {
38 | yield messageController_1.default.find(data, io, socket, listSocketID);
39 | });
40 | const handleSocket = (server) => {
41 | const io = new socket_io_1.Server(server, {
42 | cors: {
43 | origin: [types_1.URL_LOCALHOST, types_1.URL_PRODUCTION],
44 | credentials: true,
45 | },
46 | });
47 | io.on("connection", (socket) => {
48 | socket.on("login", (userId) => __awaiter(void 0, void 0, void 0, function* () {
49 | yield handleLogin(socket, userId);
50 | }));
51 | socket.on("logout", (userId) => __awaiter(void 0, void 0, void 0, function* () {
52 | yield handleLogout(socket, userId);
53 | }));
54 | socket.on("message", (data) => __awaiter(void 0, void 0, void 0, function* () {
55 | yield handleMessage(socket, io, data);
56 | }));
57 | socket.on("get-message", (data) => __awaiter(void 0, void 0, void 0, function* () {
58 | yield handleGetMessage(socket, io, data);
59 | }));
60 | });
61 | return io;
62 | };
63 | exports.handleSocket = handleSocket;
64 |
--------------------------------------------------------------------------------
/backend/build/controllers/userController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | Object.defineProperty(exports, "__esModule", { value: true });
15 | const UserModel_1 = __importDefault(require("../models/UserModel"));
16 | class userController {
17 | list(req, res) {
18 | return __awaiter(this, void 0, void 0, function* () {
19 | try {
20 | const userList = yield UserModel_1.default.find();
21 | res.status(200).json(userList);
22 | }
23 | catch (error) {
24 | res.status(400).json(error);
25 | }
26 | });
27 | }
28 | profile(req, res) {
29 | return __awaiter(this, void 0, void 0, function* () {
30 | try {
31 | const user = yield UserModel_1.default.findById(req.params.id);
32 | res.status(200).json(user);
33 | }
34 | catch (error) {
35 | res.status(400).json(error);
36 | }
37 | });
38 | }
39 | update(req, res) {
40 | return __awaiter(this, void 0, void 0, function* () {
41 | try {
42 | const updatedUser = yield UserModel_1.default.findOneAndUpdate({ _id: req.body.params }, {
43 | username: req.body.username,
44 | password: req.body.password,
45 | }, { new: true } // return the updated document
46 | );
47 | res.status(200).json(updatedUser);
48 | }
49 | catch (error) {
50 | res.status(400).json(error);
51 | }
52 | });
53 | }
54 | search(req, res) {
55 | return __awaiter(this, void 0, void 0, function* () {
56 | try {
57 | const regex = new RegExp(req.body.fullname, "i");
58 | const user = yield UserModel_1.default.find({
59 | fullname: regex,
60 | });
61 | res.status(200).json(user);
62 | }
63 | catch (error) {
64 | res.status(400).json(error);
65 | }
66 | });
67 | }
68 | destroy(req, res) {
69 | return __awaiter(this, void 0, void 0, function* () {
70 | try {
71 | const user = yield UserModel_1.default.deleteOne({ _id: req.body.params });
72 | res.status(200).json(user);
73 | }
74 | catch (error) {
75 | res.status(400).json(error);
76 | }
77 | });
78 | }
79 | destroyAll(req, res) {
80 | return __awaiter(this, void 0, void 0, function* () {
81 | try {
82 | const deletedUser = yield UserModel_1.default.deleteMany({});
83 | res.status(200).json(deletedUser);
84 | }
85 | catch (error) {
86 | res.status(400).json(error);
87 | }
88 | });
89 | }
90 | }
91 | exports.default = new userController();
92 |
--------------------------------------------------------------------------------
/frontend/src/pages/auth/sign-up.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink, useNavigate } from "react-router-dom";
2 | import { useRef, useState } from "react";
3 | import { handleRegister } from "../../services/auth";
4 |
5 | const Register = () => {
6 | const navigate = useNavigate();
7 | const fullNameRef = useRef(null);
8 | const emailRef = useRef(null);
9 | const passwordRef = useRef(null);
10 | const [avatar, setAvatar] = useState(
11 | "https://cdn.shopify.com/shopifycloud/shopify/assets/no-image-2048-5e88c1b20e087fb7bbe9a3771824e743c244f437e4f8ba93bbf7b11b53f7824c_grande.gif"
12 | );
13 |
14 | async function handleSubmit(e: React.FormEvent) {
15 | e.preventDefault();
16 |
17 | const body = {
18 | fullname: fullNameRef?.current?.value,
19 | email: emailRef?.current?.value,
20 | password: passwordRef?.current?.value,
21 | avatar: avatar,
22 | };
23 | try {
24 | await handleRegister(body);
25 | navigate("/login");
26 | } catch (error) {
27 | console.error(error);
28 | alert("Register failed!");
29 | }
30 | }
31 |
32 | return (
33 |
34 |
35 |
Register
36 |
91 |
92 |
93 | Sign In?
94 |
95 |
96 |
97 |
98 | );
99 | };
100 |
101 | export { Register };
102 |
--------------------------------------------------------------------------------
/frontend/src/style.css:
--------------------------------------------------------------------------------
1 | /* src/index.css */
2 | @import 'tailwindcss/base';
3 | @import 'tailwindcss/components';
4 | @import 'tailwindcss/utilities';
5 |
6 | * {
7 | box-sizing: border-box;
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | :root {
13 | --primary: #2a2731;
14 | --black: rgb(10, 23, 33);
15 | --white: #ffffff;
16 | --gray: #656566;
17 | --blue: #76a9ff;
18 | }
19 |
20 | ::-webkit-scrollbar:horizontal {
21 | height: 8px;
22 | width: 5px !important;
23 | }
24 |
25 | ::-webkit-scrollbar {
26 | border-radius: 0;
27 | width: 8px;
28 | }
29 |
30 | ::-webkit-scrollbar-thumb {
31 | border-radius: 4px;
32 | background-color: var(--gray);
33 | }
34 |
35 | ::-webkit-scrollbar-track {
36 | border-radius: 0;
37 | background-color: rgba(0, 0, 0, 0);
38 | }
39 |
40 | ::-webkit-scrollbar-thumb:active {
41 | cursor: pointer;
42 | }
43 |
44 | ::-webkit-scrollbar:vertical {
45 | height: 20px;
46 | }
47 |
48 |
49 | html {
50 | font-size: 62.5%;
51 | }
52 |
53 | body {
54 | font-family: "IBM Plex Sans", sans-serif;
55 | font-size: 1.6 rem;
56 | line-height: 1.5;
57 | text-rendering: optimizeSpeed;
58 | height: 100%;
59 | }
60 |
61 | input:focus {
62 | outline: none;
63 | }
64 |
65 |
66 | @media (min-width: 1024px) {
67 | .custom-w-sidebar {
68 | left: 0;
69 | width: 25%;
70 | }
71 |
72 | .custom-w-home {
73 | left: 25%;
74 | width: 55%;
75 | }
76 |
77 | .custom-w-notification {
78 | right: 0;
79 | width: 20%;
80 | }
81 | }
82 |
83 |
84 |
85 | @media (max-width: 1024px) {
86 | .custom-w-sidebar {
87 | left: 0;
88 | width: 30%;
89 | }
90 |
91 | .custom-w-home {
92 | left: 30%;
93 | width: 45%;
94 | }
95 |
96 | .custom-w-notification {
97 | right: 0;
98 | width: 25%;
99 | }
100 | }
101 |
102 | .wrapper {
103 | box-sizing: border-box;
104 | display: flex;
105 | flex-direction: column;
106 | box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.12);
107 | background-color: var(--black);
108 | color: var(--white);
109 | padding: 20px;
110 | border-radius: 8px;
111 | padding-top: 8px;
112 | overflow-y: scroll;
113 | font-size: 1.5rem;
114 | }
115 |
116 | .wrapper ul {
117 | list-style-type: none;
118 | display: flex;
119 | flex-direction: column;
120 | align-items: center;
121 | }
122 |
123 | .wrapper ul li {
124 | display: flex;
125 | flex-direction: row;
126 | align-items: center;
127 | padding-left: 15px;
128 | }
129 |
130 | .wrapper ul li:hover {
131 | cursor: pointer;
132 | background-color: #3c3844;
133 | border-radius: 20px;
134 | }
135 |
136 |
137 | @media (min-width: 992px) {
138 | .wrapper {
139 | width: 35vh;
140 | font-size: 1.5rem;
141 | gap: 10px;
142 | }
143 |
144 | .wrapper ul {
145 | margin-top: 20px;
146 | gap: 5px;
147 | }
148 |
149 | .wrapper ul li {
150 | width: 100%;
151 | height: 40px;
152 | gap: 10px;
153 | }
154 |
155 | .search {
156 | width: 35vh;
157 | height: 35vh;
158 | }
159 | }
160 |
161 | @media (max-width: 992px) and (min-width: 426px) {
162 | .wrapper {
163 | width: 40vh;
164 | font-size: 1.5rem;
165 | gap: 10px;
166 | }
167 |
168 | .wrapper ul {
169 | gap: 5px;
170 | }
171 |
172 | .wrapper ul li {
173 | width: 100%;
174 | height: 40px;
175 | gap: 10px;
176 | }
177 |
178 | .search {
179 | width: 40vh;
180 | height: 40vh;
181 | }
182 | }
183 |
184 | @media (max-width: 425px) {
185 | .wrapper {
186 | width: 350px;
187 | font-size: 1.5rem;
188 | }
189 |
190 | .wrapper ul {
191 | gap: 5px;
192 | }
193 |
194 | .wrapper ul li {
195 | width: 100%;
196 | height: 40px;
197 | gap: 10px;
198 | }
199 |
200 | .search {
201 | width: 270px;
202 | height: 270px;
203 | }
204 | }
--------------------------------------------------------------------------------
/backend/build/controllers/messageController.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
13 | };
14 | Object.defineProperty(exports, "__esModule", { value: true });
15 | const MessageModel_1 = __importDefault(require("../models/MessageModel"));
16 | const UserModel_1 = __importDefault(require("../models/UserModel"));
17 | class MessageController {
18 | list(req, res) {
19 | return __awaiter(this, void 0, void 0, function* () {
20 | const { senderID, receiverID } = req.body;
21 | try {
22 | const listMessage = yield MessageModel_1.default.find({
23 | $or: [{ senderID }, { receiverID }],
24 | })
25 | .populate("senderID")
26 | .populate("receiverID");
27 | res.status(200).json(listMessage);
28 | }
29 | catch (error) {
30 | res.status(400).json(error);
31 | }
32 | });
33 | }
34 | create(data, io, socket, listSocketID) {
35 | return __awaiter(this, void 0, void 0, function* () {
36 | const { senderID, receiverID, message } = data;
37 | const newMessage = new MessageModel_1.default({ senderID, receiverID, message });
38 | try {
39 | yield newMessage.save();
40 | const [messagesFromSender, messagesFromReceiver, receiver] = yield Promise.all([
41 | MessageModel_1.default.find({ senderID, receiverID })
42 | .populate("senderID")
43 | .populate("receiverID"),
44 | MessageModel_1.default.find({ senderID: receiverID, receiverID: senderID })
45 | .populate("senderID")
46 | .populate("receiverID"),
47 | UserModel_1.default.findById(receiverID),
48 | ]);
49 | const messages = [...messagesFromSender, ...messagesFromReceiver];
50 | if ((receiver === null || receiver === void 0 ? void 0 : receiver.socketID) && listSocketID.includes(receiver.socketID)) {
51 | io.to(receiver.socketID).emit("message", messages);
52 | }
53 | socket.emit("message", messages);
54 | const listMessage = yield MessageModel_1.default.find({
55 | $or: [{ senderID: senderID }, { receiverID: senderID }],
56 | })
57 | .populate("senderID")
58 | .populate("receiverID");
59 | socket.emit("listMessage", listMessage);
60 | }
61 | catch (error) {
62 | // res.status(400).json(error);
63 | }
64 | });
65 | }
66 | find(data, io, socket, listSocketID) {
67 | return __awaiter(this, void 0, void 0, function* () {
68 | const { senderID, receiverID } = data;
69 | try {
70 | const [messageSender, messageReceiver, user] = yield Promise.all([
71 | MessageModel_1.default.find({ senderID, receiverID })
72 | .populate("senderID")
73 | .populate("receiverID"),
74 | MessageModel_1.default.find({ senderID: receiverID, receiverID: senderID })
75 | .populate("senderID")
76 | .populate("receiverID"),
77 | UserModel_1.default.findById(receiverID),
78 | ]);
79 | const messages = [...messageSender, ...messageReceiver];
80 | if ((user === null || user === void 0 ? void 0 : user.socketID) && listSocketID.includes(user.socketID)) {
81 | io.to(user.socketID).emit("message", messages);
82 | }
83 | socket.emit("message", messages);
84 | const listMessage = yield MessageModel_1.default.find({
85 | $or: [{ senderID: senderID }, { receiverID: senderID }],
86 | })
87 | .populate("senderID")
88 | .populate("receiverID");
89 | socket.emit("listMessage", listMessage);
90 | }
91 | catch (error) {
92 | // res.status(400).json(error);
93 | }
94 | });
95 | }
96 | }
97 | exports.default = new MessageController();
98 |
--------------------------------------------------------------------------------
/frontend/src/components/Account.tsx:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { RootState } from "../redux/reducers/rootReducer";
4 | import { setCurrentReceiver, setVisibility} from "../redux/actions";
5 | import { useEffect, useRef } from "react";
6 | import socket from "../socket";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { faThumbsUp } from "@fortawesome/free-solid-svg-icons";
9 |
10 | const AccountItem = ({ ...rest }) => {
11 | const currentUser = useSelector((state: RootState) => state.currentUser);
12 | const dispatch = useDispatch();
13 | const { searchUser } = rest;
14 | const senderID = currentUser?._id;
15 | const receiverID = searchUser?._id;
16 |
17 | const handleSubmit = () => {
18 | dispatch(setVisibility("home"));
19 | dispatch(setCurrentReceiver(searchUser));
20 | const data = {
21 | senderID: receiverID,
22 | receiverID: senderID,
23 | };
24 | socket.emit("get-message", data);
25 | };
26 |
27 | return (
28 | handleSubmit()}
31 | >
32 |

37 |
{searchUser?.fullname}
38 |
39 | );
40 | };
41 |
42 | const AccountMessage = ({ ...rest }) => {
43 | const dispatch = useDispatch();
44 | const { currentMessage, searchUser } = rest;
45 | const divRef = useRef(null);
46 | const currentUser = useSelector((state: RootState) => state.currentUser);
47 | const currentReceiver = useSelector(
48 | (state: RootState) => state.currentReceiver
49 | );
50 | const senderID = currentUser?._id;
51 | const receiverID = searchUser?._id;
52 |
53 | useEffect(() => {
54 | if (
55 | divRef.current &&
56 | divRef.current.parentElement?.children[0] === divRef.current
57 | ) {
58 | divRef.current.click();
59 | }
60 | }, []);
61 |
62 | const handleSubmit = () => {
63 | dispatch(setVisibility("home"));
64 | dispatch(setCurrentReceiver(searchUser));
65 | const data = {
66 | senderID: senderID,
67 | receiverID: receiverID,
68 | };
69 | socket.emit("get-message", data);
70 | };
71 |
72 |
73 | return (
74 |
81 |

90 |
91 |
92 |
93 | {currentMessage.senderID === currentUser
94 | ? currentMessage?.senderID?.fullname
95 | : currentMessage?.receiverID?.fullname}
96 |
97 | {currentMessage?.message !== ":like" ? (
98 |
99 |
{currentMessage?.message}
100 |
101 | {moment(currentMessage?.createdAt).format("HH:mm")}
102 |
103 |
104 | ) : (
105 |
109 | )}
110 |
111 |
112 |
113 | );
114 | };
115 |
116 | const AccountStatus = ({ ...rest }) => {
117 | const dispatch = useDispatch();
118 | const { searchUser } = rest;
119 | const currentUser = useSelector((state: RootState) => state.currentUser);
120 | const receiverID = searchUser?._id;
121 | const senderID = currentUser?._id;
122 | const handleSubmit = () => {
123 | dispatch(setVisibility("home"));
124 | dispatch(setCurrentReceiver(searchUser));
125 | const data = {
126 | senderID: receiverID,
127 | receiverID: senderID,
128 | };
129 | socket.emit("get-message", data);
130 | };
131 |
132 | return (
133 | handleSubmit()}
136 | >
137 |

142 |
{searchUser?.fullname}
143 |
144 | );
145 | };
146 |
147 | export { AccountItem, AccountMessage, AccountStatus };
148 |
--------------------------------------------------------------------------------
/backend/build/middleware/auth.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27 | return new (P || (P = Promise))(function (resolve, reject) {
28 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31 | step((generator = generator.apply(thisArg, _arguments || [])).next());
32 | });
33 | };
34 | var __importDefault = (this && this.__importDefault) || function (mod) {
35 | return (mod && mod.__esModule) ? mod : { "default": mod };
36 | };
37 | Object.defineProperty(exports, "__esModule", { value: true });
38 | exports.Logout = exports.RefreshToken = exports.Login = exports.Register = void 0;
39 | const UserModel_1 = __importDefault(require("../models/UserModel"));
40 | const bcrypt_1 = __importDefault(require("bcrypt"));
41 | const Token = __importStar(require("./token"));
42 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
43 | const Register = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
44 | try {
45 | const { email, fullname, avatar, password } = req.body;
46 | const hashedPassword = bcrypt_1.default.hashSync(password, bcrypt_1.default.genSaltSync(10));
47 | const newUser = new UserModel_1.default({
48 | email,
49 | fullname,
50 | avatar,
51 | password: hashedPassword,
52 | });
53 | yield newUser.save();
54 | res.status(201).json(newUser);
55 | }
56 | catch (error) {
57 | console.error(error);
58 | res.status(400).json({ error: "Failed to register user" });
59 | }
60 | });
61 | exports.Register = Register;
62 | const Login = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
63 | try {
64 | const { email, password } = req.body;
65 | const user = yield UserModel_1.default.findOne({ email });
66 | if (!user || !bcrypt_1.default.compareSync(password, user.password)) {
67 | return res.status(400).json("Login Fail!");
68 | }
69 | const token = Token.createToken(user) || "";
70 | const refreshToken = Token.refreshToken(user, token);
71 | yield UserModel_1.default.updateOne({ email }, { refreshToken });
72 | return res.status(200).json({
73 | user: { id: user.id },
74 | token,
75 | refreshToken,
76 | });
77 | }
78 | catch (err) {
79 | console.error(err);
80 | return res.status(500).json("Internal Server Error");
81 | }
82 | });
83 | exports.Login = Login;
84 | const RefreshToken = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
85 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || "";
86 | const JWT_SECRET = process.env.JWT_SECRET || "";
87 | const { token: refreshToken, id: userId } = req.body;
88 | if (!refreshToken || !userId) {
89 | return res.sendStatus(401);
90 | }
91 | try {
92 | const user = yield UserModel_1.default.findById(userId);
93 | if (!user || user.refreshToken !== refreshToken) {
94 | return res.sendStatus(403);
95 | }
96 | jsonwebtoken_1.default.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, decoded) => {
97 | if (err) {
98 | return res.sendStatus(403);
99 | }
100 | const accessToken = jsonwebtoken_1.default.sign({ id: userId, username: decoded.username }, JWT_SECRET, { expiresIn: "3600s" });
101 | return res.status(201).json({ token: accessToken });
102 | });
103 | }
104 | catch (error) {
105 | console.error(error);
106 | return res.sendStatus(500);
107 | }
108 | });
109 | exports.RefreshToken = RefreshToken;
110 | const Logout = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
111 | const userId = req.body.id;
112 | yield UserModel_1.default.updateOne({ _id: userId }, { refreshToken: "" }).then(() => {
113 | res.sendStatus(200);
114 | });
115 | });
116 | exports.Logout = Logout;
117 |
--------------------------------------------------------------------------------
/frontend/src/pages/dashboard/notification.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2 | import {
3 | faArrowLeft,
4 | faBars,
5 | faBell,
6 | faPhone,
7 | faToggleOff,
8 | faUser,
9 | } from "@fortawesome/free-solid-svg-icons";
10 | import { Image } from "../../components/Image";
11 | import { NavLink } from "react-router-dom";
12 | import { useDispatch, useSelector } from "react-redux";
13 | import { RootState } from "../../redux/reducers/rootReducer";
14 | import { setVisibility } from "../../redux/actions";
15 | const Notification = () => {
16 | const dispatch = useDispatch();
17 | const currentReceiver = useSelector(
18 | (state: RootState) => state.currentReceiver
19 | );
20 | const isVisible = useSelector((state: RootState) => state.currentVisibility);
21 |
22 | const handleHome = () => {
23 | dispatch(setVisibility("home"));
24 | };
25 | return (
26 |
27 |
28 | {isVisible === "notification" && (
29 |
34 | )}
35 | {isVisible === "notification" && (
36 |
37 | )}
38 |
39 |
40 |
46 |
{currentReceiver.fullname}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Notifications
57 |
58 |
59 |
60 |
61 |
Bookmarks
62 |
63 |
64 |
65 |
66 |
67 |
68 | share medias
69 |
70 | View all
71 |
72 |
73 |
95 |
96 |
97 |
98 | share files
99 |
100 | View all
101 |
102 |
103 |
104 |
105 |

110 |
111 |
File Name
112 |
File Description
113 |
114 |
115 |
116 |

121 |
122 |
File Name
123 |
File Description
124 |
125 |
126 |
127 |

132 |
133 |
File Name
134 |
File Description
135 |
136 |
137 |
138 |
139 |
140 |
141 | );
142 | };
143 |
144 | export { Notification };
145 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 | 
5 | 
6 | 
7 |
8 | # Chat App MERN
9 | The Real Time Chat Application is a messaging platform that enables users to communicate with each other in real-time. It is built using Typescript, React, Express, Mongoose, and Socket.io, which are all modern technologies for developing web applications.
10 |
11 | ## Index
12 |
13 | - [Demo](#demo)
14 | - [Features](#features)
15 | - [Installation](#installation)
16 | - [How It Works](#how-it-works)
17 | - [Structure](#structure)
18 |
19 | ## Demo
20 |
21 |
22 |
23 |
24 | ### Desktop:
25 |
26 |
27 | ### Smart Phone:
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ## Features
36 |
37 | - Use Express to build the backend.
38 | - Use React to build the frontend.
39 | - Authenticates via email and password.
40 | - Real-time communication between a client and a server using [Socket.io](https://github.com/socketio/socket.io).
41 | - Uses [MongoDB](https://github.com/mongodb/mongo), [Mongoose](https://github.com/Automattic/mongoose) for storing and querying data.
42 |
43 | ## Installation
44 |
45 | ### Running Locally
46 |
47 | Make sure you have [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed.
48 |
49 | 1. Clone repository:
50 |
51 | ```
52 | $ git clone https://github.com/Vo-Huy-Khoa/chat-app-mern.git
53 | $ cd chat-app-mern
54 | ```
55 |
56 | Run Backend:
57 |
58 | ```bash
59 | cd backend
60 | npm i
61 | npm run dev
62 | ```
63 |
64 | Run Frontend:
65 |
66 | ```bash
67 | cd frontend
68 | npm i
69 | npm run start
70 | ```
71 |
72 | ## How It Works
73 |
74 | #### MongoDB
75 |
76 | You need to create a database on MongoDB, then create a database user, get the `MongoDB URI`, and assign it to `dbURI`.
77 |
78 | ### Database
79 |
80 | Mongoose is used to interact with a MongoDB.
81 |
82 | #### Schemas
83 |
84 | There are two schemas; users and rooms.
85 |
86 | Each user has a username, password,and picture. If the user is logged via username and password, and the if logged in via a social account, then the password will be null.
87 |
88 | ### Models
89 |
90 | Each model wraps Mongoose Model object, overrides and provides some methods.
91 |
92 | ### Sockets
93 |
94 | Having an active connection opened between the client and the server so client can send and receive data. This allows real-time communication using sockets. This is made possible by [Socket.io](https://github.com/socketio/socket.io).
95 |
96 | The client starts by connecting to the server through a socket (maybe also assigned to a specific namespace) . Once connections is successful, client and server can emit and listen to events.
97 |
98 | ## Structure of the project:
99 |
100 | ### BackEnd
101 |
102 | ```text
103 | src
104 | |
105 | ├── configs
106 | | └── db.ts
107 | ├── controllers
108 | | └── UserController.ts
109 | │ └── MessageController.ts
110 | ├── middleware
111 | | └── auth.ts
112 | │ └── token.ts
113 | ├── models
114 | | └── User.ts
115 | | └──Message.ts
116 | ├── routes
117 | | └── index.ts
118 | └── server.ts
119 | ```
120 |
121 | ### FrontEnd
122 |
123 | ```text
124 | src
125 | ├── components
126 | │ └── Account.tsx
127 | │ └── Image.tsx
128 | │ └── index.ts
129 | ├── hooks
130 | │ └── useDebounce
131 | │ └── index
132 | ├── layouts
133 | │ └── auth.tsx
134 | │ └── dashboard.tsx
135 | │ └── index.ts
136 | ├── pages
137 | │ └── auth
138 | │ └── sign-in.tsx
139 | │ └── sign-up.tsx
140 | │ └── index.ts
141 | │ └── dashboard
142 | │ └── home.tsx
143 | │ └── notification.tsx
144 | │ └── sidebar.tsx
145 | │ └── index.ts
146 | ├── redux
147 | │ └── actions
148 | │ └── sign-in.tsx
149 | │ └── index.ts
150 | │ └── reducers
151 | │ └── visibility.ts
152 | │ └── rootReducer.ts
153 | │ └── initState.ts
154 | │ └── store.ts
155 | ├── services
156 | │ └── auth.ts
157 | │ └── dashboard.ts
158 | │ └── index.ts
159 | ├── socket.tsx
160 | ├── route.tsx
161 | ├── App.tsx
162 | └── index.tsx
163 |
164 | ```
165 |
166 |
200 |
--------------------------------------------------------------------------------
/frontend/src/pages/dashboard/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { Image } from "../../components/Image";
2 | import HeadlessTippy from "@tippyjs/react/headless";
3 | import {
4 | AccountItem,
5 | AccountMessage,
6 | AccountStatus,
7 | } from "../../components/Account";
8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
9 | import {
10 | faBell,
11 | faBug,
12 | faChevronDown,
13 | faDeleteLeft,
14 | faGear,
15 | faRightFromBracket,
16 | faSearch,
17 | faUser,
18 | } from "@fortawesome/free-solid-svg-icons";
19 | import { useEffect, useState } from "react";
20 | import { useDebounce } from "../../hooks";
21 | import { Wrapper as PopperWrapper } from "../../components";
22 | import {
23 | handleSearch,
24 | getListMessage,
25 | getListUser,
26 | handleFilterMessage,
27 | } from "../../services/dashboard";
28 | import { IMessage, IUser, selectMessageType } from "../../types";
29 | import { useSelector } from "react-redux";
30 | import { RootState } from "../../redux/reducers/rootReducer";
31 | import { handleLogout } from "../../services";
32 | import { NavLink } from "react-router-dom";
33 |
34 | const Sidebar = () => {
35 | const currentUser = useSelector((state: RootState) => state.currentUser);
36 | const [valueSearch, setValueSearch] = useState("");
37 | const debounceValue = useDebounce(valueSearch, 500);
38 | const [listUserSearch, setListSearch] = useState([]);
39 | const [listUser, setListUser] = useState([]);
40 | const [listMessage, setListMessage] = useState([]);
41 |
42 | useEffect(() => {
43 | if (!debounceValue.trim()) {
44 | return;
45 | }
46 | handleSearch(debounceValue).then((response) => {
47 | setListSearch(response.data);
48 | });
49 | }, [debounceValue]);
50 |
51 | useEffect(() => {
52 | async function fetchMessagesAndUsers() {
53 | try {
54 | const messageResponse = await getListMessage();
55 | const listMessage = handleFilterMessage(
56 | messageResponse.data,
57 | currentUser
58 | );
59 | setListMessage(listMessage);
60 |
61 | const userResponse = await getListUser();
62 | setListUser(userResponse.data);
63 | } catch (error) {
64 | // Handle errors
65 | }
66 | }
67 |
68 | fetchMessagesAndUsers();
69 | }, [currentUser]);
70 |
71 | return (
72 |
73 |
74 |
75 |
(
81 |
82 |
83 |
84 | -
85 |
86 | Profile
87 |
88 | -
89 |
90 | Settings
91 |
92 | -
93 |
94 | Report
95 |
96 | -
97 |
101 |
102 | Logout
103 |
104 |
105 |
106 |
107 |
108 | )}
109 | >
110 |
111 |
115 |
116 |
117 |
118 |
{currentUser.username}
119 |
120 |
121 |
125 |
126 | 1
127 |
128 |
129 |
130 |
131 |
(
137 |
138 |
139 | {listUserSearch.map((user, index) => {
140 | return (
141 |
146 | );
147 | })}
148 |
149 |
150 | )}
151 | >
152 |
153 |
157 | {
159 | setValueSearch(e.currentTarget.value);
160 | }}
161 | value={valueSearch}
162 | type="text"
163 | placeholder="Search"
164 | className="w-full h-14 bg-black rounded-3xl text-white p-8 pl-14 text-2xl"
165 | />
166 | {
170 | setValueSearch("");
171 | }}
172 | />
173 |
174 |
175 |
176 |
All User
177 |
178 | {listUser.map((user: IUser, index) => {
179 | return (
180 |
185 | );
186 | })}
187 |
188 |
189 |
190 |
191 | {listMessage.map((message: IMessage, index) => {
192 | return (
193 |
202 | );
203 | })}
204 |
205 |
206 | );
207 | };
208 |
209 | export { Sidebar };
210 |
--------------------------------------------------------------------------------
/frontend/src/pages/dashboard/home.tsx:
--------------------------------------------------------------------------------
1 | import socket from "../../socket";
2 | import { Image } from "../../components/Image";
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 | import {
5 | faArrowLeft,
6 | faCircleInfo,
7 | faCopy,
8 | faFileImage,
9 | faMicrophone,
10 | faPhone,
11 | faThumbsUp,
12 | faVideoCamera,
13 | } from "@fortawesome/free-solid-svg-icons";
14 | import { useEffect, useState } from "react";
15 | import { IMessage } from "../../types";
16 | import { getProfile } from "../../services";
17 | import { setCurrentUser, setSelectMessage, setVisibility } from "../../redux/actions";
18 | import { useDispatch, useSelector } from "react-redux";
19 | import { RootState } from "../../redux/reducers/rootReducer";
20 |
21 | const Home = () => {
22 | const dispatch = useDispatch();
23 |
24 | const currentUser = useSelector((state: RootState) => state.currentUser);
25 | const currentReceiver = useSelector(
26 | (state: RootState) => state.currentReceiver
27 | );
28 | const selectMessage = useSelector((state: RootState) => state.currentMessage);
29 | const [valueMessage, setValueMessage] = useState("");
30 | const currentSenderID = currentUser?._id;
31 | const currentReceiverID = currentReceiver?._id;
32 | const countMessage = currentReceiver._id.length ? 1 : null;
33 |
34 | const emitMessage = (message: string) => {
35 | const data = {
36 | senderID: currentSenderID,
37 | receiverID: currentReceiverID,
38 | message: message,
39 | };
40 | socket.emit("message", data);
41 | setValueMessage("");
42 | };
43 |
44 | const handleCreateMessage = (event: any) => {
45 | event.preventDefault();
46 | emitMessage(valueMessage);
47 | };
48 |
49 | const handleCreateLike = (event: any) => {
50 | event.preventDefault();
51 | emitMessage(":like");
52 | };
53 |
54 | const handleReturnSidebar = ()=>{
55 | dispatch(setVisibility("sidebar"));
56 | }
57 | const handleReturnNotification = ()=>{
58 | dispatch(setVisibility("notification"));
59 | }
60 |
61 | useEffect(() => {
62 | const handleNewMessage = (data: any = []) => {
63 | const sortedMessages = data.sort(
64 | (a: IMessage, b: IMessage) =>
65 | Date.parse(a.createdAt) - Date.parse(b.createdAt)
66 | );
67 | dispatch(setSelectMessage(sortedMessages));
68 | };
69 |
70 | socket.on("message", handleNewMessage);
71 |
72 | return () => {
73 | socket.off("message", handleNewMessage);
74 | };
75 | }, [dispatch]);
76 |
77 | useEffect(() => {
78 | getProfile().then((res) => {
79 | dispatch(setCurrentUser(res.data));
80 | });
81 | }, [dispatch]);
82 |
83 | return (
84 |
85 |
86 |
87 |
92 | {countMessage !== null && (
93 |
94 |

95 |
{currentReceiver?.fullname}
96 |
97 | )}
98 |
99 |
100 |
101 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 | {selectMessage.map((message, index) => {
112 | if (message.senderID._id !== currentUser._id) {
113 | return (
114 |
115 |

120 | {message.message !== ":like" ? (
121 |
122 |
{message.message}
123 |
124 | ) : (
125 |
126 |
130 |
131 | )}
132 |
133 | );
134 | } else {
135 | return (
136 |
140 |

145 | {message.message !== ":like" ? (
146 |
147 |
{message.message}
148 |
149 | ) : (
150 |
151 |
155 |
156 | )}
157 |
158 | );
159 | }
160 | })}
161 |
162 |
163 | (
164 | {countMessage !== null && (
165 |
166 |
167 |
168 |
172 |
176 |
180 |
181 |
{
185 | setValueMessage(event.target.value);
186 | }}
187 | />
188 |
189 | {valueMessage !== "" ? (
190 |
196 | ) : (
197 |
206 | )}
207 |
208 |
209 | )}
210 | )
211 |
212 | );
213 | };
214 |
215 | export { Home };
216 |
--------------------------------------------------------------------------------
/backend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "commonjs" /* Specify what module code is generated. */,
29 | "rootDir": "./src/" /* Specify the root folder within your source files. */,
30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52 | "outDir": "./build" /* Specify an output folder for all emitted files. */,
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
77 |
78 | /* Type Checking */
79 | "strict": true /* Enable all strict type-checking options. */,
80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 | }
103 | }
104 |
--------------------------------------------------------------------------------