Commit 7c08e9a7 authored by Egor Kremnev's avatar Egor Kremnev

add logout user

parent 4b9e320f
const mongoose = require('mongoose');
const config = require('./config').db;
const Category = require('./models/Category');
const Product = require('./models/Product');
const User = require('./models/User');
mongoose.connect(config.host + '/' + config.database);
const db = mongoose.connection;
db.once('open', async () => {
try {
await db.dropCollection('categories');
await db.dropCollection('products');
await db.dropCollection('users');
} catch (e) {
console.log('Collections were not present, skipping drop...');
}
const [cpuCategory, hddCategory, monitorCategory] = await Category.create([
{
title: "CPU's",
description: "Central processor units",
},
{
title: "HDDs",
description: "Hard disk drives",
},
{
title: "Monitors",
description: "Monitors for office and home",
}
]);
await Product.create([
{
title: "Ryzen 7",
category: cpuCategory._id,
price: 300
},
{
title: "HDD seagate 1TB",
category: hddCategory._id,
price: 200
},
{
title: "Lenovo Legion 7",
category: monitorCategory._id,
price: 150
},
]);
await User.create([
{
username: "user",
password: "qwerty",
token: null
},
{
username: "admin",
password: "qwerty",
token: null
}
]);
db.close();
});
......@@ -12,7 +12,6 @@ const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
validate: {
validator: async function (username) {
const user = await User.findOne({username});
......@@ -24,7 +23,15 @@ const UserSchema = new Schema({
token: {
type: String,
required: false,
unique: true
validate: {
validator: async function (token) {
if (!token) return true;
const user = await User.findOne({token});
return !user || user._id !== this._id;
},
message: "Token duplicated"
}
}
});
......
......@@ -6,7 +6,8 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"dev": "nodemon index.js"
"dev": "nodemon index.js",
"fixture": "node fixtures.js"
},
"keywords": [],
"author": "",
......
......@@ -56,4 +56,20 @@ router.get('/profile', async (req, res) => {
});
});
router.delete('/logout', async (req, res) => {
const token = req.get('Authorization');
const success = {message: 'Success'};
if (!token) return res.send(success);
const user = await User.findOne({token});
if (!user) return res.send(success);
user.token = null;
user.save();
res.send(success);
});
module.exports = router;
......@@ -8,7 +8,6 @@ import Register from "./containers/Auth/Register/Register";
import Login from "./containers/Auth/Login/Login";
import {useSelector} from "react-redux";
import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute";
import axiosApi from "./api/axiosApi";
const App = () => {
const user = useSelector(({usersState}) => usersState.user);
......
import {AppBar, Box, Toolbar, Typography, Button, IconButton, Menu, MenuItem} from '@mui/material';
import {AppBar, Box, Toolbar, Typography, IconButton} from '@mui/material';
import HomeIcon from '@mui/icons-material/Home';
import {NavLink} from "react-router-dom";
import {MAIN, REGISTER, LOGIN} from "../../../constants/routes";
import {MAIN} from "../../../constants/routes";
import {useSelector} from "react-redux";
import {useState} from 'react';
import UserMenu from "./Menus/UserMenu/UserMenu";
import AnonymousMenu from "./Menus/AnonymousMenu/AnonymousMenu";
const AppToolbar = () => {
const user = useSelector(({usersState}) => usersState.user);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = e => {
setAnchorEl(e.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
......@@ -38,30 +29,8 @@ const AppToolbar = () => {
</Typography>
{
user
? <>
<Button
aria-controls="simple-menu"
aria-haspopup={true}
onClick={handleClick}
color="inherit"
>
Hello, {user.username}
</Button>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={handleClose}
keepMounted
>
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem>Logout</MenuItem>
</Menu>
</>
: <>
<Button color="inherit" component={NavLink} to={REGISTER}>Sign up</Button>
<Button color="inherit" component={NavLink} to={LOGIN}>Sign In</Button>
</>
? <UserMenu user={user} />
: <AnonymousMenu />
}
</Toolbar>
</AppBar>
......
import {NavLink} from "react-router-dom";
import {LOGIN, REGISTER} from "../../../../../constants/routes";
import {Button} from '@mui/material';
const AnonymousMenu = () => {
return <>
<Button color="inherit" component={NavLink} to={REGISTER}>Sign up</Button>
<Button color="inherit" component={NavLink} to={LOGIN}>Sign In</Button>
</>;
};
export default AnonymousMenu;
import {useState} from "react";
import {Button, Menu, MenuItem} from '@mui/material';
import {useDispatch} from "react-redux";
import {logoutUser} from "../../../../../store/actions/usersActions";
import {useNavigate} from "react-router-dom";
import {MAIN} from "../../../../../constants/routes";
const UserMenu = ({user}) => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = e => {
setAnchorEl(e.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return <>
<Button
aria-controls="simple-menu"
aria-haspopup={true}
onClick={handleClick}
color="inherit"
>
Hello, {user.username}
</Button>
<Menu
open={!!anchorEl}
anchorEl={anchorEl}
onClose={handleClose}
keepMounted
>
<MenuItem>Profile</MenuItem>
<MenuItem>My account</MenuItem>
<MenuItem
onClick={() => dispatch(logoutUser({callback: () => navigate(MAIN)}))}
>
Logout
</MenuItem>
</Menu>
</>;
};
export default UserMenu;
......@@ -9,11 +9,31 @@ import productsReducer from './store/services/productsSlice';
import usersReducer from './store/services/usersSlice';
import {BrowserRouter} from "react-router-dom";
const localStorageMiddleware = ({getState}) => next => action => {
const result = next(action);
localStorage.setItem('user', JSON.stringify(getState().usersState.user));
return result;
};
const reHydrateStore = () => {
if (localStorage.getItem('user') !== null) {
return {
usersState: {
user: JSON.parse(localStorage.getItem('user'))
}
};
}
return undefined;
};
const store = configureStore({
reducer: {
usersState: usersReducer,
productsState: productsReducer
}
},
preloadedState: reHydrateStore(),
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(localStorageMiddleware)
});
const root = ReactDOM.createRoot(document.getElementById('root'));
......
import {createAsyncThunk} from "@reduxjs/toolkit";
import axiosApi from "../../api/axiosApi";
import {setLoginError, setRegisterError, setUser} from "../services/usersSlice";
import {setLoginError, setRegisterError, setUser, setLogoutError} from "../services/usersSlice";
export const registerUser = createAsyncThunk(
'users/register',
async (payload, {dispatch}) => await axiosApi
.post('/users', payload.data)
.then(res => payload.callback())
async ({data, callback}, {dispatch}) => await axiosApi
.post('/users', data)
.then(res => callback())
.catch(e => {
if (e?.response?.data) dispatch(setRegisterError(e.response.data));
else dispatch(setRegisterError(e));
......@@ -16,11 +16,11 @@ export const registerUser = createAsyncThunk(
export const loginUser = createAsyncThunk(
'users/login',
async (payload, {dispatch, getState}) => await axiosApi
.post('/users/login', payload.data)
async ({data, callback}, {dispatch, getState}) => await axiosApi
.post('/users/login', data)
.then(res => {
dispatch(setUser(res.data));
payload.callback();
callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data));
......@@ -28,3 +28,21 @@ export const loginUser = createAsyncThunk(
throw e;
})
);
export const logoutUser = createAsyncThunk(
'users/logout',
async (payload, {dispatch, getState}) => await axiosApi
.delete(
'/users/logout',
{headers: {Authorization: getState().usersState.user.token}}
)
.then(res => {
dispatch(setUser(null));
payload.callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLogoutError(e.response.data));
else dispatch(setLogoutError(e));
throw e;
})
);
import {createSlice} from "@reduxjs/toolkit";
import {loginUser, registerUser} from "../actions/usersActions";
import {loginUser, logoutUser, registerUser} from "../actions/usersActions";
const initialState = {
loginError: null,
logoutError: null,
registerError: null,
loading: false,
user: null
......@@ -18,6 +19,9 @@ const usersSlice = createSlice({
setRegisterError: (state, action) => {
state.registerError = action.payload;
},
setLogoutError: (state, action) => {
state.logoutError = action.payload;
},
setUser: (state, action) => {
state.user = action.payload;
}
......@@ -66,8 +70,29 @@ const usersSlice = createSlice({
state.loading = false;
}
);
builder
.addCase(
logoutUser.pending,
state => {
state.logoutError = null;
state.loading = true;
}
)
.addCase(
logoutUser.rejected,
state => {
state.loading = false;
}
)
.addCase(
logoutUser.fulfilled,
state => {
state.loading = false;
}
);
}
});
export const {setLoginError, setRegisterError, setUser} = usersSlice.actions;
export const {setLoginError, setRegisterError, setUser, setLogoutError} = usersSlice.actions;
export default usersSlice.reducer;
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