Compare commits
42 Commits
e6231f86b4
...
ilias-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 79289457c9 | |||
| 5f386c0f4e | |||
| 33c8f863a1 | |||
|
|
dd16f42995 | ||
| f3e93bae19 | |||
| 07728cbbb0 | |||
| 17697d7f77 | |||
|
|
2829c11e4c | ||
|
|
0bcdb95b57 | ||
|
|
cabe02be57 | ||
|
|
59d4a44079 | ||
|
|
fde1f8ecfe | ||
|
|
9b4870995f | ||
|
|
2f447cef1a | ||
|
|
964ca236e8 | ||
|
|
9c1c670ccb | ||
|
|
ff9bd3ac8c | ||
|
|
51618c8858 | ||
|
|
54f6c5c053 | ||
|
|
8fecf7cb1f | ||
|
|
49a8af611f | ||
|
|
7c4457dea4 | ||
|
|
6096bdc4cb | ||
|
|
dd10b080e8 | ||
|
|
4cf909c607 | ||
|
|
13f4d43761 | ||
|
|
22a549e269 | ||
|
|
25c3e16c74 | ||
|
|
86db5df813 | ||
|
|
d1e824ab77 | ||
|
|
073c61977f | ||
|
|
1301c145e8 | ||
|
|
ac6dffa129 | ||
|
|
8e4cad4c85 | ||
|
|
d1091e570b | ||
|
|
4a5041a65e | ||
|
|
8d0fadc906 | ||
|
|
6bea0428f4 | ||
|
|
660f2e9d5c | ||
|
|
a9b683797b | ||
|
|
a3484f4525 | ||
|
|
876ef28221 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.idea
|
||||
.idea
|
||||
coverage/
|
||||
1
__mocks__/fileMock.js
Normal file
1
__mocks__/fileMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'test-file-stub';
|
||||
11
__tests__/AccountButtons.test.tsx
Normal file
11
__tests__/AccountButtons.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import AccountButtons from '../src/components/account/AccountButtons.jsx';
|
||||
|
||||
describe('AccountButtons Component', () => {
|
||||
it('should render the Back link', () => {
|
||||
render(<AccountButtons registered={false} />);
|
||||
const backLinkElement = screen.getByRole('link', { name: /back/i });
|
||||
expect(backLinkElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
18
__tests__/ActionButton.test.tsx
Normal file
18
__tests__/ActionButton.test.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import ActionButton from '../src/components/account/ActionButton.jsx';
|
||||
|
||||
describe('ActionButton Component', () => {
|
||||
it('should render with the correct title and call the action when clicked', () => {
|
||||
const mockAction = jest.fn();
|
||||
const title = 'Click Me';
|
||||
|
||||
render(<ActionButton action={mockAction} title={title} />);
|
||||
|
||||
const buttonElement = screen.getByText(title);
|
||||
expect(buttonElement).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(buttonElement);
|
||||
expect(mockAction).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
23
__tests__/Card.test.tsx
Normal file
23
__tests__/Card.test.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import Card from '../src/components/home/Card.jsx';
|
||||
|
||||
describe('Card Component', () => {
|
||||
it('should render the Card component with the given ID', () => {
|
||||
const testId = '123';
|
||||
render(<Card id={testId} color={"FFA500FF"}/>);
|
||||
|
||||
const cardElement = screen.getByText(/123/i);
|
||||
expect(cardElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should store the ID in local storage when clicked', () => {
|
||||
const testId = '456';
|
||||
render(<Card id={testId} color={"FFA500FF"}/>);
|
||||
|
||||
const cardElement = screen.getByText(/456/i);
|
||||
fireEvent.click(cardElement);
|
||||
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith('selectedId', testId);
|
||||
});
|
||||
});
|
||||
24
__tests__/Helloitem.test.tsx
Normal file
24
__tests__/Helloitem.test.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import HelloItem from '../src/components/account/HelloItem.jsx';
|
||||
|
||||
describe('HelloItem Component', () => {
|
||||
it('should display a personalized greeting when nickname is provided', () => {
|
||||
const nickname = 'JohnDoe';
|
||||
const id = '12345';
|
||||
|
||||
render(<HelloItem nickname={nickname} id={id} />);
|
||||
|
||||
const greetingElement = screen.getByText(`Hello, ${nickname}!`);
|
||||
const idElement = screen.getByText(`Your ID: ${id}`);
|
||||
expect(greetingElement).toBeInTheDocument();
|
||||
expect(idElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display a default message when nickname is not provided', () => {
|
||||
render(<HelloItem nickname="" id="12345" />);
|
||||
|
||||
const defaultMessage = screen.getByText("You don't have an account :(");
|
||||
expect(defaultMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
60
__tests__/Home.test.jsx
Normal file
60
__tests__/Home.test.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
import configureStore from "redux-mock-store";
|
||||
import Home from "src/pages/Home";
|
||||
import { useGetChatsQuery } from "src/backend/redux/api_slice";
|
||||
|
||||
// Mock Redux store
|
||||
const mockStore = configureStore([]);
|
||||
|
||||
// Mock the useGetChatsQuery hook
|
||||
jest.mock("src/backend/redux/api_slice", () => ({
|
||||
useGetChatsQuery: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("Home Page", () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({});
|
||||
});
|
||||
|
||||
test("renders Home page with loading state", () => {
|
||||
useGetChatsQuery.mockReturnValue({ isLoading: true });
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<Home />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders Home page with chat data", () => {
|
||||
const chatsData = [{ id: 1, name: "Chat 1" }, { id: 2, name: "Chat 2" }];
|
||||
useGetChatsQuery.mockReturnValue({ data: chatsData, isLoading: false });
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<Home />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Chat 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Chat 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders error message when fetching chats fails", () => {
|
||||
useGetChatsQuery.mockReturnValue({ error: true, isLoading: false });
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<Home />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/error/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
41
__tests__/InputField.test.tsx
Normal file
41
__tests__/InputField.test.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import InputField from '../src/components/reg/InputField.jsx';
|
||||
|
||||
describe('InputField Component', () => {
|
||||
it('should render with the correct title and placeholder', () => {
|
||||
const title = 'Username';
|
||||
const placeholder = 'Enter your username';
|
||||
|
||||
render(<InputField title={title} placeholder={placeholder} value="" setValue={() => {}} />);
|
||||
|
||||
const titleElement = screen.getByText(title);
|
||||
const inputElement = screen.getByPlaceholderText(placeholder);
|
||||
|
||||
expect(titleElement).toBeInTheDocument();
|
||||
expect(inputElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call setValue on input change', () => {
|
||||
const mockSetValue = jest.fn();
|
||||
const newValue = 'testUser';
|
||||
|
||||
render(<InputField title="Username" value="" setValue={mockSetValue} />);
|
||||
|
||||
const inputElement = screen.getByRole('textbox');
|
||||
fireEvent.change(inputElement, { target: { value: newValue } });
|
||||
|
||||
expect(mockSetValue).toHaveBeenCalledWith(newValue);
|
||||
});
|
||||
|
||||
it('should call submit function when Enter key is pressed', () => {
|
||||
const mockSubmit = jest.fn();
|
||||
|
||||
render(<InputField title="Username" value="" setValue={() => {}} submit={mockSubmit} />);
|
||||
|
||||
const inputElement = screen.getByRole('textbox');
|
||||
fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter' });
|
||||
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
16
__tests__/NavButton.test.tsx
Normal file
16
__tests__/NavButton.test.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import NavButton from '../src/components/init/NavButton.jsx';
|
||||
|
||||
describe('NavButton Component', () => {
|
||||
it('should render the NavButton with the correct text and link', () => {
|
||||
const navLink = '/home';
|
||||
const buttonText = 'Go Home';
|
||||
|
||||
render(<NavButton nav={navLink} text={buttonText} />);
|
||||
|
||||
const linkElement = screen.getByText(buttonText);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
expect(linkElement.closest('a')).toHaveAttribute('href', navLink);
|
||||
});
|
||||
});
|
||||
23
__tests__/Search.test.tsx
Normal file
23
__tests__/Search.test.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import Search from '../src/components/home/Search.jsx';
|
||||
|
||||
describe('Search Component', () => {
|
||||
it('should render the Search button', () => {
|
||||
render(<Search search={() => {}} item="testItem" />);
|
||||
const searchButton = screen.getByText(/find/i);
|
||||
expect(searchButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call the search function with the correct item when clicked', () => {
|
||||
const mockSearch = jest.fn();
|
||||
const item = 'testItem';
|
||||
|
||||
render(<Search search={mockSearch} item={item} />);
|
||||
|
||||
const searchButton = screen.getByText(/find/i);
|
||||
fireEvent.click(searchButton);
|
||||
|
||||
expect(mockSearch).toHaveBeenCalledWith(item);
|
||||
});
|
||||
});
|
||||
52
__tests__/SignIn.test.jsx
Normal file
52
__tests__/SignIn.test.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import SignIn from "src/pages/SignIn";
|
||||
import { displayMessage } from "src/backend/notifications/notifications";
|
||||
import { post } from "src/backend/api";
|
||||
|
||||
// Mock the displayMessage and post functions
|
||||
jest.mock("src/backend/notifications/notifications", () => ({
|
||||
displayMessage: jest.fn(),
|
||||
}));
|
||||
jest.mock("src/backend/api", () => ({
|
||||
post: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("SignIn Page", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("renders SignIn form", () => {
|
||||
render(<SignIn />);
|
||||
expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/sign in/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("displays error message on failed login", async () => {
|
||||
post.mockResolvedValueOnce({ ok: false, data: { message: "Invalid credentials" } });
|
||||
|
||||
render(<SignIn />);
|
||||
fireEvent.change(screen.getByLabelText(/username/i), { target: { value: "user" } });
|
||||
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: "password" } });
|
||||
fireEvent.click(screen.getByText(/sign in/i));
|
||||
|
||||
expect(await screen.findByText(/invalid credentials/i)).toBeInTheDocument();
|
||||
expect(displayMessage).toHaveBeenCalledWith("Invalid credentials", "error");
|
||||
});
|
||||
|
||||
test("displays additional info message after multiple login attempts", async () => {
|
||||
post.mockResolvedValueOnce({ ok: false, data: { message: "Invalid credentials" } });
|
||||
|
||||
render(<SignIn />);
|
||||
fireEvent.change(screen.getByLabelText(/username/i), { target: { value: "user" } });
|
||||
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: "password" } });
|
||||
|
||||
// Simulate two failed login attempts
|
||||
fireEvent.click(screen.getByText(/sign in/i));
|
||||
fireEvent.click(screen.getByText(/sign in/i));
|
||||
|
||||
expect(displayMessage).toHaveBeenCalledWith("Note that you need to enter your ID name", "info");
|
||||
});
|
||||
});
|
||||
13
__tests__/helloworld.test.tsx.no
Normal file
13
__tests__/helloworld.test.tsx.no
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
describe('Hello World Test', () => {
|
||||
it('should display hello world', () => {
|
||||
// Render a simple component
|
||||
render(<div>Hello World</div>);
|
||||
|
||||
// Check if "Hello World" is in the document
|
||||
const helloWorldElement = screen.getByText(/hello world/i);
|
||||
expect(helloWorldElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
7
babel.config.js
Normal file
7
babel.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript'],
|
||||
};
|
||||
|
||||
@@ -23,5 +23,6 @@ module.exports = {
|
||||
},
|
||||
config: {
|
||||
"enterfront.api": "/api",
|
||||
// paste stand URL to config
|
||||
},
|
||||
};
|
||||
|
||||
28
jest.config.ts
Normal file
28
jest.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { Config } from 'jest';
|
||||
import { defaults } from 'jest-config';
|
||||
|
||||
const config: Config = {
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
"src/components/**/*.{js,jsx,ts,tsx}", // Include all components
|
||||
"src/pages/**/*.{js,jsx,ts,tsx}",
|
||||
"!src/**/*.test.{js,jsx,ts,tsx}", // Exclude test files
|
||||
"!src/**/index.{js,jsx,ts,tsx}", // Optionally exclude index files
|
||||
],
|
||||
coverageDirectory: "coverage",
|
||||
coverageProvider: "v8",
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testEnvironment: "jsdom",
|
||||
// transform: {
|
||||
// '^.+\\.(ts|tsx|js|jsx)$': 'babel-jest',
|
||||
// },
|
||||
moduleNameMapper: {
|
||||
"^@/src/(.*)$": "<rootDir>/src/$1", // Map '@/src' to the 'src' folder
|
||||
"^src/(.*)$": "<rootDir>/src/$1", // Map 'src' to the 'src' folder
|
||||
"\\.(svg|png|jpg|jpeg|gif)$": "<rootDir>/__mocks__/fileMock.js", // Add this line
|
||||
},
|
||||
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx', 'js', 'jsx'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
17
jest.setup.ts
Normal file
17
jest.setup.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import '@testing-library/jest-dom';
|
||||
import mockedConfig from './bro.config.js'
|
||||
|
||||
jest.mock('@brojs/cli', () => {
|
||||
return {
|
||||
getNavigations() {
|
||||
return mockedConfig.navigations
|
||||
},
|
||||
getNavigationsValue(key) {
|
||||
return mockedConfig.navigations[key]
|
||||
},
|
||||
getConfigValue(key) {
|
||||
return mockedConfig.config[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
5336
package-lock.json
generated
5336
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -3,15 +3,22 @@
|
||||
"@brojs/cli": "^1.0.0",
|
||||
"@brojs/create": "^1.0.0",
|
||||
"@ijl/cli": "^5.1.0",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"express": "^4.19.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-emoji-picker": "^1.0.13",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-toastify": "^10.0.5",
|
||||
"socket.io": "^4.8.0",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"styled-components": "^6.1.13",
|
||||
"typescript": "^5.5.4",
|
||||
"ws": "^8.18.0"
|
||||
@@ -21,8 +28,25 @@
|
||||
"start": "brojs server --port=8099 --with-open-browser",
|
||||
"build": "npm run clean && brojs build --dev",
|
||||
"build:prod": "npm run clean && brojs build",
|
||||
"clean": "rimraf dist"
|
||||
"clean": "rimraf dist",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watchAll"
|
||||
},
|
||||
"name": "enterfront",
|
||||
"version": "0.2.3"
|
||||
"version": "0.5.4",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.8",
|
||||
"@babel/preset-env": "^7.25.8",
|
||||
"@babel/preset-react": "^7.25.7",
|
||||
"@babel/preset-typescript": "^7.25.7",
|
||||
"@testing-library/jest-dom": "^6.6.2",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@types/jest": "^29.5.13",
|
||||
"babel-jest": "^29.7.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
37
src/app.tsx
37
src/app.tsx
@@ -1,15 +1,42 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import React, {useEffect} from 'react';
|
||||
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Dashboard } from './dashboard';
|
||||
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './backend/redux/store.js'; // Import your store
|
||||
|
||||
|
||||
import './index.css'
|
||||
import {displayMessage} from "./backend/notifications/notifications.js";
|
||||
import {MessageType} from "./backend/notifications/message";
|
||||
|
||||
const App = () => {
|
||||
useEffect(() => {
|
||||
document.title = 'Enterfront';
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const msg = localStorage.getItem('message');
|
||||
|
||||
if (!msg) return;
|
||||
|
||||
displayMessage(msg, MessageType.SUCCESS);
|
||||
localStorage.removeItem('message');
|
||||
}, []);
|
||||
|
||||
return(
|
||||
<BrowserRouter>
|
||||
<Dashboard />
|
||||
</BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<Dashboard />
|
||||
</BrowserRouter>
|
||||
|
||||
<ToastContainer/>
|
||||
</Provider>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
60
src/backend/api.js
Normal file
60
src/backend/api.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import {getConfigValue} from "@brojs/cli";
|
||||
|
||||
|
||||
const LOCAL = "http://localhost:8099";
|
||||
const DEV = "https://dev.bro-js.ru";
|
||||
|
||||
export const BASE_API_URL = LOCAL + getConfigValue("enterfront.api");
|
||||
|
||||
// fetch(`${BASE_API_URL}/books/list`)
|
||||
|
||||
export async function post(path, body) {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
const res = await fetch(`${BASE_API_URL}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": token ? `Bearer ${token}` : undefined
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
console.log("Initial data from API:", res)
|
||||
const data = JSON.parse(await res.text());
|
||||
console.log("Data from API:", data)
|
||||
|
||||
if (res.status === 200) {
|
||||
console.log("Received post:", data);
|
||||
|
||||
return {ok: true, data: data};
|
||||
} else {
|
||||
console.log("Error during post:", data.message);
|
||||
|
||||
return {ok: false, data: data};
|
||||
}
|
||||
}
|
||||
|
||||
export async function get(path){
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
const res = await fetch(`${BASE_API_URL}${path}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": token ? `Bearer ${token}` : undefined
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Data from API:", res)
|
||||
const data = await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
console.log("Received get:", data);
|
||||
|
||||
return {ok: true, data: data};
|
||||
} else {
|
||||
console.log("Error during get:", data.message);
|
||||
|
||||
return {ok: false, data: data};
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
const WebSocket = require("ws");
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
const clients = new Map();
|
||||
|
||||
wss.on("connection", (ws, req) => {
|
||||
console.log("New client connected");
|
||||
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
if (parsedMessage.type === "register") {
|
||||
clients.set(parsedMessage.userId, ws);
|
||||
console.log(`User registered: ${parsedMessage.userId}`);
|
||||
} else if (parsedMessage.type === "message") {
|
||||
const recipientWs = clients.get(parsedMessage.recipientId);
|
||||
if (recipientWs) {
|
||||
recipientWs.send(
|
||||
JSON.stringify({
|
||||
senderId: parsedMessage.senderId,
|
||||
message: parsedMessage.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.error(`User ${parsedMessage.recipientId} is not connected.`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error processing message:", err.message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Client disconnected");
|
||||
[...clients.entries()].forEach(([userId, clientWs]) => {
|
||||
if (clientWs === ws) {
|
||||
clients.delete(userId);
|
||||
console.log(`User disconnected: ${userId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
export default class Interlocutor {
|
||||
constructor(id, name) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
static name;
|
||||
static id;
|
||||
}
|
||||
6
src/backend/notifications/message.tsx
Normal file
6
src/backend/notifications/message.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum MessageType {
|
||||
ERROR,
|
||||
SUCCESS,
|
||||
INFO,
|
||||
WARN
|
||||
}
|
||||
28
src/backend/notifications/notifications.js
Normal file
28
src/backend/notifications/notifications.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import {toast} from "react-toastify";
|
||||
import {MessageType} from "./message.tsx";
|
||||
|
||||
export const displayMessage = (message, type) => {
|
||||
switch (type) {
|
||||
default:
|
||||
case MessageType.ERROR:
|
||||
toast.error(message, {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
break;
|
||||
case MessageType.INFO:
|
||||
toast.info(message, {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
break;
|
||||
case MessageType.SUCCESS:
|
||||
toast.success(message, {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
break;
|
||||
case MessageType.WARN:
|
||||
toast.warn(message, {
|
||||
position: 'bottom-right',
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
34
src/backend/redux/api_slice.js
Normal file
34
src/backend/redux/api_slice.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
import {getConfigValue} from "@brojs/cli";
|
||||
|
||||
import { BASE_API_URL } from "../api.js";
|
||||
|
||||
const baseQuery = fetchBaseQuery({
|
||||
baseUrl: BASE_API_URL,
|
||||
prepareHeaders: (headers) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
});
|
||||
|
||||
export const apiSlice = createApi({
|
||||
reducerPath: 'api',
|
||||
baseQuery,
|
||||
endpoints: (builder) => ({
|
||||
getChats: builder.query({
|
||||
query: (username) => `/chat/list/${username}`,
|
||||
}),
|
||||
postChat: builder.mutation({
|
||||
query: ({ id1, id2 }) => ({
|
||||
url: `/chat/item/${id1}/${id2}`,
|
||||
method: 'POST',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components
|
||||
export const { useGetChatsQuery, usePostChatMutation } = apiSlice;
|
||||
12
src/backend/redux/store.js
Normal file
12
src/backend/redux/store.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { apiSlice } from './api_slice';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
[apiSlice.reducerPath]: apiSlice.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(apiSlice.middleware),
|
||||
});
|
||||
|
||||
export default store;
|
||||
31
src/backend/server.js
Normal file
31
src/backend/server.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const express = require("express");
|
||||
const http = require("http");
|
||||
const { Server } = require("socket.io");
|
||||
require("dotenv").config();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("New connection:", socket.id);
|
||||
|
||||
// For messages
|
||||
socket.on("sendMessage", (message) => {
|
||||
console.log("Message received:", message);
|
||||
socket.broadcast.emit("receiveMessage", message);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("User disconnected:", socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Socket.IO Server is running");
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 8099;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
export default class User {
|
||||
constructor(id, name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.status = "online";
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,48 @@
|
||||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import { URLs } from "../../__data__/urls";
|
||||
import ActionButton from "./ActionButton.jsx";
|
||||
import InputField from "../reg/InputField.jsx";
|
||||
|
||||
const AccountButtons = (props) => {
|
||||
const [chName, setChName] = useState(false);
|
||||
const [chPassword, setChPassword] = useState(false);
|
||||
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
return (
|
||||
<div className="account-buttons">
|
||||
<ActionButton title={"Exit"} action={props.exitHandler}/>
|
||||
<ActionButton title={"Change Name"} action={props.changeNameHandler}/>
|
||||
<ActionButton title={"Change Pass"} action={props.changePassHandler}/>
|
||||
{props.registered ? (
|
||||
<>
|
||||
<ActionButton title={"Exit"} action={props.exitHandler}/>
|
||||
<ActionButton title={"Change Name"} action={() => setChName(true)}/>
|
||||
{chName ? (
|
||||
<InputField
|
||||
title={""}
|
||||
value={nickname}
|
||||
setValue={setNickname}
|
||||
placeholder='Enter your new nickname'
|
||||
|
||||
submit={nickname}
|
||||
enter={props.changeNameHandler}
|
||||
/>
|
||||
) : null}
|
||||
<ActionButton title={"Change Pass"} action={() => setChPassword(true)}/>
|
||||
{chPassword ? (
|
||||
<div>
|
||||
<InputField
|
||||
title={""}
|
||||
value={password}
|
||||
setValue={setPassword}
|
||||
placeholder='Enter your new password'
|
||||
|
||||
submit={password}
|
||||
enter={props.changePassHandler}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<a className="MyButton mclaren-regular" href={URLs.home.url}>Back</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
18
src/components/account/HelloItem.jsx
Normal file
18
src/components/account/HelloItem.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
const HelloItem = (props) => {
|
||||
return (
|
||||
<div className="hello-item-class">
|
||||
{!!props.nickname ? (
|
||||
<>
|
||||
<h1 className="mclaren-regular">Hello, {props.nickname}!</h1>
|
||||
<p className="mclaren-regular">Your ID: {props.id}</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="mclaren-regular">You don't have an account :(</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelloItem;
|
||||
@@ -3,6 +3,8 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.account-buttons a {
|
||||
@@ -20,10 +22,33 @@
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hello-item-class {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hello-item-class h1 {
|
||||
font-size: 4vw;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hello-item-class p {
|
||||
font-size: 1.5vw;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.account-buttons a {
|
||||
font-size: 2.5vh;
|
||||
width: 60vw;
|
||||
margin-top: 3vh;
|
||||
}
|
||||
|
||||
.hello-item-class h1 {
|
||||
font-size: 5vh;
|
||||
}
|
||||
|
||||
.hello-item-class p {
|
||||
font-size: 2vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,92 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Card from "./Card.jsx";
|
||||
import { get } from "../../backend/api";
|
||||
import { displayMessage } from "../../backend/notifications/notifications";
|
||||
import { MessageType } from "../../backend/notifications/message";
|
||||
|
||||
const ChatsList = (props) => {
|
||||
const { chats } = props;
|
||||
const { chats } = props;
|
||||
const [customChats, setCustomChats] = useState([]);
|
||||
|
||||
const colorMap = {
|
||||
orange: 'FFA500FF',
|
||||
aqua: '00FFFFFF',
|
||||
crimson: 'DC143CFF',
|
||||
red: 'FF0000FF',
|
||||
violet: '8A2BE2FF',
|
||||
seagreen: '20B2AAFF',
|
||||
green: 'ADFF2FFF',
|
||||
blue: '0000FFFF',
|
||||
pink: 'FF1493FF',
|
||||
cyan: '72FAFAFF'
|
||||
}
|
||||
const updateList = async () => {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) return null;
|
||||
|
||||
function getColor(chatId) {
|
||||
const keys = Object.keys(colorMap);
|
||||
const index = chatId % keys.length;
|
||||
return colorMap[keys[index]];
|
||||
}
|
||||
const updatedChats = await Promise.all(
|
||||
chats.map(async (chat) => {
|
||||
const interlocutorId = chat.id1 === username ? chat.id2 : chat.id1;
|
||||
|
||||
const { ok, data } = await get("/auth/" + interlocutorId);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return null;
|
||||
}
|
||||
|
||||
const interlocutor = data.user;
|
||||
|
||||
const lastMessage =
|
||||
chat.messages.length > 0
|
||||
? chat.messages[chat.messages.length - 1]
|
||||
: { data: "", timestamp: new Date(0).toISOString() };
|
||||
|
||||
return {
|
||||
id: interlocutorId,
|
||||
name: interlocutor.nickname,
|
||||
lastMessageData: lastMessage.data,
|
||||
lastMessageTimestamp: lastMessage.timestamp,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validChats = updatedChats.filter((chat) => chat !== null);
|
||||
|
||||
validChats.sort(
|
||||
(a, b) =>
|
||||
new Date(b.lastMessageTimestamp) - new Date(a.lastMessageTimestamp)
|
||||
);
|
||||
|
||||
setCustomChats(validChats);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateList().then();
|
||||
}, [chats]);
|
||||
|
||||
const colorMap = {
|
||||
orange: "FFA500FF",
|
||||
aqua: "00FFFFFF",
|
||||
crimson: "DC143CFF",
|
||||
red: "FF0000FF",
|
||||
violet: "8A2BE2FF",
|
||||
seagreen: "20B2AAFF",
|
||||
green: "ADFF2FFF",
|
||||
blue: "0000FFFF",
|
||||
pink: "FF1493FF",
|
||||
cyan: "72FAFAFF",
|
||||
};
|
||||
|
||||
function getColor(chatId) {
|
||||
const keys = Object.keys(colorMap);
|
||||
const numericId = Array.from(chatId).reduce(
|
||||
(sum, char) => sum + char.charCodeAt(0),
|
||||
0
|
||||
);
|
||||
const index = numericId % keys.length;
|
||||
return colorMap[keys[index]];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ChatsList">
|
||||
{chats.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
name={item.name}
|
||||
lastMessage={item.lastMessage}
|
||||
id={item.id}
|
||||
color={getColor(item.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="ChatsList">
|
||||
{customChats.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
name={item.name}
|
||||
lastMessage={item.lastMessageData}
|
||||
id={item.id}
|
||||
color={getColor(item.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
11
src/components/home/Search.jsx
Normal file
11
src/components/home/Search.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
const Search = (props) => {
|
||||
return (
|
||||
<a className="MyButton search-class mclaren-regular" onClick={() => {
|
||||
props.search(props.item);
|
||||
}}>Find</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
@@ -12,11 +12,30 @@
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.search-class {
|
||||
margin-top: 2vw;
|
||||
margin-bottom: 4vw;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input div input {
|
||||
background-color: white;
|
||||
color: black;
|
||||
|
||||
border: 3px solid black;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.homeTitle {
|
||||
font-size: 8vh;
|
||||
}
|
||||
|
||||
.search-class {
|
||||
margin-top: 3vh;
|
||||
}
|
||||
}
|
||||
|
||||
.chatIcon {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const InputField = (props) => {
|
||||
console.log('class:', props.className)
|
||||
return (
|
||||
<div>
|
||||
<p>{props.title}</p>
|
||||
@@ -8,6 +9,14 @@ const InputField = (props) => {
|
||||
onChange={(e) => props.setValue(e.target.value)}
|
||||
value={props.value}
|
||||
className="Input"
|
||||
placeholder={(props.placeholder) ? props.placeholder : ''}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (props.submit) {
|
||||
props.submit();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,78 @@
|
||||
import React from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import AccountButtons from "../components/account/AccountButtons.jsx";
|
||||
import userIcon from "../../images/user.svg";
|
||||
import {get, post} from "../backend/api";
|
||||
import {displayMessage} from "../backend/notifications/notifications";
|
||||
import {MessageType} from "../backend/notifications/message";
|
||||
import HelloItem from "../components/account/HelloItem.jsx";
|
||||
import { URLs } from "../__data__/urls";
|
||||
|
||||
const Account = () => {
|
||||
const exitHandler = () => {}
|
||||
const changeNameHandler = () => {}
|
||||
const changePassHandler = () => {}
|
||||
const exitHandler = () => {
|
||||
localStorage.removeItem("username");
|
||||
localStorage.removeItem("token");
|
||||
|
||||
localStorage.setItem("message", "Exited successfully!");
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [id, setId] = useState("");
|
||||
|
||||
async function changeNameHandler (newNickname) {
|
||||
if (!newNickname) return;
|
||||
|
||||
const {ok, data} = await post('/change/nickname', {id: id, newNickname: newNickname});
|
||||
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
} else {
|
||||
localStorage.setItem("message", "Name was changed");
|
||||
window.location.href = URLs.account.url;
|
||||
}
|
||||
}
|
||||
|
||||
async function changePassHandler (newPass){
|
||||
if (!newPass) return;
|
||||
|
||||
const {ok, data} = await post('/change/password', {id: id, newPassword: newPass});
|
||||
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
} else {
|
||||
localStorage.setItem("message", "Password was changed");
|
||||
window.location.href = URLs.account.url;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
const {ok, data} = await get('/auth/' + username);
|
||||
if (!ok) {
|
||||
displayMessage("Some error with auth:" + data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
setNickname(data.user.nickname);
|
||||
setId(username);
|
||||
}
|
||||
|
||||
useEffect(() => {getUser().then()}, [])
|
||||
|
||||
return (
|
||||
<div className="account-items">
|
||||
<img src={userIcon} alt="user" />
|
||||
<HelloItem nickname={nickname} id={id} />
|
||||
<AccountButtons
|
||||
exitHandler={exitHandler}
|
||||
changeNameHandler={changeNameHandler}
|
||||
changePassHandler={changePassHandler}
|
||||
registered={!!nickname}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,10 @@ import React, { useEffect, useState, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./css/Chat.css";
|
||||
import { FaPaperPlane, FaSmile } from "react-icons/fa";
|
||||
import { get, post } from "../backend/api";
|
||||
import { displayMessage } from "../backend/notifications/notifications";
|
||||
import { MessageType } from "../backend/notifications/message";
|
||||
import io from "socket.io-client";
|
||||
|
||||
const emojis = [
|
||||
"😀",
|
||||
@@ -74,7 +78,7 @@ const emojis = [
|
||||
];
|
||||
|
||||
const Chat = () => {
|
||||
const [interlocutorId, setInterlocutorId] = useState(0);
|
||||
const [interlocutorId, setInterlocutorId] = useState("");
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [newMessage, setNewMessage] = useState("");
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
@@ -82,54 +86,82 @@ const Chat = () => {
|
||||
const chatRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [myId, setMyId] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const id = parseInt(localStorage.getItem("interlocutorId"), 10) || 0;
|
||||
const id = localStorage.getItem("interlocutorId");
|
||||
setInterlocutorId(id);
|
||||
|
||||
socket.current = new WebSocket("ws://localhost:8080");
|
||||
const username = localStorage.getItem("username");
|
||||
setMyId(username);
|
||||
|
||||
socket.current.onopen = () => {
|
||||
console.log("WebSocket connected");
|
||||
socket.current.send(
|
||||
JSON.stringify({ type: "register", userId: "yourUserId" })
|
||||
);
|
||||
};
|
||||
if (!id || !username) {
|
||||
displayMessage("You are not logged in!", MessageType.WARN);
|
||||
return () => {};
|
||||
}
|
||||
|
||||
socket.current.onmessage = (event) => {
|
||||
const receivedData = JSON.parse(event.data);
|
||||
setMessages((prev) => [...prev, receivedData]);
|
||||
};
|
||||
socket.current = io("http://localhost:8099");
|
||||
|
||||
socket.current.onerror = (event) => {
|
||||
console.error("WebSocket error observed:", event);
|
||||
};
|
||||
socket.current.on("receiveMessage", (message) => {
|
||||
setMessages((prev) => [...prev, message]);
|
||||
});
|
||||
|
||||
socket.current.onclose = () => {
|
||||
console.log("WebSocket closed");
|
||||
};
|
||||
socket.current.on("connect_error", (err) => {
|
||||
console.error("Connection Error:", err.message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.current.close();
|
||||
socket.current.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// retrieveMessages().then();
|
||||
const interval = setInterval(() => {
|
||||
retrieveMessages().then()
|
||||
}, 2000);
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [myId, interlocutorId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatRef.current) {
|
||||
chatRef.current.scrollTop = chatRef.current.scrollHeight;
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
async function sendMessageToDB(messageData) {
|
||||
const { ok, data } = await post(
|
||||
"/chat/message/" + myId + "/" + interlocutorId,
|
||||
{ message: messageData }
|
||||
);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
async function retrieveMessages() {
|
||||
if (!myId || !interlocutorId) return;
|
||||
|
||||
const { ok, data } = await get("/chat/item/" + myId + "/" + interlocutorId);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
setMessages(data.chat.messages);
|
||||
}
|
||||
|
||||
const sendMessage = () => {
|
||||
if (newMessage.trim()) {
|
||||
const messageData = {
|
||||
type: "message",
|
||||
senderId: "yourUserId",
|
||||
senderId: myId,
|
||||
recipientId: interlocutorId,
|
||||
message: newMessage,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
data: newMessage,
|
||||
timestamp: new Date().toLocaleString(),
|
||||
};
|
||||
socket.current.send(JSON.stringify(messageData));
|
||||
socket.current.emit("sendMessage", messageData);
|
||||
setMessages((prev) => [...prev, messageData]);
|
||||
sendMessageToDB(messageData).then();
|
||||
setNewMessage("");
|
||||
}
|
||||
};
|
||||
@@ -154,20 +186,18 @@ const Chat = () => {
|
||||
className="home-button"
|
||||
>
|
||||
Home
|
||||
</button>{" "}
|
||||
{}
|
||||
</button>
|
||||
</div>
|
||||
<div className="chat-messages" ref={chatRef}>
|
||||
{messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message-bubble ${
|
||||
msg.senderId === "yourUserId" ? "sent" : "received"
|
||||
msg.senderId === myId ? "sent" : "received"
|
||||
}`}
|
||||
>
|
||||
<div className="message-content">
|
||||
<b>{msg.senderId === "yourUserId" ? "You" : "Interlocutor"}:</b>{" "}
|
||||
{msg.message}
|
||||
<b>{msg.senderId === myId ? "You" : "They"}:</b> {msg.data}
|
||||
</div>
|
||||
<span className="message-timestamp">{msg.timestamp}</span>
|
||||
</div>
|
||||
@@ -180,7 +210,7 @@ const Chat = () => {
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
placeholder="Type a message..."
|
||||
className="chat-input"
|
||||
onKeyPress={handleKeyPress}
|
||||
onKeyDown={handleKeyPress}
|
||||
/>
|
||||
<button
|
||||
className="emoji-button"
|
||||
|
||||
@@ -1,76 +1,115 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import HomeTitle from "../components/home/HomeTitle.jsx";
|
||||
import ChatsList from "../components/home/ChatsList.jsx";
|
||||
import Header from "../components/home/Header.jsx";
|
||||
import { displayMessage } from "../backend/notifications/notifications";
|
||||
import { MessageType } from "../backend/notifications/message";
|
||||
import { useGetChatsQuery, usePostChatMutation } from "../backend/redux/api_slice"; // Update the import based on your API slice
|
||||
import InputField from "../components/reg/InputField.jsx";
|
||||
import Search from "../components/home/Search.jsx";
|
||||
import { URLs } from "../__data__/urls";
|
||||
|
||||
const Home = () => {
|
||||
const [chats, setChats] = useState([]); // Retained original variable name
|
||||
const [interlocutor, setInterlocutor] = useState("");
|
||||
|
||||
// temp for testing
|
||||
const chats = [
|
||||
{
|
||||
name: "Alice Johnson",
|
||||
id: 123456,
|
||||
lastMessage: "See you later!"
|
||||
},
|
||||
{
|
||||
name: "Bob Smith",
|
||||
id: 654321,
|
||||
lastMessage: "Got it, thanks!"
|
||||
},
|
||||
{
|
||||
name: "Charlie Brown",
|
||||
id: 234567,
|
||||
lastMessage: "How's the project going? How's the project going? How's the project going?" +
|
||||
"How's the project going? How's the project going?"
|
||||
},
|
||||
{
|
||||
name: "David Clark",
|
||||
id: 765432,
|
||||
lastMessage: "I'll send the files."
|
||||
},
|
||||
{
|
||||
name: "Eve Adams",
|
||||
id: 345678,
|
||||
lastMessage: "Let's meet tomorrow."
|
||||
},
|
||||
{
|
||||
name: "Frank Wright",
|
||||
id: 876543,
|
||||
lastMessage: "Can you review this?"
|
||||
},
|
||||
{
|
||||
name: "Grace Lee",
|
||||
id: 456789,
|
||||
lastMessage: "Thanks for your help!"
|
||||
},
|
||||
{
|
||||
name: "Hannah Scott",
|
||||
id: 987654,
|
||||
lastMessage: "See you at the meeting."
|
||||
},
|
||||
{
|
||||
name: "Ian Davis",
|
||||
id: 567890,
|
||||
lastMessage: "Let me know when you're free."
|
||||
},
|
||||
{
|
||||
name: "Jill Thompson",
|
||||
id: 678901,
|
||||
lastMessage: "I'll catch up with you later."
|
||||
}
|
||||
];
|
||||
const username = localStorage.getItem("username");
|
||||
|
||||
return (
|
||||
// Use Redux Queries
|
||||
const { data: chatsData, error: getError, isLoading: isGetting } = useGetChatsQuery(username, {
|
||||
skip: !username
|
||||
});
|
||||
|
||||
console.log('From Redux:', chatsData);
|
||||
|
||||
const [createChat, { error: postError }] = usePostChatMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (getError) {
|
||||
displayMessage(getError.message, MessageType.ERROR);
|
||||
}
|
||||
if (getError) {
|
||||
displayMessage(getError.message, MessageType.ERROR);
|
||||
}
|
||||
}, [getError, postError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatsData) {
|
||||
// setChats(chatsData.chats);
|
||||
|
||||
let data = chatsData.chats;
|
||||
|
||||
try {
|
||||
const sortedChats = [...data].sort((a, b) => {
|
||||
const lastMessageA = a.messages[a.messages.length - 1];
|
||||
const lastMessageB = b.messages[b.messages.length - 1];
|
||||
|
||||
const dateA = new Date(lastMessageA.timestamp);
|
||||
const dateB = new Date(lastMessageB.timestamp);
|
||||
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
setChats(sortedChats);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}, [chatsData]);
|
||||
|
||||
|
||||
const createChatHandler = async (alias) => {
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
displayMessage("Sent", MessageType.INFO);
|
||||
|
||||
try {
|
||||
const data = await createChat({ id1: alias, id2: username }).unwrap(); // Using unwrap to handle promise rejection
|
||||
localStorage.setItem("message", "Successfully opened chat!");
|
||||
localStorage.setItem("interlocutorId", alias);
|
||||
window.location.href = URLs.chat.url;
|
||||
} catch (error) {
|
||||
displayMessage(error.data.message, MessageType.ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="homeWrapper">
|
||||
<div className="headerPos">
|
||||
<Header/>
|
||||
</div>
|
||||
<div className="headerPos">
|
||||
<Header />
|
||||
</div>
|
||||
|
||||
<HomeTitle />
|
||||
|
||||
<div className="search-input">
|
||||
<InputField
|
||||
title="Create new chat"
|
||||
value={interlocutor}
|
||||
setValue={setInterlocutor}
|
||||
placeholder="Enter the username (id)"
|
||||
|
||||
enter={createChatHandler}
|
||||
submit={interlocutor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isGetting ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<>
|
||||
<Search search={createChatHandler} item={interlocutor} />
|
||||
|
||||
<p>Your chats</p>
|
||||
<ChatsList chats={chats} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<HomeTitle/>
|
||||
<p>Your chats</p>
|
||||
<ChatsList chats={chats} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Home
|
||||
export default Home;
|
||||
|
||||
@@ -3,12 +3,42 @@ import InputField from "../components/reg/InputField.jsx";
|
||||
import LoginButtons from "../components/reg/LoginButtons.jsx";
|
||||
import LoginTitle from "../components/reg/loginTitle.jsx";
|
||||
|
||||
import {MessageType} from "../backend/notifications/message.tsx";
|
||||
import {displayMessage} from "../backend/notifications/notifications.js";
|
||||
import {post} from "../backend/api.js";
|
||||
import {URLs} from "../__data__/urls";
|
||||
|
||||
const SignIn = () => {
|
||||
const [name, setName] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const submit = (e) => {
|
||||
console.log('Sign In!')
|
||||
const [nameErrorsCounter, setNameErrorsCounter] = useState(0);
|
||||
|
||||
async function submit() {
|
||||
console.log('Sign In!')
|
||||
|
||||
const {ok, data} = await post('/auth/login', {name: name, password: password});
|
||||
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
|
||||
if (nameErrorsCounter >= 1) {
|
||||
displayMessage("Note that you need to enter your ID name", MessageType.INFO);
|
||||
setNameErrorsCounter(0);
|
||||
} else {
|
||||
setNameErrorsCounter(nameErrorsCounter + 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('username', name);
|
||||
|
||||
setNameErrorsCounter(0);
|
||||
|
||||
localStorage.setItem('message', 'Successfully logged in!');
|
||||
window.location.href = URLs.baseUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,20 +2,60 @@ import React, {useState} from 'react';
|
||||
import InputField from "../components/reg/InputField.jsx";
|
||||
import LoginButtons from "../components/reg/LoginButtons.jsx";
|
||||
import LoginTitle from "../components/reg/loginTitle.jsx";
|
||||
import {post} from "../backend/api";
|
||||
import {displayMessage} from "../backend/notifications/notifications";
|
||||
import {MessageType} from "../backend/notifications/message";
|
||||
import { URLs } from "../__data__/urls";
|
||||
|
||||
|
||||
const SignUp = () => {
|
||||
const [name, setName] = useState("");
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [repeatPassword, setRepeatPassword] = useState("");
|
||||
|
||||
const submit = (e) => {
|
||||
console.log('Sign Up!')
|
||||
async function login(name, password) {
|
||||
const {ok, data} = await post('/auth/login', {name: name, password: password});
|
||||
return {loginStatus: ok, loginData: data};
|
||||
}
|
||||
|
||||
async function submit () {
|
||||
console.log('Sign Up!');
|
||||
|
||||
if (password !== repeatPassword) {
|
||||
displayMessage("Passwords don't match", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
const {ok, data} = await post('/auth/reg',
|
||||
{name: name, password: password, nickname: nickname});
|
||||
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const { loginStatus, loginData } = await login(name, password);
|
||||
|
||||
console.log(loginStatus, loginData)
|
||||
|
||||
if (!loginStatus) {
|
||||
displayMessage(loginData.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('token', loginData.token);
|
||||
localStorage.setItem('username', name);
|
||||
|
||||
localStorage.setItem('message', 'Successfully signed up!');
|
||||
window.location.href = URLs.baseUrl;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="LoginList">
|
||||
<LoginTitle/>
|
||||
<InputField title="Name" setValue={setName} value={name}/>
|
||||
<InputField title="Nickname (for others)" setValue={setNickname} value={nickname}/>
|
||||
<InputField title="Password" setValue={setPassword} value={password}/>
|
||||
<InputField title="Repeat Password" setValue={setRepeatPassword} value={repeatPassword}/>
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
body {
|
||||
background: linear-gradient(to right, #e0f7fa, #fffde7);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -11,10 +15,13 @@
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 2px solid #0056b3;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
@@ -24,6 +31,7 @@
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
@@ -33,6 +41,12 @@
|
||||
max-width: 70%;
|
||||
word-wrap: break-word;
|
||||
display: inline-block;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.message-bubble:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.sent {
|
||||
@@ -58,8 +72,9 @@
|
||||
|
||||
.message-timestamp {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
color: black;
|
||||
text-align: right;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
73
stubs/api/auth/index.js
Normal file
73
stubs/api/auth/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const authRouter = require('express').Router();
|
||||
|
||||
// For creating tokens
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const { TOKEN_KEY } = require('../key')
|
||||
|
||||
|
||||
module.exports = authRouter;
|
||||
|
||||
const { addUserToDB, getUserFromDB } = require('../db');
|
||||
|
||||
|
||||
// Get a user by its id
|
||||
authRouter.get('/:id', (req, res) => {
|
||||
const user = getUserFromDB(req.params.id);
|
||||
|
||||
if (user) {
|
||||
res.status(200).send({user});
|
||||
} else {
|
||||
res.status(404).send({message: 'User was not found'});
|
||||
}
|
||||
})
|
||||
|
||||
// For login (authorization)
|
||||
authRouter.post('/login', (req, res) => {
|
||||
const { name, password } = req.body;
|
||||
|
||||
const user = getUserFromDB(name);
|
||||
|
||||
// Invalid identification
|
||||
if (!user) {
|
||||
res.status(401).send({message: 'Invalid credentials (id)'});
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalid authentication
|
||||
if (!password || password !== user.password) {
|
||||
res.status(401).send({message: 'Invalid credentials (password)'});
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, authorization
|
||||
const token = jwt.sign({id: name}, TOKEN_KEY, {
|
||||
expiresIn: '1h'
|
||||
})
|
||||
|
||||
res.status(200).send({token});
|
||||
})
|
||||
|
||||
|
||||
authRouter.post('/reg', (req, res) => {
|
||||
const { name, password, nickname } = req.body;
|
||||
|
||||
const user = getUserFromDB(name);
|
||||
|
||||
// Invalid identification
|
||||
if (user) {
|
||||
res.status(409).send({message: 'Such id already exists'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name || !password || !nickname) {
|
||||
res.status(401).send({message: 'Empty or invalid fields'});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to 'DB'
|
||||
const newUser = {id: name, password: password, nickname: nickname};
|
||||
addUserToDB(newUser)
|
||||
|
||||
res.status(200).send({user: newUser});
|
||||
})
|
||||
52
stubs/api/auth/users.json
Normal file
52
stubs/api/auth/users.json
Normal file
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"nickname": "Alice Johnson",
|
||||
"password": "1234",
|
||||
"id": "alice"
|
||||
},
|
||||
{
|
||||
"nickname": "Bob Smith",
|
||||
"password": "1234",
|
||||
"id": "bobsm"
|
||||
},
|
||||
{
|
||||
"nickname": "Charlie Brown",
|
||||
"password": "1234",
|
||||
"id": "charl"
|
||||
},
|
||||
{
|
||||
"nickname": "David Clark",
|
||||
"password": "1234",
|
||||
"id": "david"
|
||||
},
|
||||
{
|
||||
"nickname": "Eve Adams",
|
||||
"password": "1234",
|
||||
"id": "evead"
|
||||
},
|
||||
{
|
||||
"nickname": "Frank Wright",
|
||||
"password": "1234",
|
||||
"id": "frank"
|
||||
},
|
||||
{
|
||||
"nickname": "Grace Lee",
|
||||
"password": "1234",
|
||||
"id": "grace"
|
||||
},
|
||||
{
|
||||
"nickname": "Hannah Scott",
|
||||
"password": "1234",
|
||||
"id": "hanna"
|
||||
},
|
||||
{
|
||||
"nickname": "Ian Davis",
|
||||
"password": "1234",
|
||||
"id": "ianda"
|
||||
},
|
||||
{
|
||||
"nickname": "Jill Thompson",
|
||||
"password": "1234",
|
||||
"id": "jillt"
|
||||
}
|
||||
]
|
||||
64
stubs/api/change/index.js
Normal file
64
stubs/api/change/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const changeRouter = require('express').Router();
|
||||
|
||||
module.exports = changeRouter;
|
||||
|
||||
const { getUserFromDB, deleteUserFromDB, addUserToDB } = require('../db');
|
||||
|
||||
|
||||
changeRouter.post('/nickname', (req, res) => {
|
||||
const { id, newNickname } = req.body;
|
||||
|
||||
const user = getUserFromDB(id);
|
||||
|
||||
// Invalid identification
|
||||
if (!user) {
|
||||
res.status(401).send({message: 'Invalid credentials (id)'});
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedUser = {
|
||||
"nickname": newNickname,
|
||||
"password": user.password,
|
||||
"id": user.id
|
||||
};
|
||||
|
||||
// Delete the old one
|
||||
deleteUserFromDB(id)
|
||||
|
||||
// Insert updated
|
||||
addUserToDB(updatedUser);
|
||||
|
||||
res.status(200).send({});
|
||||
});
|
||||
|
||||
changeRouter.post('/password', (req, res) => {
|
||||
const { id, newPassword } = req.body;
|
||||
|
||||
const user = getUserFromDB(id);
|
||||
|
||||
// Invalid identification
|
||||
if (!user) {
|
||||
res.status(401).send({message: 'Invalid credentials (id)'});
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the old one
|
||||
deleteUserFromDB(id)
|
||||
|
||||
// Insert updated
|
||||
const updatedUser = {
|
||||
"nickname": user.nickname,
|
||||
"password": newPassword,
|
||||
"id": user.id
|
||||
};
|
||||
addUserToDB(updatedUser);
|
||||
|
||||
res.status(200).send({});
|
||||
});
|
||||
|
||||
changeRouter.delete('/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
deleteUserFromDB(id);
|
||||
});
|
||||
|
||||
662
stubs/api/chat/chats.json
Normal file
662
stubs/api/chat/chats.json
Normal file
@@ -0,0 +1,662 @@
|
||||
[
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "bobsm",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Hello Bob!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 07:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Hey Alice, how are you?",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:05:00"
|
||||
},
|
||||
{
|
||||
"data": "I'm good, thanks for asking.",
|
||||
"senderId": "alice",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 07:10:00"
|
||||
},
|
||||
{
|
||||
"data": "Glad to hear!",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "charl",
|
||||
"messages": [
|
||||
{
|
||||
"data": "How's the project going?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 07:20:00"
|
||||
},
|
||||
{
|
||||
"data": "It's coming along, almost done!",
|
||||
"senderId": "charl",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:25:00"
|
||||
},
|
||||
{
|
||||
"data": "That's great to hear!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 07:30:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks for checking in.",
|
||||
"senderId": "charl",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "david",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Did you get the files?",
|
||||
"senderId": "david",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, I did. Thank you!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 07:45:00"
|
||||
},
|
||||
{
|
||||
"data": "You're welcome.",
|
||||
"senderId": "david",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 07:50:00"
|
||||
},
|
||||
{
|
||||
"data": "Let me know if you need anything else.",
|
||||
"senderId": "alice",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 07:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "frank",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Can you review this document for me?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 08:20:00"
|
||||
},
|
||||
{
|
||||
"data": "Sure, I'll take a look.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 08:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks, much appreciated!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 08:30:00"
|
||||
},
|
||||
{
|
||||
"data": "No problem.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 08:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "grace",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Hey Grace, let's meet up for coffee!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 08:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Sounds good, when are you free?",
|
||||
"senderId": "grace",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 08:45:00"
|
||||
},
|
||||
{
|
||||
"data": "How about tomorrow afternoon?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 08:50:00"
|
||||
},
|
||||
{
|
||||
"data": "Works for me!",
|
||||
"senderId": "grace",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 08:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "hanna",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Hannah, do you have a moment?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 09:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Sure, what's up?",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:05:00"
|
||||
},
|
||||
{
|
||||
"data": "Just wanted to check on the report.",
|
||||
"senderId": "alice",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 09:10:00"
|
||||
},
|
||||
{
|
||||
"data": "I'll send it soon.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "ianda",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Ian, have you completed the review?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 09:20:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, I sent my feedback.",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks for that.",
|
||||
"senderId": "alice",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 09:30:00"
|
||||
},
|
||||
{
|
||||
"data": "Anytime!",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "jillt",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Jill, let's schedule a catch-up meeting.",
|
||||
"senderId": "alice",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 09:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Sounds good, when works for you?",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:45:00"
|
||||
},
|
||||
{
|
||||
"data": "Tomorrow afternoon?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 09:50:00"
|
||||
},
|
||||
{
|
||||
"data": "That works for me!",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 09:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "alice",
|
||||
"id2": "evead",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Eve, did you send the schedule?",
|
||||
"senderId": "alice",
|
||||
"recipientId": "evead",
|
||||
"timestamp": "09.10.2024 10:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, just sent it.",
|
||||
"senderId": "evead",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 10:05:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks, much appreciated!",
|
||||
"senderId": "alice",
|
||||
"recipientId": "evead",
|
||||
"timestamp": "09.10.2024 10:10:00"
|
||||
},
|
||||
{
|
||||
"data": "No problem!",
|
||||
"senderId": "evead",
|
||||
"recipientId": "alice",
|
||||
"timestamp": "09.10.2024 10:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "bobsm",
|
||||
"id2": "charl",
|
||||
"messages": [
|
||||
{
|
||||
"data": "How's everything going?",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 10:20:00"
|
||||
},
|
||||
{
|
||||
"data": "Pretty good, how about you?",
|
||||
"senderId": "charl",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 10:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Can't complain!",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 10:30:00"
|
||||
},
|
||||
{
|
||||
"data": "Glad to hear that.",
|
||||
"senderId": "charl",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 10:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "bobsm",
|
||||
"id2": "david",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Can you send the report?",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 10:40:00"
|
||||
},
|
||||
{
|
||||
"data": "I'll send it in an hour.",
|
||||
"senderId": "david",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 10:45:00"
|
||||
},
|
||||
{
|
||||
"data": "Perfect, thanks.",
|
||||
"senderId": "bobsm",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 10:50:00"
|
||||
},
|
||||
{
|
||||
"data": "No problem.",
|
||||
"senderId": "david",
|
||||
"recipientId": "bobsm",
|
||||
"timestamp": "09.10.2024 10:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "charl",
|
||||
"id2": "evead",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Hey Eve, how's it going?",
|
||||
"senderId": "charl",
|
||||
"recipientId": "evead",
|
||||
"timestamp": "09.10.2024 11:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Good, how about you?",
|
||||
"senderId": "evead",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 11:05:00"
|
||||
},
|
||||
{
|
||||
"data": "Can't complain!",
|
||||
"senderId": "charl",
|
||||
"recipientId": "evead",
|
||||
"timestamp": "09.10.2024 11:10:00"
|
||||
},
|
||||
{
|
||||
"data": "Glad to hear.",
|
||||
"senderId": "evead",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 11:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "charl",
|
||||
"id2": "frank",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Do you have time to talk today?",
|
||||
"senderId": "charl",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 11:20:00"
|
||||
},
|
||||
{
|
||||
"data": "I have a meeting, but I can chat afterward.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 11:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Sounds good.",
|
||||
"senderId": "charl",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 11:30:00"
|
||||
},
|
||||
{
|
||||
"data": "I'll message you after.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "charl",
|
||||
"timestamp": "09.10.2024 11:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "david",
|
||||
"id2": "frank",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Did you review the document?",
|
||||
"senderId": "david",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 11:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, it's all good.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 11:45:00"
|
||||
},
|
||||
{
|
||||
"data": "Great, thanks for the quick turnaround!",
|
||||
"senderId": "david",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 11:50:00"
|
||||
},
|
||||
{
|
||||
"data": "No worries!",
|
||||
"senderId": "frank",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 11:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "david",
|
||||
"id2": "grace",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Grace, can you send the updated schedule?",
|
||||
"senderId": "david",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 12:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, I'll send it in a few minutes.",
|
||||
"senderId": "grace",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 12:05:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks, much appreciated!",
|
||||
"senderId": "david",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 12:10:00"
|
||||
},
|
||||
{
|
||||
"data": "You're welcome!",
|
||||
"senderId": "grace",
|
||||
"recipientId": "david",
|
||||
"timestamp": "09.10.2024 12:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "frank",
|
||||
"id2": "grace",
|
||||
"messages": [
|
||||
{
|
||||
"data": "How are you today?",
|
||||
"senderId": "frank",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 12:20:00"
|
||||
},
|
||||
{
|
||||
"data": "I'm doing well, thanks for asking.",
|
||||
"senderId": "grace",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 12:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Glad to hear that.",
|
||||
"senderId": "frank",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 12:30:00"
|
||||
},
|
||||
{
|
||||
"data": "How about you?",
|
||||
"senderId": "grace",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 12:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "frank",
|
||||
"id2": "hanna",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Did you attend the meeting?",
|
||||
"senderId": "frank",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 12:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, it was productive.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 12:45:00"
|
||||
},
|
||||
{
|
||||
"data": "Good to hear!",
|
||||
"senderId": "frank",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 12:50:00"
|
||||
},
|
||||
{
|
||||
"data": "Indeed, lots to follow up on.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "frank",
|
||||
"timestamp": "09.10.2024 12:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "grace",
|
||||
"id2": "hanna",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Can we meet later today?",
|
||||
"senderId": "grace",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 01:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Sure, what's a good time?",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 01:05:00"
|
||||
},
|
||||
{
|
||||
"data": "How about 3?",
|
||||
"senderId": "grace",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 01:10:00"
|
||||
},
|
||||
{
|
||||
"data": "Works for me.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 01:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "grace",
|
||||
"id2": "ianda",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Ian, did you get the message I sent?",
|
||||
"senderId": "grace",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 01:20:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, I'll respond soon.",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 01:25:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks, appreciate it!",
|
||||
"senderId": "grace",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 01:30:00"
|
||||
},
|
||||
{
|
||||
"data": "You're welcome!",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "grace",
|
||||
"timestamp": "09.10.2024 01:35:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "hanna",
|
||||
"id2": "ianda",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Ian, do you have a minute?",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 01:40:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, what do you need?",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 01:45:00"
|
||||
},
|
||||
{
|
||||
"data": "Just a quick update on the project.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 01:50:00"
|
||||
},
|
||||
{
|
||||
"data": "I'll email you the details.",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 01:55:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "hanna",
|
||||
"id2": "jillt",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Jill, can we talk tomorrow?",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 02:00:00"
|
||||
},
|
||||
{
|
||||
"data": "Yes, I'm free after 2.",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 02:05:00"
|
||||
},
|
||||
{
|
||||
"data": "Perfect, see you then.",
|
||||
"senderId": "hanna",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 02:10:00"
|
||||
},
|
||||
{
|
||||
"data": "Looking forward to it.",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "hanna",
|
||||
"timestamp": "09.10.2024 02:15:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id1": "ianda",
|
||||
"id2": "jillt",
|
||||
"messages": [
|
||||
{
|
||||
"data": "Jill, I have the files you requested.",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 02:20:00"
|
||||
},
|
||||
{
|
||||
"data": "Thanks, please send them over.",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 02:25:00"
|
||||
},
|
||||
{
|
||||
"data": "I'll send them right now.",
|
||||
"senderId": "ianda",
|
||||
"recipientId": "jillt",
|
||||
"timestamp": "09.10.2024 02:30:00"
|
||||
},
|
||||
{
|
||||
"data": "Great, thanks again!",
|
||||
"senderId": "jillt",
|
||||
"recipientId": "ianda",
|
||||
"timestamp": "09.10.2024 02:35:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
86
stubs/api/chat/index.js
Normal file
86
stubs/api/chat/index.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const chatRouter = require('express').Router();
|
||||
|
||||
module.exports = chatRouter;
|
||||
|
||||
const { getChatFromDB, getUsersChats, addChatToDB, getUserFromDB,
|
||||
addMessageToChat} = require('../db');
|
||||
|
||||
chatRouter.get('/item/:id1/:id2', (req, res) => {
|
||||
const { id1, id2 } = req.params;
|
||||
|
||||
if (id1 === id2) {
|
||||
res.status(400).send({message: 'Ids should be different'});
|
||||
return;
|
||||
}
|
||||
|
||||
const chat = getChatFromDB(id1, id2);
|
||||
|
||||
if (chat) {
|
||||
res.status(200).send({chat});
|
||||
} else {
|
||||
res.status(404).send({message: 'Chat was not found'});
|
||||
}
|
||||
})
|
||||
|
||||
chatRouter.post('/item/:id1/:id2', (req, res) => {
|
||||
const { id1, id2 } = req.params;
|
||||
|
||||
if (id1 === id2) {
|
||||
res.status(400).send({message: 'Ids should be different'});
|
||||
return;
|
||||
}
|
||||
|
||||
const chat = getChatFromDB(id1, id2);
|
||||
|
||||
if (chat) {
|
||||
// Chat already exists
|
||||
res.status(200).send({chat});
|
||||
} else {
|
||||
if (!getUserFromDB(id1) || !getUserFromDB(id2)) {
|
||||
res.status(404).send({message: 'Such interlocutor does not exist'});
|
||||
} else {
|
||||
// Creating new chat
|
||||
const newChat = {
|
||||
id1: id1,
|
||||
id2: id2,
|
||||
messages: []
|
||||
}
|
||||
|
||||
addChatToDB(newChat);
|
||||
|
||||
res.status(200).send({newChat});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
chatRouter.get('/list/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const userChats = getUsersChats(id);
|
||||
|
||||
if (!userChats) {
|
||||
res.status(404).send({message: 'Error with retrieving chats'});
|
||||
} else {
|
||||
res.status(200).send({chats: userChats});
|
||||
}
|
||||
})
|
||||
|
||||
chatRouter.post('/message/:sender/:receiver', (req, res) => {
|
||||
const { sender, receiver } = req.params;
|
||||
const { message } = req.body;
|
||||
|
||||
const chat = getChatFromDB(sender, receiver);
|
||||
|
||||
if (!chat) {
|
||||
// Chat already exists
|
||||
res.status(400).send({message: "Such chat does not exist"});
|
||||
} else {
|
||||
if (!getUserFromDB(sender) || !getUserFromDB(receiver)) {
|
||||
res.status(404).send({message: 'Such people do not exist'});
|
||||
} else {
|
||||
// Add new message
|
||||
addMessageToChat(chat, message);
|
||||
res.status(200).send({});
|
||||
}
|
||||
}
|
||||
})
|
||||
74
stubs/api/db.js
Normal file
74
stubs/api/db.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// Read already defined users (pseudo-DB)
|
||||
const users = require('./auth/users.json');
|
||||
const chats = require('./chat/chats.json');
|
||||
|
||||
const getUserFromDB = (userID) => {
|
||||
if (!userID) {return false;}
|
||||
|
||||
// Accessing 'DB'
|
||||
const user = users.find((user) => user.id === userID);
|
||||
|
||||
if (user) {
|
||||
return user;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const deleteUserFromDB = (userID) => {
|
||||
const index = users.findIndex(item => item.id === userID);
|
||||
if (index !== -1) {
|
||||
users.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const addUserToDB = (user) => {
|
||||
users.push(user);
|
||||
}
|
||||
|
||||
const getChatFromDB = (firstID, secondID) => {
|
||||
if (!firstID || !secondID) {return false;}
|
||||
|
||||
// Accessing 'DB'
|
||||
const chat = chats.find((item) =>
|
||||
(item.id1 === firstID && item.id2 === secondID) || (item.id1 === secondID && item.id2 === firstID));
|
||||
|
||||
if (chat) {
|
||||
return chat;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const getUsersChats = (userID) => {
|
||||
if (!userID) {return false;}
|
||||
|
||||
const userChats = chats.filter((chat) => (chat.id1 === userID || chat.id2 === userID));
|
||||
|
||||
if (userChats) {
|
||||
return userChats;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const addMessageToChat = (chat, msg) => {
|
||||
chat.messages.push(msg);
|
||||
}
|
||||
|
||||
const deleteChatFromDB = (firstID, secondID) => {
|
||||
const index = chats.findIndex(item =>
|
||||
(item.id1 === firstID && item.id2 === secondID) || (item.id1 === secondID && item.id2 === firstID));
|
||||
|
||||
if (index !== -1) {
|
||||
chats.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const addChatToDB = (chat) => {
|
||||
chats.push(chat);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {users, chats, getUserFromDB, getChatFromDB, addUserToDB,
|
||||
deleteUserFromDB, addChatToDB, deleteChatFromDB, getUsersChats, addMessageToChat}
|
||||
@@ -1,3 +1,17 @@
|
||||
const changeRouter = require("./change");
|
||||
const authRouter = require("./auth");
|
||||
const chatRouter = require("./chat");
|
||||
|
||||
const router = require('express').Router();
|
||||
|
||||
const delay = require('./middlewares/delay');
|
||||
const verify = require('./middlewares/verify');
|
||||
|
||||
module.exports = router;
|
||||
|
||||
// router.use(delay(300));
|
||||
// router.use('/books', delay, booksRouter);
|
||||
|
||||
router.use('/auth', authRouter);
|
||||
router.use('/change', verify, changeRouter);
|
||||
router.use('/chat', verify, chatRouter)
|
||||
|
||||
3
stubs/api/key.js
Normal file
3
stubs/api/key.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const TOKEN_KEY = '5frv12e4few3r';
|
||||
|
||||
module.exports = { TOKEN_KEY }
|
||||
5
stubs/api/middlewares/delay.js
Normal file
5
stubs/api/middlewares/delay.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const delay = (ms = 1000) => (req, res, next) => {
|
||||
setTimeout(next, ms)
|
||||
}
|
||||
|
||||
module.exports = delay
|
||||
22
stubs/api/middlewares/verify.js
Normal file
22
stubs/api/middlewares/verify.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const { TOKEN_KEY } = require('../key')
|
||||
|
||||
function verifyToken(req, res, next) {
|
||||
const token = req.headers['authorization']?.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).send({ message: 'No token provided' });
|
||||
}
|
||||
|
||||
// Verify token
|
||||
jwt.verify(token, TOKEN_KEY, (err, decoded) => {
|
||||
if (err) {
|
||||
return res.status(401).send({ message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
next(); // Proceed to the next middleware or route
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = verifyToken;
|
||||
@@ -13,7 +13,7 @@
|
||||
"target": "es6",
|
||||
"jsx": "react",
|
||||
"typeRoots": ["node_modules/@types", "src/typings"],
|
||||
"types" : ["webpack-env", "node"],
|
||||
"types" : ["webpack-env", "node", "jest"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user