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({ ...@@ -12,7 +12,6 @@ const UserSchema = new Schema({
username: { username: {
type: String, type: String,
required: true, required: true,
unique: true,
validate: { validate: {
validator: async function (username) { validator: async function (username) {
const user = await User.findOne({username}); const user = await User.findOne({username});
...@@ -24,7 +23,15 @@ const UserSchema = new Schema({ ...@@ -24,7 +23,15 @@ const UserSchema = new Schema({
token: { token: {
type: String, type: String,
required: false, 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 @@ ...@@ -6,7 +6,8 @@
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js", "start": "node index.js",
"dev": "nodemon index.js" "dev": "nodemon index.js",
"fixture": "node fixtures.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
......
...@@ -56,4 +56,20 @@ router.get('/profile', async (req, res) => { ...@@ -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; module.exports = router;
...@@ -8,7 +8,6 @@ import Register from "./containers/Auth/Register/Register"; ...@@ -8,7 +8,6 @@ import Register from "./containers/Auth/Register/Register";
import Login from "./containers/Auth/Login/Login"; import Login from "./containers/Auth/Login/Login";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute"; import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute";
import axiosApi from "./api/axiosApi";
const App = () => { const App = () => {
const user = useSelector(({usersState}) => usersState.user); 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 HomeIcon from '@mui/icons-material/Home';
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import {MAIN, REGISTER, LOGIN} from "../../../constants/routes"; import {MAIN} from "../../../constants/routes";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import {useState} from 'react'; import UserMenu from "./Menus/UserMenu/UserMenu";
import AnonymousMenu from "./Menus/AnonymousMenu/AnonymousMenu";
const AppToolbar = () => { const AppToolbar = () => {
const user = useSelector(({usersState}) => usersState.user); const user = useSelector(({usersState}) => usersState.user);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = e => {
setAnchorEl(e.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return ( return (
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<AppBar position="static"> <AppBar position="static">
...@@ -38,30 +29,8 @@ const AppToolbar = () => { ...@@ -38,30 +29,8 @@ const AppToolbar = () => {
</Typography> </Typography>
{ {
user user
? <> ? <UserMenu user={user} />
<Button : <AnonymousMenu />
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>
</>
} }
</Toolbar> </Toolbar>
</AppBar> </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'; ...@@ -9,11 +9,31 @@ import productsReducer from './store/services/productsSlice';
import usersReducer from './store/services/usersSlice'; import usersReducer from './store/services/usersSlice';
import {BrowserRouter} from "react-router-dom"; 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({ const store = configureStore({
reducer: { reducer: {
usersState: usersReducer, usersState: usersReducer,
productsState: productsReducer productsState: productsReducer
} },
preloadedState: reHydrateStore(),
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(localStorageMiddleware)
}); });
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById('root'));
......
import {createAsyncThunk} from "@reduxjs/toolkit"; import {createAsyncThunk} from "@reduxjs/toolkit";
import axiosApi from "../../api/axiosApi"; import axiosApi from "../../api/axiosApi";
import {setLoginError, setRegisterError, setUser} from "../services/usersSlice"; import {setLoginError, setRegisterError, setUser, setLogoutError} from "../services/usersSlice";
export const registerUser = createAsyncThunk( export const registerUser = createAsyncThunk(
'users/register', 'users/register',
async (payload, {dispatch}) => await axiosApi async ({data, callback}, {dispatch}) => await axiosApi
.post('/users', payload.data) .post('/users', data)
.then(res => payload.callback()) .then(res => callback())
.catch(e => { .catch(e => {
if (e?.response?.data) dispatch(setRegisterError(e.response.data)); if (e?.response?.data) dispatch(setRegisterError(e.response.data));
else dispatch(setRegisterError(e)); else dispatch(setRegisterError(e));
...@@ -16,11 +16,11 @@ export const registerUser = createAsyncThunk( ...@@ -16,11 +16,11 @@ export const registerUser = createAsyncThunk(
export const loginUser = createAsyncThunk( export const loginUser = createAsyncThunk(
'users/login', 'users/login',
async (payload, {dispatch, getState}) => await axiosApi async ({data, callback}, {dispatch, getState}) => await axiosApi
.post('/users/login', payload.data) .post('/users/login', data)
.then(res => { .then(res => {
dispatch(setUser(res.data)); dispatch(setUser(res.data));
payload.callback(); callback();
}) })
.catch(e => { .catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data)); if (e?.response?.data) dispatch(setLoginError(e.response.data));
...@@ -28,3 +28,21 @@ export const loginUser = createAsyncThunk( ...@@ -28,3 +28,21 @@ export const loginUser = createAsyncThunk(
throw e; 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 {createSlice} from "@reduxjs/toolkit";
import {loginUser, registerUser} from "../actions/usersActions"; import {loginUser, logoutUser, registerUser} from "../actions/usersActions";
const initialState = { const initialState = {
loginError: null, loginError: null,
logoutError: null,
registerError: null, registerError: null,
loading: false, loading: false,
user: null user: null
...@@ -18,6 +19,9 @@ const usersSlice = createSlice({ ...@@ -18,6 +19,9 @@ const usersSlice = createSlice({
setRegisterError: (state, action) => { setRegisterError: (state, action) => {
state.registerError = action.payload; state.registerError = action.payload;
}, },
setLogoutError: (state, action) => {
state.logoutError = action.payload;
},
setUser: (state, action) => { setUser: (state, action) => {
state.user = action.payload; state.user = action.payload;
} }
...@@ -66,8 +70,29 @@ const usersSlice = createSlice({ ...@@ -66,8 +70,29 @@ const usersSlice = createSlice({
state.loading = false; 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; 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