add

parent ddeeae31
...@@ -22,11 +22,13 @@ ...@@ -22,11 +22,13 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^9.1.0", "react-redux": "^9.1.0",
"react-router-dom": "^6.22.3" "react-router-dom": "^6.22.3",
"redux-persist": "^6.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.43", "@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@types/redux-persist": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.14.0", "@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
......
...@@ -47,6 +47,9 @@ importers: ...@@ -47,6 +47,9 @@ importers:
react-router-dom: react-router-dom:
specifier: ^6.22.3 specifier: ^6.22.3
version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
redux-persist:
specifier: ^6.0.0
version: 6.0.0(react@18.3.1)(redux@5.0.1)
devDependencies: devDependencies:
'@types/react': '@types/react':
specifier: ^18.2.43 specifier: ^18.2.43
...@@ -54,6 +57,9 @@ importers: ...@@ -54,6 +57,9 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: ^18.2.17 specifier: ^18.2.17
version: 18.3.6(@types/react@18.3.20) version: 18.3.6(@types/react@18.3.20)
'@types/redux-persist':
specifier: ^4.3.1
version: 4.3.1(react@18.3.1)(redux@5.0.1)
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^6.20.0 specifier: ^6.20.0
version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2) version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2)
...@@ -778,6 +784,10 @@ packages: ...@@ -778,6 +784,10 @@ packages:
'@types/react@18.3.20': '@types/react@18.3.20':
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
'@types/redux-persist@4.3.1':
resolution: {integrity: sha512-YkMnMUk+4//wPtiSTMfsxST/F9Gh9sPWX0LVxHuOidGjojHtMdpep2cYvQgfiDMnj34orXyZI+QJCQMZDlafKA==}
deprecated: This is a stub types definition for redux-persist (https://github.com/rt2zz/redux-persist). redux-persist provides its own type definitions, so you don't need @types/redux-persist installed!
'@types/semver@7.7.0': '@types/semver@7.7.0':
resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
...@@ -1989,6 +1999,15 @@ packages: ...@@ -1989,6 +1999,15 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
redux-persist@6.0.0:
resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==}
peerDependencies:
react: '>=16'
redux: '>4.0.0'
peerDependenciesMeta:
react:
optional: true
redux-thunk@3.1.0: redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies: peerDependencies:
...@@ -2969,6 +2988,13 @@ snapshots: ...@@ -2969,6 +2988,13 @@ snapshots:
'@types/prop-types': 15.7.14 '@types/prop-types': 15.7.14
csstype: 3.1.3 csstype: 3.1.3
'@types/redux-persist@4.3.1(react@18.3.1)(redux@5.0.1)':
dependencies:
redux-persist: 6.0.0(react@18.3.1)(redux@5.0.1)
transitivePeerDependencies:
- react
- redux
'@types/semver@7.7.0': {} '@types/semver@7.7.0': {}
'@types/use-sync-external-store@0.0.6': {} '@types/use-sync-external-store@0.0.6': {}
...@@ -4509,6 +4535,12 @@ snapshots: ...@@ -4509,6 +4535,12 @@ snapshots:
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0
redux-persist@6.0.0(react@18.3.1)(redux@5.0.1):
dependencies:
redux: 5.0.1
optionalDependencies:
react: 18.3.1
redux-thunk@3.1.0(redux@5.0.1): redux-thunk@3.1.0(redux@5.0.1):
dependencies: dependencies:
redux: 5.0.1 redux: 5.0.1
......
import { LogoutOutlined } from '@ant-design/icons';
import { AppBar, Toolbar, Typography, styled } from '@mui/material'; import { AppBar, Toolbar, Typography, styled } from '@mui/material';
import { Avatar, Button, Dropdown, MenuProps, Row } from 'antd';
import { shallowEqual } from 'react-redux';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { logout } from '../../features/usersSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
const StyledLink = styled(Link)(() => ({ const StyledLink = styled(Link)(() => ({
color: 'inherit', color: 'inherit',
...@@ -8,13 +13,49 @@ const StyledLink = styled(Link)(() => ({ ...@@ -8,13 +13,49 @@ const StyledLink = styled(Link)(() => ({
})); }));
export const AppToolBar = () => { export const AppToolBar = () => {
const dispatch = useAppDispatch();
const { user } = useAppSelector((state) => state.user, shallowEqual);
const items: MenuProps['items'] = [
{
label: (
<Button danger icon={<LogoutOutlined />} onClick={() => dispatch(logout())}>
Logout
</Button>
),
key: '0',
},
];
return ( return (
<> <>
<AppBar position="fixed"> <AppBar position="fixed">
<Toolbar> <Toolbar style={{ width: '100%' }}>
<Row justify={'space-between'} align={'middle'} style={{ width: '100%' }}>
{user ? (
<Typography variant="h6" component={StyledLink} to={'/'}> <Typography variant="h6" component={StyledLink} to={'/'}>
Computer parts shop Computer parts shop
</Typography> </Typography>
) : (
<Typography variant="h6">Auth</Typography>
)}
{user && (
<Row align={'middle'}>
<Typography variant="h6" style={{ marginRight: '15px' }}>
Welcome {user?.username}
</Typography>
<Dropdown menu={{ items }} trigger={['click']}>
<Avatar size={40}>
<Typography style={{ textTransform: 'capitalize', fontSize: '20px' }}>
{user?.username[0]}
</Typography>
</Avatar>
</Dropdown>
</Row>
)}
</Row>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
</> </>
......
import { IUser } from '@/containers/Auth'; import { IUser } from '@/containers/Auth';
import { axiosApiClient } from '../helpers/axiosApiClient';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { AxiosError, isAxiosError } from 'axios'; import { AxiosError, isAxiosError } from 'axios';
import { axiosApiClient } from '../helpers/axiosApiClient';
interface State { interface State {
user: IUser | null; user: IUser | null;
...@@ -57,6 +57,18 @@ export const login = createAsyncThunk( ...@@ -57,6 +57,18 @@ export const login = createAsyncThunk(
} }
); );
export const logout = createAsyncThunk('auth/logout', async () => {
await axiosApiClient
.get('/auth/logout', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
})
.then(() => {
localStorage.clear();
});
});
const usersSlice = createSlice({ const usersSlice = createSlice({
name: 'users', name: 'users',
initialState, initialState,
...@@ -101,6 +113,18 @@ const usersSlice = createSlice({ ...@@ -101,6 +113,18 @@ const usersSlice = createSlice({
}) })
.addCase(refreshToken.pending, (state) => { .addCase(refreshToken.pending, (state) => {
state.loading = true; state.loading = true;
})
// LOGOUT TOKEN
.addCase(logout.fulfilled, (state) => {
state.user = null;
state.loading = false;
})
.addCase(logout.rejected, (state) => {
state.loading = false;
})
.addCase(logout.pending, (state) => {
state.loading = true;
}); });
}, },
}); });
......
...@@ -2,16 +2,19 @@ import React from 'react'; ...@@ -2,16 +2,19 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';
import App from './App.tsx'; import App from './App.tsx';
import './index.css'; import './index.css';
import store from './store/index.tsx'; import store, { persistor } from './store/index.tsx';
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<Provider store={store}> <Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter> <BrowserRouter>
<App /> <App />
</BrowserRouter> </BrowserRouter>
</PersistGate>
</Provider> </Provider>
</React.StrictMode> </React.StrictMode>
); );
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import {
FLUSH,
PAUSE,
PERSIST,
persistReducer,
persistStore,
PURGE,
REGISTER,
REHYDRATE,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import productsReducer from '../features/productsSlice'; import productsReducer from '../features/productsSlice';
import userReducer from '../features/usersSlice'; import userReducer from '../features/usersSlice';
const persistConfig = {
key: 'root',
version: 1,
storage,
whitelist: ['user'],
};
const persistProducts = persistReducer(persistConfig, productsReducer);
const persistUser = persistReducer(persistConfig, userReducer);
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
products: productsReducer, products: persistProducts,
user: userReducer, user: persistUser,
}, },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
}); });
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch;
export default store; export default store;
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ProductModule } from './product/product.module';
import { CategoryModule } from './category/category.module';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from './product/entities/product.entity'; import { AuthModule } from './auth/auth.module';
import { CategoryModule } from './category/category.module';
import { Category } from './category/entities/category.entity'; import { Category } from './category/entities/category.entity';
import { Product } from './product/entities/product.entity';
import { ProductModule } from './product/product.module';
import { SeedModule } from './seed/seed.module';
import { User } from './user/entities/user.entity'; import { User } from './user/entities/user.entity';
import { UserModule } from './user/user.module'; import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { SeedModule } from './seed/seed.module';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forRoot({ TypeOrmModule.forRoot({
...@@ -16,7 +16,7 @@ import { SeedModule } from './seed/seed.module'; ...@@ -16,7 +16,7 @@ import { SeedModule } from './seed/seed.module';
port: 5432, port: 5432,
username: 'postgres', username: 'postgres',
password: 'root', password: 'root',
database: 'test_server', database: 'postgres',
schema: 'public', schema: 'public',
entities: [Product, Category, User], entities: [Product, Category, User],
synchronize: true, synchronize: true,
......
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { import {
Controller,
Post,
Body, Body,
Controller,
Get, Get,
UseGuards, Post,
Request, Request,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto'; import { RegisterDto } from './dto/register.dto';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
...@@ -26,6 +26,13 @@ export class AuthController { ...@@ -26,6 +26,13 @@ export class AuthController {
return this.authService.login(body.username, body.password); return this.authService.login(body.username, body.password);
} }
@UseGuards(JwtAuthGuard)
@Get('logout')
logout(@Request() req) {
const username = req?.user?.username;
return this.authService.logout(username as string);
}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('refreshToken') @Get('refreshToken')
refreshToken(@Request() req) { refreshToken(@Request() req) {
......
...@@ -3,10 +3,10 @@ import { ...@@ -3,10 +3,10 @@ import {
NotFoundException, NotFoundException,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { RegisterDto } from './dto/register.dto';
import { UserService } from 'src/user/user.service';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { UserService } from 'src/user/user.service';
import { RegisterDto } from './dto/register.dto';
const SALT_WORK_FACTOR = 10; const SALT_WORK_FACTOR = 10;
...@@ -39,6 +39,10 @@ export class AuthService { ...@@ -39,6 +39,10 @@ export class AuthService {
return await this.userService.findOneByUserName(username); return await this.userService.findOneByUserName(username);
} }
async logout(username: string) {
return await this.userService.logout(username);
}
async login(username: string, password: string) { async login(username: string, password: string) {
const user = await this.userService.findOneByUserName(username); const user = await this.userService.findOneByUserName(username);
if (!user) throw new NotFoundException(' Такого юзера нет '); if (!user) throw new NotFoundException(' Такого юзера нет ');
......
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable() @Injectable()
export class UserService { export class UserService {
...@@ -32,6 +32,11 @@ export class UserService { ...@@ -32,6 +32,11 @@ export class UserService {
return await this.userRepo.findOneBy({ username }); return await this.userRepo.findOneBy({ username });
} }
async logout(username: string): Promise<string> {
await this.userRepo.update({ username }, { refreshToken: '' });
return 'logout';
}
async remove(id: number): Promise<number> { async remove(id: number): Promise<number> {
const user = await this.userRepo.findOneBy({ id }); const user = await this.userRepo.findOneBy({ id });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment