Commit 4b9e320f authored by Egor Kremnev's avatar Egor Kremnev

add auth user

parent 6025b9c0
......@@ -14,9 +14,9 @@ const UserSchema = new Schema({
required: true,
unique: true,
validate: {
validator: async (username) => {
validator: async function (username) {
const user = await User.findOne({username});
return !user;
return !user || user._id !== this._id;
},
message: "This user is already exists"
}
......
......@@ -5,6 +5,7 @@ const multer = require('multer');
const path = require('path');
const {uploadPath} = require('./../config');
const Product = require('../models/Product');
const User = require("../models/User");
const storage = multer.diskStorage({
destination: (req, file, cb) => {
......@@ -19,6 +20,18 @@ const upload = multer({storage});
const createRoutes = () => {
router.post('/', upload.single('image'), async (req, res) => {
const token = req.get('Authorization');
if (!token) return res
.status(401)
.send({error: 'No token present'});
const user = await User.findOne({token});
if (!user) return res
.status(401)
.send('Token is wrong');
const productData = {...req.body};
if (req.file) productData.image = req.file.filename;
......
......@@ -25,10 +25,16 @@ router.post('/login', async (req, res) => {
.status(400)
.send({error: 'Username or password incorrect'});
user.generateToken();
await user.save();
try {
user.generateToken();
await user.save();
res.send({message: "User success authenticated", user});
res.send(user);
} catch (e) {
res
.status(400)
.send({error: e.message});
}
});
router.get('/profile', async (req, res) => {
......
import {Route, Routes} from "react-router-dom";
import {PRODUCT_ADD, PRODUCT_LIST, PRODUCT_VIEW, REGISTER} from "./constants/routes";
import {LOGIN, PRODUCT_ADD, PRODUCT_LIST, PRODUCT_VIEW, REGISTER} from "./constants/routes";
import Layout from "./components/Layout/Layout";
import Products from "./containers/Products/Products";
import AddProduct from "./containers/Addproduct/AddProduct";
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 = () => (
<>
<Routes>
<Route element={<Layout />}>
<Route index element={<Products />}/>
<Route path={REGISTER} element={<Register />}/>
<Route path={PRODUCT_LIST} element={<Products />}/>
<Route path={PRODUCT_ADD} element={<AddProduct />}/>
<Route path={PRODUCT_VIEW} element={<h1>Show product</h1>}/>
</Route>
</Routes>
</>
);
const App = () => {
const user = useSelector(({usersState}) => usersState.user);
const productAdd = <ProtectedRoute
isAllowed={!!user}
redirectPath={LOGIN}
>
<AddProduct />
</ProtectedRoute>;
return <Routes>
<Route element={<Layout />}>
<Route index element={<Products />}/>
<Route path={REGISTER} element={<Register />}/>
<Route path={LOGIN} element={<Login />}/>
<Route path={PRODUCT_LIST} element={<Products />}/>
<Route path={PRODUCT_ADD} element={productAdd}/>
<Route path={PRODUCT_VIEW} element={<h1>Show product</h1>}/>
</Route>
</Routes>;
};
export default App;
import {useState} from "react";
import {Button, Grid, TextField} from "@mui/material";
import FileInput from "../../UI/FileInput/FileInput";
import FileInput from "../../UI/Form/FormElement/FileInput/FileInput";
const ProductForm = ({createProductHandler}) => {
const [state, setState] = useState({
......
import {Navigate, Outlet} from "react-router-dom";
const ProtectedRoute = ({isAllowed, redirectPath, children}) => {
if (!isAllowed) {
return <Navigate to={redirectPath} replace />;
}
return children || <Outlet/>;
};
export default ProtectedRoute;
import {AppBar, Box, Toolbar, Typography, Button, IconButton} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import {AppBar, Box, Toolbar, Typography, Button, IconButton, Menu, MenuItem} from '@mui/material';
import HomeIcon from '@mui/icons-material/Home';
import {NavLink} from "react-router-dom";
import {MAIN, REGISTER} from "../../../constants/routes";
import {MAIN, REGISTER, LOGIN} from "../../../constants/routes";
import {useSelector} from "react-redux";
import {useState} from 'react';
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">
......@@ -14,15 +28,42 @@ const AppToolbar = () => {
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
component={NavLink}
to={MAIN}
>
<MenuIcon />
<HomeIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Shop
</Typography>
<Button color="inherit" component={NavLink} to={MAIN}>Home</Button>
<Button color="inherit" component={NavLink} to={REGISTER}>Sign up</Button>
</Toolbar>
{
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>
</>
}
</Toolbar>
</AppBar>
</Box>
);
......
import {Grid, TextField} from "@mui/material";
import PropTypes from "prop-types";
const TextInput = ({name, label, value, onChange, required, error, type}) => {
return <Grid item xs={12}>
<TextField
fullWidth
required={required}
id={name}
name={name}
label={label}
error={!!error}
helperText={error}
value={value}
onChange={onChange}
autoComplete={name}
type={type}
/>
</Grid>;
};
TextInput.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
type: PropTypes.string,
error: PropTypes.string,
required: PropTypes.bool,
onChange: PropTypes.func.isRequired
};
export default TextInput;
import {useState} from "react";
import {Avatar, Button, Container, Grid, Typography, Link, Box, CssBaseline, Alert} from "@mui/material";
import { createTheme, ThemeProvider } from '@mui/material/styles';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {Link as RouterLink, useNavigate} from 'react-router-dom';
import {REGISTER} from "../../../constants/routes";
import {useDispatch, useSelector} from "react-redux";
import {loginUser} from "../../../store/actions/usersActions";
import TextInput from "../../../components/UI/Form/FormElement/TextInput/TextInput";
const theme = createTheme();
const Login = () => {
const error = useSelector(({usersState}) => usersState.loginError);
const dispatch = useDispatch();
const navigate = useNavigate();
const [state, setState] = useState({
username: "",
password: ""
});
const inputChangeHandler = (e) => {
const {name, value} = e.currentTarget;
setState(prevState => {
return {...prevState, [name]: value};
});
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginUser({
data: {...state},
callback: () => navigate('/')
}));
};
return (
<ThemeProvider theme={theme}>
<Container component="main" maxWidth="xs">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign In
</Typography>
{error && <Alert severity="error">{error.error}</Alert>}
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<TextInput
required={true}
label="Username"
name="username"
onChange={inputChangeHandler}
value={state.username}
/>
<TextInput
required={true}
name="password"
label="Password"
type="password"
onChange={inputChangeHandler}
value={state.password}
/>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
<Grid container justifyContent="flex-end">
<Grid item>
<Link href="#" variant="body2" component={RouterLink} to={REGISTER}>
Sign Up
</Link>
</Grid>
</Grid>
</Box>
</Box>
</Container>
</ThemeProvider>
);
};
export default Login;
import {useState} from "react";
import {Avatar, Button, Container, Grid, TextField, Typography, Link, Box, CssBaseline} from "@mui/material";
import {Avatar, Button, Container, Grid, Typography, Link, Box, CssBaseline} from "@mui/material";
import { createTheme, ThemeProvider } from '@mui/material/styles';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {Link as RouterLink, useNavigate} from 'react-router-dom';
import {LOGIN} from "../../../constants/routes";
import {useDispatch, useSelector} from "react-redux";
import {registerUser} from "../../../store/actions/usersActions";
import TextInput from "../../../components/UI/Form/FormElement/TextInput/TextInput";
const theme = createTheme();
const Register = () => {
const error = useSelector(({usersState}) => usersState.error);
const error = useSelector(({usersState}) => usersState.registerError);
const dispatch = useDispatch();
const navigate = useNavigate();
const [state, setState] = useState({
......@@ -58,35 +59,23 @@ const Register = () => {
</Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
required
fullWidth
id="username"
label="Username"
name="username"
autoComplete="family-name"
onChange={inputChangeHandler}
value={state.username}
error={!!getFieldError('username')}
helperText={getFieldError('username')}
/>
</Grid>
<Grid item xs={12}>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="new-password"
onChange={inputChangeHandler}
value={state.password}
error={!!getFieldError('password')}
helperText={getFieldError('password')}
/>
</Grid>
<TextInput
required={true}
label="Username"
name="username"
onChange={inputChangeHandler}
value={state.username}
error={getFieldError('username')}
/>
<TextInput
required={true}
name="password"
label="Password"
type="password"
onChange={inputChangeHandler}
value={state.password}
error={getFieldError('password')}
/>
</Grid>
<Button
type="submit"
......
......@@ -8,5 +8,7 @@ export const fetchProducts = createAsyncThunk(
export const createProduct = createAsyncThunk(
'products/create',
async (data) => await axios.post('/products', data).then(res => res.data)
async (data) => await axios
.post('/products', data)
.then(res => res.data)
);
import {createAsyncThunk} from "@reduxjs/toolkit";
import axiosApi from "../../api/axiosApi";
import {setError} from "../services/usersSlice";
import {setLoginError, setRegisterError, setUser} from "../services/usersSlice";
export const registerUser = createAsyncThunk(
'users/register',
......@@ -8,7 +8,23 @@ export const registerUser = createAsyncThunk(
.post('/users', payload.data)
.then(res => payload.callback())
.catch(e => {
if (e?.response?.data) dispatch(setError(e.response.data));
if (e?.response?.data) dispatch(setRegisterError(e.response.data));
else dispatch(setRegisterError(e));
throw e;
})
);
export const loginUser = createAsyncThunk(
'users/login',
async (payload, {dispatch, getState}) => await axiosApi
.post('/users/login', payload.data)
.then(res => {
dispatch(setUser(res.data));
payload.callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data));
else dispatch(setLoginError(e));
throw e;
})
);
import {createSlice} from "@reduxjs/toolkit";
import {registerUser} from "../actions/usersActions";
import {loginUser, registerUser} from "../actions/usersActions";
const initialState = {
error: null,
loading: false
loginError: null,
registerError: null,
loading: false,
user: null
};
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
setError: (state, action) => {
state.error = action.payload;
setLoginError: (state, action) => {
state.loginError = action.payload;
},
setRegisterError: (state, action) => {
state.registerError = action.payload;
},
setUser: (state, action) => {
state.user = action.payload;
}
},
extraReducers: builder => {
......@@ -19,8 +27,9 @@ const usersSlice = createSlice({
.addCase(
registerUser.pending,
state => {
state.error = null;
state.registerError = null;
state.loading = true;
state.user = null;
}
)
.addCase(
......@@ -35,8 +44,30 @@ const usersSlice = createSlice({
state.loading = false;
}
);
builder
.addCase(
loginUser.pending,
state => {
state.loginError = null;
state.loading = true;
state.user = null;
}
)
.addCase(
loginUser.rejected,
state => {
state.loading = false;
}
)
.addCase(
loginUser.fulfilled,
state => {
state.loading = false;
}
);
}
});
export const {setError} = usersSlice.actions;
export const {setLoginError, setRegisterError, setUser} = 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