diff --git a/client/package.json b/client/package.json index f8568bf..1a91585 100644 --- a/client/package.json +++ b/client/package.json @@ -8,17 +8,15 @@ "@emotion/styled": "^11.3.0", "@mui/icons-material": "^5.0.5", "@mui/material": "^5.0.4", - "@testing-library/jest-dom": "^5.14.1", - "@testing-library/react": "^11.2.7", - "@testing-library/user-event": "^12.8.3", + "@mui/styles": "^5.0.2", "axios": "^0.23.0", "eslint-config-react-app": "^6.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-icons": "^4.3.1", "react-router-dom": "^5.3.0", - "react-scripts": "4.0.3", - "web-vitals": "^1.1.2" + "react-scripts": "4.0.3" }, "scripts": { "start": "react-scripts start", diff --git a/client/src/App.js b/client/src/App.js index 2a4bc88..1ed2d84 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,8 +1,16 @@ +import { BrowserRouter as Router } from 'react-router-dom'; +import NavBar from './components/Navbar'; +import { Footer } from './components'; + function App() { return ( -
+
+ + +
+ +
); } - export default App; diff --git a/client/src/assets/background.png b/client/src/assets/background.png new file mode 100644 index 0000000..83624e3 Binary files /dev/null and b/client/src/assets/background.png differ diff --git a/client/src/assets/bath.png b/client/src/assets/bath.png new file mode 100644 index 0000000..b0fba3c Binary files /dev/null and b/client/src/assets/bath.png differ diff --git a/client/src/assets/beds.png b/client/src/assets/beds.png new file mode 100644 index 0000000..22c6f0f Binary files /dev/null and b/client/src/assets/beds.png differ diff --git a/client/src/assets/facebook.png b/client/src/assets/facebook.png new file mode 100644 index 0000000..2a772da Binary files /dev/null and b/client/src/assets/facebook.png differ diff --git a/client/src/assets/homeIcon.png b/client/src/assets/homeIcon.png new file mode 100644 index 0000000..0934f9b Binary files /dev/null and b/client/src/assets/homeIcon.png differ diff --git a/client/src/assets/profile-picture.jpg b/client/src/assets/profile-picture.jpg new file mode 100644 index 0000000..fdc5ea5 Binary files /dev/null and b/client/src/assets/profile-picture.jpg differ diff --git a/client/src/assets/rooms.png b/client/src/assets/rooms.png new file mode 100644 index 0000000..90e56b2 Binary files /dev/null and b/client/src/assets/rooms.png differ diff --git a/client/src/assets/space.png b/client/src/assets/space.png new file mode 100644 index 0000000..69f59ca Binary files /dev/null and b/client/src/assets/space.png differ diff --git a/client/src/asstes/avatar.png b/client/src/asstes/avatar.png new file mode 100644 index 0000000..57ac67e Binary files /dev/null and b/client/src/asstes/avatar.png differ diff --git a/client/src/asstes/logo.png b/client/src/asstes/logo.png new file mode 100644 index 0000000..b00a296 Binary files /dev/null and b/client/src/asstes/logo.png differ diff --git a/client/src/components/Agent/EstateAgent/index.jsx b/client/src/components/Agent/EstateAgent/index.jsx index a40d9cd..1fa0c93 100644 --- a/client/src/components/Agent/EstateAgent/index.jsx +++ b/client/src/components/Agent/EstateAgent/index.jsx @@ -13,109 +13,106 @@ import Typography from '@mui/material/Typography'; import House from '../../../assetes/house.jpeg'; import './style.css'; -const AgenteEstate = ({ data }) => { - console.log('AgenteEstate', data); - return ( - ( + + + {data.length ? data[0].estateData.map((datas) => ( + + - - {data.length ? data.map((datas) => ( - - + - + - {datas.title} - + + {datas.title} + {' '} - - {datas.title} - {' '} - - - - {datas.description} - - - - {datas.category} - {' '} - - - - - {datas.type} - {' '} - - - - - - - - + {datas.description} + + + + {datas.category} + {' '} + + + + + {datas.type} + {' '} + + + + + + + + - - )) : ' You are still not published any estate' } + + )) : ' You are still not published any estate' } - - ); -}; + +); export default AgenteEstate; diff --git a/client/src/components/Agent/Info/index.jsx b/client/src/components/Agent/Info/index.jsx index 44a630b..b66f8ff 100644 --- a/client/src/components/Agent/Info/index.jsx +++ b/client/src/components/Agent/Info/index.jsx @@ -13,7 +13,7 @@ import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import PropTypes from 'prop-types'; -import PresonImg from '../../../assetes/avatar.png'; +import PresonImg from '../../../asstes/avatar.png'; const AgentInfo = ({ name, email, phone, avater, diff --git a/client/src/components/Footer/data.js b/client/src/components/Footer/data.js new file mode 100644 index 0000000..f9666d3 --- /dev/null +++ b/client/src/components/Footer/data.js @@ -0,0 +1,91 @@ +import { + AiFillFacebook, AiFillTwitterCircle, AiFillInstagram, AiOutlineWhatsApp, +} from 'react-icons/ai'; + +const data = [ + { + title: 'About Us', + links: [ + { + href: '/', + name: 'Story', + }, + { + href: '/', + name: 'Clients', + }, + { + href: '/', + name: 'Testimonials', + }, + ], + }, + { + title: 'Services', + links: [ + { + href: '/', + name: 'Marketing', + }, + { + href: '/', + name: 'Consulting', + }, + { + href: '/', + name: 'Development', + }, + { + href: '/', + name: 'Design', + }, + ], + }, + { + title: 'Contact us', + links: [ + { + href: '/', + name: 'United States', + }, + { + href: '/', + name: 'United Kingdom', + }, + { + href: '/', + name: 'Australia', + }, + { + href: '/', + name: 'Support', + }, + ], + }, + { + title: 'Social', + links: [ + { + href: '/', + name: 'Facebook', + children: , + }, + { + href: '/', + name: 'Twitter', + children: , + }, + { + href: '/', + name: 'Instagram', + children: , + }, + { + href: '/', + name: 'WhatsApp', + children: , + }, + ], + }, +]; +export default data; diff --git a/client/src/components/Footer/index.js b/client/src/components/Footer/index.js new file mode 100644 index 0000000..b685a2e --- /dev/null +++ b/client/src/components/Footer/index.js @@ -0,0 +1,62 @@ +import { + AppBar, Container, Link, +} from '@mui/material'; +import HomeIcon from '../../assets/homeIcon.png'; +import data from './data'; +import './style.css'; + +export default function Footer() { + return ( +
+ + +
+ home icon + +
+
+ { + data.map(({ title, links }, i) => ( +
+

+ {title} +

+ { + links.map(({ href, name, children }) => ( + + {children} + {name} + + )) + } +
+ )) + } +
+
+
+

All right reserved @ to this website

+
+
+
+ ); +} diff --git a/client/src/components/Footer/style.css b/client/src/components/Footer/style.css new file mode 100644 index 0000000..3f7efd0 --- /dev/null +++ b/client/src/components/Footer/style.css @@ -0,0 +1,43 @@ +.section2 { + display: flex; + width: 60%; + height: 100%; + align-content: center; + justify-content: space-around; + align-items: flex-start; + margin-top: 30px; +} + +.footer--list { + display: flex; + flex-direction: column; + height: 50%; +} + +.footer--list > a { + font-size: 24px; + margin-bottom: 20px; + font-size: 18px; + text-align: left; +} +.footer--list > a > svg { + margin-right: 5px; +} + +.footer--list > p { + font-size: 26px; + color: #fff; + margin-bottom: 40px; + font-weight: bold; + text-align: left; +} + +.Social * { + display: flex; + align-items: center; + margin-left: 10px; +} + +.footer { + background-color: black; +} diff --git a/client/src/components/ImageSection/ImageSection.js b/client/src/components/ImageSection/ImageSection.js new file mode 100644 index 0000000..774b7a5 --- /dev/null +++ b/client/src/components/ImageSection/ImageSection.js @@ -0,0 +1,28 @@ +import './imageSection.css'; + +function ImageSection({ arr }) { + const mainImage = arr[0]; + const images = arr.slice(1); + return ( +
+
+ home +
+
+ {images.map((item) => ( + home + ))} +
+
+ ); +} + +export default ImageSection; diff --git a/client/src/components/ImageSection/imageSection.css b/client/src/components/ImageSection/imageSection.css new file mode 100644 index 0000000..0a81431 --- /dev/null +++ b/client/src/components/ImageSection/imageSection.css @@ -0,0 +1,26 @@ +.main{ + display: flex; + width: 80%; + height: 60vh; +} +.bigImage{ + width: 70%; +} +.bigImage img{ + min-width: 100%; + max-height: 100% +} +.anotherImage{ + display: flex; + flex-direction:column; + width: 30%; + justify-content: space-between + +} +.anotherImage img{ + min-width: 100%; + min-height: 30%; + margin-left:10px; + margin-bottom: 6px; + +} \ No newline at end of file diff --git a/client/src/components/Navbar/index.js b/client/src/components/Navbar/index.js new file mode 100644 index 0000000..4d2c5ca --- /dev/null +++ b/client/src/components/Navbar/index.js @@ -0,0 +1,75 @@ +/* eslint-disable no-unused-vars */ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { + ListItem, ListItemText, Button, Avatar, Container, List, +} from '@mui/material'; +import './style.css'; + +import Logo from '../../asstes/logo.png'; +import PresonImg from '../../asstes/avatar.png'; + +function NavBar() { + const [logged, setLogged] = useState(true); + return ( + + + + + ); +} +export default NavBar; diff --git a/client/src/components/Navbar/style.css b/client/src/components/Navbar/style.css new file mode 100644 index 0000000..63b5d4a --- /dev/null +++ b/client/src/components/Navbar/style.css @@ -0,0 +1,18 @@ +.container { + display: flex; + flex-direction: row; + justify-content: space-around; +} +.img-logo { + width: 90px; + height: 80px; +} +.list { + display: flex; + align-self: center; +} +.agent { + display: flex; + flex-direction: row; + align-self: center; +} diff --git a/client/src/components/PropertyCard/PropertyCard.css b/client/src/components/PropertyCard/PropertyCard.css new file mode 100644 index 0000000..78db7f6 --- /dev/null +++ b/client/src/components/PropertyCard/PropertyCard.css @@ -0,0 +1,37 @@ +.row{ + display: flex; + flex-wrap: wrap; +} +.rectangle{ + width: 50%; + background-color:#26B919; + clip-path: polygon(0 -134%, 91% 75%, 91% 100%, 0% 100%); + border-radius: 6px; + display: flex; + justify-content: flex-start; + align-items: center; + padding: 10px; + + +} +.loveicon{ + text-align: end; +} +.price{ + font-size: 1.9rem; + width: 100%; + text-align: center; + margin-top: 5px; +} +.bathroom,.rooms,.beds,.sqft{ + width: 45%; + background-color:#3781CB; + padding: 10px; + margin:0 0 10px 10px; + border-radius: 6px; + display: flex; + justify-content: space-around; + align-items: center; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + +} \ No newline at end of file diff --git a/client/src/components/PropertyCard/index.js b/client/src/components/PropertyCard/index.js new file mode 100644 index 0000000..9edaea2 --- /dev/null +++ b/client/src/components/PropertyCard/index.js @@ -0,0 +1,103 @@ +import { + Card, + CardContent, +} from '@mui/material'; +import PropTypes from 'prop-types'; + +import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; +import { makeStyles } from '@mui/styles'; +import Space from '../../assets/space.png'; +import Bath from '../../assets/bath.png'; +import Rooms from '../../assets/rooms.png'; +import Bed from '../../assets/beds.png'; +import './PropertyCard.css'; + +const useStyles = makeStyles({ + card: { + backgroundColor: '#F1F1F1', + maxWidth: 345, + display: 'flex', + flexDirection: 'column', + }, + icons: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + + }, +}); +function PropertyCard({ + data: { + type, price, beds, baths, rooms, space, + }, +}) { + const classes = useStyles(); + + return ( + + +
+ {type} +
+
+ +
+
+ {price} + {' '} + $ +
+
+ + +
+ space + {baths} + {' '} + Bathrooms +
+
+ space + {rooms} + {' '} + Rooms +
+
+ space + {beds} + {' '} + Beds +
+
+ space + {space} + Sqft +
+
+
+ ); +} +PropertyCard.propTypes = { + data: PropTypes.shape({ + type: PropTypes.string, + price: PropTypes.string, + beds: PropTypes.string, + baths: PropTypes.string, + rooms: PropTypes.string, + space: PropTypes.string, + }).isRequired, +}; + +export default PropertyCard; diff --git a/client/src/components/Search/index.js b/client/src/components/Search/index.js new file mode 100644 index 0000000..b04d2a2 --- /dev/null +++ b/client/src/components/Search/index.js @@ -0,0 +1,11 @@ +import './style.css'; + +function Search() { + return ( +
+ Hello +
+ ); +} + +export default Search; diff --git a/client/src/components/Search/style.css b/client/src/components/Search/style.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/UserContacatCard/index.js b/client/src/components/UserContacatCard/index.js new file mode 100644 index 0000000..1d16b0c --- /dev/null +++ b/client/src/components/UserContacatCard/index.js @@ -0,0 +1,87 @@ +import { + Card, + CardContent, + Typography, + Avatar, + colors, + +} from '@mui/material'; +import MdPhone from '@mui/icons-material/Phone'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; +import EmailIcon from '@mui/icons-material/Email'; +import { makeStyles } from '@mui/styles'; +import PropTypes from 'prop-types'; + +const useStyles = makeStyles({ + card: { + backgroundColor: 'secondary', + maxWidth: 345, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: '10px', + }, + icon: { + marginRight: '0.5rem', + }, +}); +const contentStyles = { display: 'flex', marginBottom: '15px', alignItems: 'center' }; + +function UserContactCard({ + data: { + name, phone, image, location, email, + }, +}) { + const classes = useStyles(); + + return ( + + + + + + {name} + + + Building Owner + + + Contact Info: + + + + + {location} + + + + + {phone} + + + + + {email} + + + + + ); +} + +UserContactCard.propTypes = { + data: PropTypes.shape({ + image: PropTypes.string, + name: PropTypes.string, + location: PropTypes.string, + phone: PropTypes.string, + email: PropTypes.string, + }).isRequired, +}; + +export default UserContactCard; diff --git a/client/src/components/index.js b/client/src/components/index.js index e69de29..73198f3 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -0,0 +1,2 @@ +export { default as Search } from './Search'; +export { default as Footer } from './Footer'; diff --git a/client/src/pages/Estate/estate.css b/client/src/pages/Estate/estate.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pages/Estate/index.js b/client/src/pages/Estate/index.js new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pages/Profile/index.jsx b/client/src/pages/Profile/index.jsx index 4e26929..2b9c33b 100644 --- a/client/src/pages/Profile/index.jsx +++ b/client/src/pages/Profile/index.jsx @@ -18,9 +18,9 @@ const AgentProfile = () => { useEffect(() => { const getData = async () => { - await axios.get(`/api/v1/users/${userId}/estates`) + await axios.get(`/api/v1/user/${userId}/estates`) .then((res) => { - setData(res.data.data); + setData([res.data]); }) .catch((err) => { setError(err); @@ -29,8 +29,6 @@ const AgentProfile = () => { getData(); }, [userId]); - console.log('ssss', data); - console.log(error); return ( @@ -38,10 +36,10 @@ const AgentProfile = () => { data.length ? ( ) : 'loading' diff --git a/package.json b/package.json index 4d94a2e..0c61348 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "cross-env NODE_ENV=production node server", "dev": "cross-env NODE_ENV=development nodemon server", - "test": "cross-env NODE_ENV=test jest", + "test": "cross-env NODE_ENV=test jest --runInBand", "build:db": "cross-env NODE_ENV=development node server/database/config" }, "repository": { @@ -37,7 +37,7 @@ "eslint": "^7.32.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.25.2", - "jest": "^27.3.1", + "jest": "26.6.0", "nodemon": "^2.0.14", "pre-commit": "^1.2.2", "supertest": "^6.1.6" diff --git a/server/controllers/admins/index.js b/server/controllers/admins/index.js index e69de29..4655054 100644 --- a/server/controllers/admins/index.js +++ b/server/controllers/admins/index.js @@ -0,0 +1,7 @@ +const adminLogin = require('./login'); +const signupAdmin = require('./signupAdmin'); + +module.exports = { + adminLogin, + signupAdmin, +}; diff --git a/server/controllers/admins/login.js b/server/controllers/admins/login.js new file mode 100644 index 0000000..8c8742b --- /dev/null +++ b/server/controllers/admins/login.js @@ -0,0 +1,35 @@ +/* eslint-disable consistent-return */ +const bcrypt = require('bcrypt'); +const { loginSchema } = require('../../utils/validation/loginSchema'); +const { checkAdminQuery } = require('../../database/quieres'); +const { signToken } = require('../../utils'); + +const adminLogin = async (req, res, next) => { + try { + const { email, password } = req.body; + await loginSchema.validateAsync(req.body); + + const { rows } = await checkAdminQuery(email); + + if (!rows.length) { + return res.status(400).json({ message: 'Invalid email or password' }); + } + + const compared = await bcrypt.compare(password, rows[0].password); + if (!compared) { + return res.status(400).json({ message: 'Invalid email or password' }); + } + const token = await signToken(email, rows[0].id); + return res.cookie('token', token).json({ message: 'You are Logged Successfully' }); + } catch (err) { + if (err.details) { + res.status(400).json({ + message: err.details[0].message, + }); + } else { + return next(err); + } + } +}; + +module.exports = adminLogin; diff --git a/server/controllers/admins/signupAdmin.js b/server/controllers/admins/signupAdmin.js new file mode 100644 index 0000000..a143468 --- /dev/null +++ b/server/controllers/admins/signupAdmin.js @@ -0,0 +1,24 @@ +const { hash } = require('bcrypt'); +const adminSchema = require('../../utils/validation/adminSchema'); +const { signUpAdminQuery } = require('../../database/quieres'); +const { signToken } = require('../../utils'); + +module.exports = async (req, res, next) => { + try { + const { + error, value: { + password, email, username, + }, + } = adminSchema.validate(req.body); + if (error) return res.status(400).json({ message: error.details[0].message }); + const hasedPasword = await hash(password, 10); + await signUpAdminQuery(username, hasedPasword, email); + const token = await signToken(email, username); + return res.status(201).cookie('token', token).json({ message: 'user created' }); + } catch (err) { + if (err.code === '23505') { + return res.status(400).json({ message: 'The user is already exists' }); + } + return next(err); + } +}; diff --git a/server/controllers/estates/filterEstate.js b/server/controllers/estates/filterEstate.js new file mode 100644 index 0000000..72e3dc4 --- /dev/null +++ b/server/controllers/estates/filterEstate.js @@ -0,0 +1,19 @@ +const { filterEstates } = require('../../database/quieres'); + +module.exports = async (req, res, next) => { + try { + const { + query: { + type, category, location, price, roomNumbers, bathRooms, space, + }, + } = req; + const { rows } = await filterEstates({ + type, category, location, price, roomNumbers, bathRooms, space, + }); + res.json({ + data: rows, + }); + } catch (err) { + next(err); + } +}; diff --git a/server/controllers/estates/getEstate.js b/server/controllers/estates/getEstate.js new file mode 100644 index 0000000..e52564e --- /dev/null +++ b/server/controllers/estates/getEstate.js @@ -0,0 +1,24 @@ +const { getEstateQuery, getImagesQuery } = require('../../database/quieres'); + +module.exports = async (req, res, next) => { + const { estateId } = req.params; + if (!(estateId > 0)) { + return res.status(400).json({ + message: 'Invalid estate id', + }); + } + try { + const { rowCount, rows: estateData } = await getEstateQuery(estateId); + const { rows: imagesData } = await getImagesQuery(estateId); + if (rowCount > 0) { + return res.json({ + data: { ...estateData, images: [...imagesData] }, + }); + } + return res.status(400).json({ + message: 'Estate not found', + }); + } catch (err) { + return next(err); + } +}; diff --git a/server/controllers/estates/index.js b/server/controllers/estates/index.js index 0275996..3720bdd 100644 --- a/server/controllers/estates/index.js +++ b/server/controllers/estates/index.js @@ -1,7 +1,11 @@ const editEstate = require('./editEstate'); const deleteEstate = require('./deleteEstate'); +const filterEstate = require('./filterEstate'); +const getEstate = require('./getEstate'); module.exports = { editEstate, deleteEstate, + filterEstate, + getEstate, }; diff --git a/server/controllers/index.js b/server/controllers/index.js index aeaa25e..250bf75 100644 --- a/server/controllers/index.js +++ b/server/controllers/index.js @@ -1,8 +1,21 @@ -const { userEstateshandler, login } = require('./users'); -const getAllUsers = require('./users/getAllUsers'); -const { editEstate, deleteEstate } = require('./estates'); +const { + userEstateshandler, login, putAgent, getAllUsers, +} = require('./users'); +const { + editEstate, deleteEstate, filterEstate, getEstate, +} = require('./estates'); const logout = require('./logout'); +const { adminLogin } = require('./admins'); module.exports = { - getAllUsers, userEstateshandler, logout, login, deleteEstate, editEstate, + filterEstate, + getAllUsers, + userEstateshandler, + logout, + login, + deleteEstate, + editEstate, + putAgent, + adminLogin, + getEstate, }; diff --git a/server/controllers/logout.js b/server/controllers/logout.js index d04e524..7498b19 100644 --- a/server/controllers/logout.js +++ b/server/controllers/logout.js @@ -1,4 +1,4 @@ const logout = (req, res) => { - res.clearCookie('token'); + res.clearCookie('token').json({ message: 'Logout Successfully' }); }; module.exports = logout; diff --git a/server/controllers/users/index.js b/server/controllers/users/index.js index adee2b1..d0f5aca 100644 --- a/server/controllers/users/index.js +++ b/server/controllers/users/index.js @@ -1,11 +1,13 @@ const signup = require('./signup'); const userEstateshandler = require('./userEstates'); const getAllUsers = require('./getAllUsers'); +const putAgent = require('./putAgent'); const login = require('./login'); module.exports = { signup, getAllUsers, userEstateshandler, + putAgent, login, }; diff --git a/server/controllers/users/login.js b/server/controllers/users/login.js index 87b99eb..696baf6 100644 --- a/server/controllers/users/login.js +++ b/server/controllers/users/login.js @@ -11,16 +11,16 @@ const login = async (req, res, next) => { await loginSchema.validateAsync(req.body); const { rows } = await checkEmailQuery(email); - if (!rows.length) { return res.status(400).json({ message: 'Invalid email or password' }); } const compared = await bcrypt.compare(password, rows[0].password); + if (!compared) { return res.status(400).json({ message: 'Invalid email or password' }); } - const token = await signToken(email, rows[0].id); + const token = await signToken({ email, userId: rows[0].id }); return res.cookie('token', token).json({ message: 'You are Logged Successfully' }); } catch (err) { if (err.details) { diff --git a/server/controllers/users/putAgent.js b/server/controllers/users/putAgent.js new file mode 100644 index 0000000..a0f8cc6 --- /dev/null +++ b/server/controllers/users/putAgent.js @@ -0,0 +1,24 @@ +const { putAgent } = require('../../database/quieres'); +const editAgentSchema = require('../../utils/validation/editAgentSchema'); + +module.exports = async (req, res, next) => { + try { + const { userId } = req.user; + const { error, value } = editAgentSchema.validate({ ...req.body, userId }); + if (error) return res.status(400).json({ message: error.details[0].message }); + const { rowCount } = await putAgent(value); + if (rowCount === 1) { + return res.status(200).json({ + message: "Agent's data updated successfully", + }); + } + return res.status(400).json({ + message: 'There\'s no Agent, put correct id', + }); + } catch (err) { + if (err.detail) { + return res.status(400).json({ message: err.detail }); + } + return next(err); + } +}; diff --git a/server/controllers/users/signup.js b/server/controllers/users/signup.js index ab56711..1e61264 100644 --- a/server/controllers/users/signup.js +++ b/server/controllers/users/signup.js @@ -12,8 +12,10 @@ module.exports = async (req, res, next) => { } = agentSchema.validate(req.body); if (error) return res.status(400).json({ message: error.details[0].message }); const hasedPasword = await hash(password, 10); - await signUpQuery(username, email, phone, hasedPasword); - const token = await signToken(email, username, phone); + const { rows } = await signUpQuery(username, email, phone, hasedPasword); + const token = await signToken({ + email, username, phone, userId: rows[0].id, + }); return res.status(201).cookie('token', token).json({ message: 'user created' }); } catch (err) { if (err.code === '23505') { diff --git a/server/controllers/users/userEstates.js b/server/controllers/users/userEstates.js index 5f7a1ae..72a1c5f 100644 --- a/server/controllers/users/userEstates.js +++ b/server/controllers/users/userEstates.js @@ -1,13 +1,15 @@ -const { userEstatesQuery } = require('../../database/quieres/index'); +const { userEstatesQuery, agentQuery } = require('../../database/quieres/index'); const userEstateshandler = async (req, res) => { const { userId } = req.params; try { if (userId > 0) { - const { rows } = await userEstatesQuery(userId); + const { rows: agentData } = await agentQuery(userId); + const { rows: estateData } = await userEstatesQuery(userId); return res.json({ - data: rows, + agentData, + estateData, }); } return res.status(404).json({ diff --git a/server/database/config/build.sql b/server/database/config/build.sql index 3aaa5f2..2f58eb1 100644 --- a/server/database/config/build.sql +++ b/server/database/config/build.sql @@ -31,7 +31,7 @@ CREATE TABLE estates( rooms INTEGER DEFAULT 0, space DECIMAL NOT NULL , approved BOOLEAN DEFAULT FALSE, - rate INTEGER DEFAULT 0, + rate DECIMAL DEFAULT 0, available BOOLEAN DEFAULT TRUE ); diff --git a/server/database/config/connection.js b/server/database/config/connection.js index 4a402a1..f1ed1f7 100644 --- a/server/database/config/connection.js +++ b/server/database/config/connection.js @@ -23,6 +23,6 @@ switch (NODE_ENV) { const options = { connectionString: dbUrl, - ssl: { rejectUnauthorized: false }, + ssl: false, }; module.exports = new Pool(options); diff --git a/server/database/config/fakeData.sql b/server/database/config/fakeData.sql index 2ad51e6..ab48d18 100644 --- a/server/database/config/fakeData.sql +++ b/server/database/config/fakeData.sql @@ -1,25 +1,41 @@ -- INSERT INTO agents (name, email, password , phone) - VALUES ('Kai', 'kallport0@patch.com', '$2b$10$gT8Qb2Qe01W1QMRFmH9IC.3bmbA4PS2yG4XQvdkYWxKday.SbjGI2', '677-871-7450'), + VALUES ('Kai', 'kallport0@patch.com', '$2b$10$hZZ2f3zk.pV/9ndMRn78ze47MAh8SN8uy01qeoK8P54tTe526Pqz6', '677-871-7450'), ('Trixie', 'tbeadon1@plala.or.jp', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', '0599832685'), -('Allina', 'aburford2@tumblr.com', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', '630-385-8312'); +('Allina', 'aburford2@tumblr.com', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', '630-385-8312'), +('Fadel','fadel@gmail.com','$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2','0597854785'), +('Kelsey','kelsey@gmail.com','$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2','0597854755'); + INSERT INTO admins (username, password , email) VALUES ('ameera', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', 'ameera2021abed@gmail.com'), ('haroon', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', 'hro19502001@gmail.com'), ('elham', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', 'elham2000fadel@gmail.com'), ('sallah', '$2b$10$oNaAu46EHAyOCiufPgchaOQDq5opRxSFHB20m.e3wzDBlM5Yzztf2', 'mohmsal96@gmail.com'); -INSERT INTO estates ( agent_id, title, price, description, type, category, street, city, region, bathrooms, bedrooms, rooms, space, approved, rate, available) VALUES (1, 'suscipit ligula in', 190483.22, 'tristique', 'Buy', 'House', '3152 Morningstar Park', 'Edinburgh of the Seven Seas', 'Saint Helena', 2, 1, 1, 194, false, 2, false), - (2, 'ipsum primis in', 242471.89, 'est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante', 'Buy', 'House', '100 Butternut Hill', 'Bern', 'Switzerland', 1, 2, 2, 226, true, 5, true), - (3, 'rhoncus sed vestibulum', 84726.00, 'massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in', 'Buy', 'House', '0824 Mcguire Way', 'Kungshamn', 'Sweden', 1, 3, 3, 235, true, 5, false), -(1, 'sociis natoque penatibus et', 106226.71, 'quis odio consequat', 'Buy', 'House', '15918 Mcguire Point', 'Ranong', 'Thailand', 2, 4, 4, 244, true, 1, false), - (2, 'in faucibus orci luctus', 116162.27, 'parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum', 'Buy', 'House', '898 Dixon Crossing', 'Gelap', 'Indonesia', 3, 2, 4, 150, false, 3, false), - (3, 'vestibulum ante ipsum primis', 194193.55, 'leo odio porttitor id consequat in consequat ut nulla sed', 'Buy', 'House', '0891 7th Park', 'Álimos', 'Greece', 1, 3, 2, 174, false, 1, false); +INSERT INTO estates ( agent_id, title, price, description, type, category, street, city, region, bathrooms, bedrooms, rooms, space, approved, rate, available) VALUES + (1, 'Queen bed', 2152, 'A beautiful log house imported directly from Finland to be the perfect retreat from busy city life.', 'sale', 'whole house', 'Omare street', 'Gaza', 'Gaza', 2, 3, 4, 1200, true, 3.5,true), + (1, 'Decorated house', 242.89, 'A unique private house with a high wooden ceiling, wide and lit spaces, a yard house surrounded by plants and fruit trees, seating and grass corners. In addition, the house contains games for children of various ages(box games, table tennis, etc.) The house is located in a carriage on a quiet street and within walking distance to a commercial center that Kemer kept open on Saturday as well as for recreational areas ("artists stables")', 'rent', 'apartment', '100 Butternut Hill', 'Bern', 'Switzerland', 1, 2, 2, 226, true, 5, true), + (3, 'Amazing house in Galilee', 847.00, 'Big beautiful stonehouse with garden and balcony with breathtaking view. A large fireplace and cousy livingroom. A lot of plants and artistic decoration. Harashim lays on top of a mountain in Galilee. Great for relaxing vacation and sightseeing.', 'sale', 'villa', '0824 Mcguire Way', 'Kungshamn', 'Galilee', 1, 3, 3, 235, true, 5, true), +(2, 'Ecologic mud house facing Mt Tabor', 106.71, 'n the pastoral KIBUTZ Beit Keshet you will find our special ecologic mud house. The house is 130 sqm, with a large garden facing Mt Tabor. 5 min walk will take you to the forest, 25 min driving you will be in the sea of galilee.', 'rent', 'villa', '15918 Mcguire Point', 'Ranong', 'Thailand', 2, 4, 4, 244, true, 1, true), + (2, 'in faucibus orci luctus', 116162.27, 'parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum', 'rent', 'villa', '898 Dixon Crossing', 'Gelap', 'Indonesia', 3, 2, 4, 150, false, 3, false), + (3, 'vestibulum ante ipsum primis', 194193.55, 'leo odio porttitor id consequat in consequat ut nulla sed', 'sale', 'whole house', '0891 7th Park', 'Álimos', 'Greece', 1, 3, 2, 174, false, 1, false); + +INSERT INTO images ( estate_id, image) VALUES (1,'https://a0.muscache.com/im/pictures/65ad24f5-3c4f-4340-8e75-1c84278361f1.jpg'), +(1,'https://a0.muscache.com/im/pictures/9ccfb248-370e-49c8-833b-72a649908d5a.jpg'), +(1,'https://a0.muscache.com/im/pictures/039384b0-75de-4c34-aae2-6fd5c76a2b50.jpg'), +(1,'https://a0.muscache.com/im/pictures/c8e4ccb3-b13a-42a0-a1e3-8d48199b81a8.jpg'), + (2,'https://a0.muscache.com/im/pictures/miso/Hosting-37479423/original/9106de21-4abc-4dc6-ae10-8e071dcb4977.jpeg'), + (2,'https://a0.muscache.com/im/pictures/miso/Hosting-37479423/original/3072b6e9-3709-46a7-9bcf-3fee6c96b8bf.jpeg'), + (2,'https://a0.muscache.com/im/pictures/8d432dcd-6023-4d94-804b-f38e76d8e9b8.jpg'), + (2,'https://a0.muscache.com/im/pictures/878fdb40-1ded-4e5b-989f-20097ca59c89.jpg'), + (3,'https://a0.muscache.com/im/pictures/d87542ce-1089-44b7-b3a0-2ef397825fac.jpg'), + (3,'https://a0.muscache.com/im/pictures/2f64652d-7bce-48ef-96ac-61d74b3c70c4.jpg'), + (3,'https://a0.muscache.com/im/pictures/8673fa65-04a7-4a29-acfe-d6806ef01605.jpg'), + (3,'https://a0.muscache.com/im/pictures/5bea56a8-1028-410e-9891-4266b58d6dd4.jpg'), + (2,'https://a0.muscache.com/im/pictures/79715735/f68f24e3_original.jpg'), + (2,'https://a0.muscache.com/im/pictures/79716661/5ae7300a_original.jpg'), + (2,'https://a0.muscache.com/im/pictures/79716124/5a836c95_original.jpg'), + (2,'https://a0.muscache.com/im/pictures/71393509/07948f45_original.jpg'); + + -INSERT INTO images ( estate_id, image) VALUES (1,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/1.tobiarchitects.1526566679.5654.jpg'), - (2,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/2.tobiarchitects.1526566679.5654.jpg'), - (3,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/3.tobiarchitects.1526566679.5654.jpg'), - (1,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/4.tobiarchitects.1526566679.5654.jpg'), - (2,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/5.tobiarchitects.1526566679.5654.jpg'), - (3,'https://archello.s3.eu-central-1.amazonaws.com/images/2018/05/17/6.tobiarchitects.1526566679.5654.jpg'); - diff --git a/server/database/quieres/account/signUp.js b/server/database/quieres/account/signUp.js index 80d429c..39bcd98 100644 --- a/server/database/quieres/account/signUp.js +++ b/server/database/quieres/account/signUp.js @@ -1,3 +1,3 @@ const connection = require('../../config/connection'); -module.exports = (userName, email, phone, password) => connection.query('INSERT INTO agents (name,email,phone,password) VALUES ($1,$2,$3,$4)', [userName, email, phone, password]); +module.exports = (userName, email, phone, password) => connection.query('INSERT INTO agents (name,email,phone,password) VALUES ($1,$2,$3,$4) RETURNING id', [userName, email, phone, password]); diff --git a/server/database/quieres/agentQuery.js b/server/database/quieres/agentQuery.js new file mode 100644 index 0000000..d01f8ba --- /dev/null +++ b/server/database/quieres/agentQuery.js @@ -0,0 +1,3 @@ +const connection = require('../config/connection'); + +module.exports = (userId) => connection.query('SELECT agents.id, agents.name,agents.email,agents.phone,agents.avater FROM agents WHERE agents.id = ($1) ', [userId]); diff --git a/server/database/quieres/filterEstates.js b/server/database/quieres/filterEstates.js new file mode 100644 index 0000000..ce779d7 --- /dev/null +++ b/server/database/quieres/filterEstates.js @@ -0,0 +1,38 @@ +const connection = require('../config/connection'); + +module.exports = ({ + type, category, location, price, roomNumbers, bathRooms, space, +}) => { + let text = 'SELECT * FROM estates WHERE '; + const values = []; + + if (type) { + text += `type = $${values.length + 1} AND `; + values.push(type); + } + if (category) { + text += `category = $${values.length + 1} AND `; + values.push(category); + } + if (location) { + text += `location = $${values.length + 1} AND `; + values.push(location); + } + if (price) { + text += `price >= $${values.length + 1} AND `; + values.push(price); + } + if (roomNumbers) { + text += `rooms >= $${values.length + 1} AND `; + values.push(roomNumbers); + } + if (bathRooms) { + text += `bathrooms >= $${values.length + 1} AND `; + values.push(bathRooms); + } + if (space) { + text += `space >= $${values.length + 1} AND `; + values.push(space); + } + return connection.query({ text: text.slice(0, -4), values }); +}; diff --git a/server/database/quieres/getEstateQuery.js b/server/database/quieres/getEstateQuery.js new file mode 100644 index 0000000..69dc62a --- /dev/null +++ b/server/database/quieres/getEstateQuery.js @@ -0,0 +1,3 @@ +const connection = require('../config/connection'); + +module.exports = (estateId) => connection.query('SELECT * FROM estates where estates.id = $1;', [estateId]); diff --git a/server/database/quieres/getImagesQuery.js b/server/database/quieres/getImagesQuery.js new file mode 100644 index 0000000..d81deee --- /dev/null +++ b/server/database/quieres/getImagesQuery.js @@ -0,0 +1,3 @@ +const connection = require('../config/connection'); + +module.exports = (estateId) => connection.query('SELECT image FROM images where estate_id = $1;', [estateId]); diff --git a/server/database/quieres/index.js b/server/database/quieres/index.js index 9c42a66..de25226 100644 --- a/server/database/quieres/index.js +++ b/server/database/quieres/index.js @@ -1,15 +1,29 @@ const userEstatesQuery = require('./userEstatesQuiery'); const getAllUsersQuery = require('./getAllUsersQuery'); +const putAgent = require('./putAgent'); +const deleteEstate = require('./deleteEstateQuery'); const checkAdminQuery = require('./checkAdmin'); const checkEmailQuery = require('./checkEmailQuery'); const editEstateQuery = require('./editEstatesQuery'); const deleteEstateQuery = require('./deleteEstateQuery'); +const filterEstates = require('./filterEstates'); +const signUpAdminQuery = require('./signUpAdminQuery'); +const agentQuery = require('./agentQuery'); +const getImagesQuery = require('./getImagesQuery'); +const getEstateQuery = require('./getEstateQuery'); module.exports = { getAllUsersQuery, userEstatesQuery, + putAgent, + deleteEstate, checkAdminQuery, checkEmailQuery, editEstateQuery, deleteEstateQuery, + filterEstates, + signUpAdminQuery, + agentQuery, + getImagesQuery, + getEstateQuery, }; diff --git a/server/database/quieres/putAgent.js b/server/database/quieres/putAgent.js new file mode 100644 index 0000000..69bb01f --- /dev/null +++ b/server/database/quieres/putAgent.js @@ -0,0 +1,5 @@ +const connection = require('../config/connection'); + +module.exports = ({ + userId, username, email, phone, avater = '', +}) => connection.query('UPDATE agents SET name=$1,email=$2,phone=$3,avater=$4 WHERE id=$5', [username, email, phone, avater, userId]); diff --git a/server/database/quieres/signUpAdminQuery.js b/server/database/quieres/signUpAdminQuery.js new file mode 100644 index 0000000..2507b9e --- /dev/null +++ b/server/database/quieres/signUpAdminQuery.js @@ -0,0 +1,3 @@ +const connection = require('../config/connection'); + +module.exports = (userName, password, email) => connection.query('INSERT INTO admins (username,password, email) VALUES ($1,$2,$3)', [userName, password, email]); diff --git a/server/database/quieres/userEstatesQuiery.js b/server/database/quieres/userEstatesQuiery.js index f1b86ed..8b94ced 100644 --- a/server/database/quieres/userEstatesQuiery.js +++ b/server/database/quieres/userEstatesQuiery.js @@ -1,3 +1,3 @@ const connection = require('../config/connection'); -module.exports = (userId) => connection.query('SELECT * FROM estates WHERE agent_id = $1', [userId]); +module.exports = (userId) => connection.query('SELECT estates.id, estates.title ,estates.description , estates.type,estates.category FROM estates WHERE estates.id = ($1) ', [userId]); diff --git a/server/routes/admin.js b/server/routes/admin.js new file mode 100644 index 0000000..f2c7671 --- /dev/null +++ b/server/routes/admin.js @@ -0,0 +1,7 @@ +const router = require('express').Router(); +const { signupAdmin, adminLogin } = require('../controllers/admins'); + +router.post('/signup', signupAdmin); +router.post('/login', adminLogin); + +module.exports = router; diff --git a/server/routes/auth.js b/server/routes/auth.js deleted file mode 100644 index ad9201b..0000000 --- a/server/routes/auth.js +++ /dev/null @@ -1,7 +0,0 @@ -const router = require('express').Router(); -const { logout, login } = require('../controllers'); - -router.get('/logout', logout); -router.post('/login', login); - -module.exports = router; diff --git a/server/routes/estate.js b/server/routes/estate.js index 687f025..09c9047 100644 --- a/server/routes/estate.js +++ b/server/routes/estate.js @@ -1,9 +1,12 @@ const router = require('express').Router(); +const { + editEstate, deleteEstate, filterEstate, getEstate, +} = require('../controllers'); +const { isAuth } = require('../middleware'); -// const { isAuth, isAdmin } = require('../middleware'); -const { editEstate, deleteEstate } = require('../controllers'); - +router.get('/search', filterEstate); router.put('/:estateId', editEstate); +router.get('/:estateId', getEstate); -router.delete('/:estateId', deleteEstate); +router.delete('/:estateId', isAuth, deleteEstate); module.exports = router; diff --git a/server/routes/index.js b/server/routes/index.js index eb1e629..ebefe57 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,10 +1,12 @@ const router = require('express').Router(); -const auth = require('./auth'); const estate = require('./estate'); const users = require('./users'); +const { logout } = require('../controllers'); +const admins = require('./admin'); -router.use('/users', users); +router.get('/logout', logout); +router.use('/admin', admins); router.use('/estate', estate); -router.use('/', auth); +router.use('/user', users); module.exports = router; diff --git a/server/routes/users.js b/server/routes/users.js index e93c396..7210acc 100644 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -1,11 +1,17 @@ const router = require('express').Router(); -const { userEstateshandler, getAllUsers, logout } = require('../controllers'); +const { + userEstateshandler, getAllUsers, putAgent, login, +} = require('../controllers'); const estate = require('./estate'); const signup = require('../controllers/users/signup'); +const { isAuth } = require('../middleware'); router.use('/estate', estate); router.post('/signup', signup); +router.post('/login', login); router.get('/:userId/estates', userEstateshandler); +router.put('/', isAuth, putAgent); router.get('/', getAllUsers); +router.post('/login', login); module.exports = router; diff --git a/server/test/admins.test.js b/server/test/admins.test.js new file mode 100644 index 0000000..2cf26a6 --- /dev/null +++ b/server/test/admins.test.js @@ -0,0 +1,50 @@ +/* eslint-disable no-undef */ +const supertest = require('supertest'); +const app = require('../app'); +const dbBuild = require('../database/config/build'); +const connection = require('../database/config/connection'); + +beforeEach(() => dbBuild()); +afterAll(() => connection.end()); + +describe('Tests login route to admin', () => { + test(' login route /login ', async () => { + const res = await supertest(app) + .post('/api/v1/admin/login') + .send({ + email: 'mohmsal96@gmail.com', + password: '1234567894455', + }) + .expect(200); + return expect(res.body).toEqual({ message: 'You are Logged Successfully' }); + }); + + test(' login route /login with error in email or password ', async () => { + const res = await supertest(app) + .post('/api/v1/admin/login') + .send({ + email: 'msal96@gmail.com', + password: '1234566', + }) + .expect(400); + return expect(res.body).toEqual({ message: 'Invalid email or password' }); + }); +}); + +describe('test signup as admin ', () => { + test('test sign up endpoint when success', async () => { + const res = await supertest(app) + .post('/api/v1/admin/signup') + .send({ + username: 'test', + password: 'test123456', + email: 'test@gmail.com', + }) + .expect(201) + .expect((response) => expect(response.header['set-cookie'][0].split('=')[0]).toBe('token')) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'user created', + }); + }); +}); diff --git a/server/test/index.test.js b/server/test/agents.test.js similarity index 57% rename from server/test/index.test.js rename to server/test/agents.test.js index 56106ec..302736f 100644 --- a/server/test/index.test.js +++ b/server/test/agents.test.js @@ -7,23 +7,25 @@ const connection = require('../database/config/connection'); beforeEach(() => dbBuild()); afterAll(() => connection.end()); +const userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImthbGxwb3J0MEBwYXRjaC5jb20iLCJ1c2VySWQiOjEsImlhdCI6MTYzNjExMjAwNH0.o4iDQMffvwmqt47SDb5RifHWBSBK9FyG55cBN_QfcfQ'; + describe('Get all users', () => { test('get all users', async () => { const res = await supertest(app) - .get('/api/v1/users') + .get('/api/v1/user') .expect(200) .expect('Content-Type', /json/); - return expect(3).toEqual(res.body.data.length); + return expect(5).toEqual(res.body.data.length); }); }); describe('Tests login route', () => { test(' login route /login ', async () => { const res = await supertest(app) - .post('/api/v1/login') + .post('/api/v1/user/login') .send({ email: 'kallport0@patch.com', - password: '12345', + password: '123456789', }) .expect(200); return expect(res.body).toEqual({ message: 'You are Logged Successfully' }); @@ -31,7 +33,7 @@ describe('Tests login route', () => { test(' login route /login with error in email or password ', async () => { const res = await supertest(app) - .post('/api/v1/login') + .post('/api/v1/user/login') .send({ email: 'kallport0@patch.com', password: '123456987', @@ -44,51 +46,28 @@ describe('Tests login route', () => { describe('user estates', () => { test('get users estates', async () => { const res = await supertest(app) - .get('/api/v1/users/3/estates') + .get('/api/v1/user/3/estates') .expect(200) .expect('Content-Type', /json/); return expect(res.body).toEqual({ - data: [ + agentData: [ { id: 3, - agent_id: 3, - title: 'rhoncus sed vestibulum', - price: '84726.00', - description: 'massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in', - type: 'Buy', - category: 'House', - street: '0824 Mcguire Way', - city: 'Kungshamn', - region: 'Sweden', - bathrooms: 1, - bedrooms: 3, - rooms: 3, - space: '235', - approved: true, - rate: 5, - available: false, + name: 'Allina', + email: 'aburford2@tumblr.com', + phone: '630-385-8312', + avater: null, }, + ], + estateData: [ { - id: 6, - agent_id: 3, - title: 'vestibulum ante ipsum primis', - price: '194193.55', - description: 'leo odio porttitor id consequat in consequat ut nulla sed', - type: 'Buy', - category: 'House', - street: '0891 7th Park', - city: 'Álimos', - region: 'Greece', - bathrooms: 1, - bedrooms: 3, - rooms: 2, - space: '174', - approved: false, - rate: 1, - available: false, + id: 3, + title: 'Amazing house in Galilee', + description: 'Big beautiful stonehouse with garden and balcony with breathtaking view. A large fireplace and cousy livingroom. A lot of plants and artistic decoration. Harashim lays on top of a mountain in Galilee. Great for relaxing vacation and sightseeing.', + type: 'sale', + category: 'villa', }, ], - }); }); }); @@ -96,7 +75,7 @@ describe('user estates', () => { describe('user estates', () => { test('get users estates', async () => { const res = await supertest(app) - .get('/api/v1/users/three/estates') + .get('/api/v1/user/three/estates') .expect(404) .expect('Content-Type', /json/); return expect(res.body).toEqual({ @@ -104,87 +83,10 @@ describe('user estates', () => { }); }); }); -describe('user estates', () => { - test('edit estates', async () => { - const res = await supertest(app) - .put('/api/v1/estate/3') - .send({ - title: '1', - price: 10, - description: 's', - type: 's', - category: 's', - street: 's', - city: 's', - region: 's', - bathrooms: 1, - bedrooms: 1, - rooms: 1, - space: 50, - available: false, - }) - .expect(200) - .expect('Content-Type', /json/); - return expect(res.body.message).toBe('Estate updated successfully'); - }); - - test('edit estates erorr', async () => { - const res = await supertest(app) - .put('/api/v1/estate/350') - .send({ - title: '1', - price: 10, - description: 's', - type: 's', - category: 's', - street: 's', - city: 's', - region: 's', - bathrooms: 1, - bedrooms: 1, - rooms: 1, - space: 50, - available: false, - }) - .expect(400) - .expect('Content-Type', /json/); - return expect(res.body.message).toBe('enter valid estate id '); - }); -}); - -describe('Delete Specific Estate By Using Id', () => { - test('/estate/:estateId status 200 ', async () => { - const res = await supertest(app) - .delete('/api/v1/estate/1') - .expect(200) - .expect('Content-Type', /json/); - return expect(res.body).toEqual({ - message: 'Estate deleted successfully', - }); - }); - test('/estate/:estateId status 400, when delete the same estate was deleted or not found ', async () => { - const res = await supertest(app) - .delete('/api/v1/estate/100') - .expect(400) - .expect('Content-Type', /json/); - return expect(res.body).toEqual({ - message: 'You can\'t complete this process at the moment', - }); - }); - test('/estate/:estateId status 400, Invalid estate id ', async () => { - const res = await supertest(app) - .delete('/api/v1/estate/-121') - .expect(400) - .expect('Content-Type', /json/); - return expect(res.body).toEqual({ - message: 'Invalid estate id', - }); - }); -}); describe('test signup endpoint with all cases ', () => { test('test sign up endpoint when success', async () => { const res = await supertest(app) - .post('/api/v1/users/signup') + .post('/api/v1/user/signup') .send({ username: 'test', password: 'test123456', @@ -199,10 +101,9 @@ describe('test signup endpoint with all cases ', () => { message: 'user created', }); }); - test('test signup error validation phone" length must be 10 characters long ', async () => { const res = await supertest(app) - .post('/api/v1/users/signup') + .post('/api/v1/user/signup') .send({ username: 'Kai', password: '1234567894455', @@ -218,7 +119,7 @@ describe('test signup endpoint with all cases ', () => { }); test('test signup username or phone already exists ', async () => { const res = await supertest(app) - .post('/api/v1/users/signup') + .post('/api/v1/user/signup') .send({ username: 'Kai', password: '1234567894455', @@ -235,7 +136,7 @@ describe('test signup endpoint with all cases ', () => { test('test signup confirmpassword ', async () => { const res = await supertest(app) - .post('/api/v1/users/signup') + .post('/api/v1/user/signup') .send({ username: 'test', password: 'test123456', @@ -250,3 +151,51 @@ describe('test signup endpoint with all cases ', () => { }); }); }); +describe('test Edit Agent data /user/:iduser ', () => { + test('test 200', async () => { + const res = await supertest(app) + .put('/api/v1/user') + .set('Cookie', [`token=${userToken}`]) + .send({ + username: 'test', + email: 'kallport0@patch.com', + phone: '059985555555', + }) + .expect(200) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'Agent\'s data updated successfully', + }); + }); + + test('test 400', async () => { + const res = await supertest(app) + .put('/api/v1/user') + .set('Cookie', [`token=${userToken}`]) + .send({ + username: 'test', + email: 'kallport0@patch.com', + phone: '0599', + }) + .expect(400) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: '"phone" length must be at least 9 characters long', + }); + }); + test('test 400 when user unknown get token for another user ', async () => { + const res = await supertest(app) + .put('/api/v1/user') + .set('Cookie', ['token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1hdTdhbW1hZGFiZWRAZ21haWwuY29tIiwidXNlcklkIjo0LCJpYXQiOjE2MzU5NDkyNTl9.St177PIpsDIHAVke6PxoGC8_cJmUrggpyhEcJ4QWKfI']) + .send({ + username: 'test', + email: 'kallport0@patch.com', + phone: '059915587555', + }) + .expect(401) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'You are not authorized ', + }); + }); +}); diff --git a/server/test/estates.test.js b/server/test/estates.test.js new file mode 100644 index 0000000..1c58e24 --- /dev/null +++ b/server/test/estates.test.js @@ -0,0 +1,156 @@ +/* eslint-disable no-undef */ +const supertest = require('supertest'); +const app = require('../app'); +const dbBuild = require('../database/config/build'); +const connection = require('../database/config/connection'); + +const userToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEifQ.ABHyuVZkr37lK6lelg18vAFAApbMj6KGTGhEbm9ROg4'; + +beforeEach(() => dbBuild()); +afterAll(() => connection.end()); +test('edit estates erorr', async () => { + const res = await supertest(app) + .put('/api/v1/estate/350') + .send({ + title: '1', + price: 10, + description: 's', + type: 's', + category: 's', + street: 's', + city: 's', + region: 's', + bathrooms: 1, + bedrooms: 1, + rooms: 1, + space: 50, + available: false, + }) + .expect(400) + .expect('Content-Type', /json/); + return expect(res.body.message).toBe('enter valid estate id '); +}); + +describe('Delete Specific Estate By Using Id', () => { + test('/estate/:estateId status 200 ', async () => { + const res = await supertest(app) + .delete('/api/v1/estate/1') + .set('Cookie', [`token=${userToken}`]) + .expect(200) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'Estate deleted successfully', + }); + }); + test('/estate/:estateId status 400, when delete the same estate was deleted or not found ', async () => { + const res = await supertest(app) + .delete('/api/v1/estate/100') + .set('Cookie', [`token=${userToken}`]) + .expect(400) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'You can\'t complete this process at the moment', + }); + }); + test('/estate/:estateId status 400, Invalid estate id ', async () => { + const res = await supertest(app) + .delete('/api/v1/estate/-121') + .set('Cookie', [`token=${userToken}`]) + .expect(400) + .expect('Content-Type', /json/); + return expect(res.body).toEqual({ + message: 'Invalid estate id', + }); + }); +}); + +describe('user estates', () => { + test('edit estates', async () => { + const res = await supertest(app) + .put('/api/v1/estate/3') + .send({ + title: '1', + price: 10, + description: 's', + type: 's', + category: 's', + street: 's', + city: 's', + region: 's', + bathrooms: 1, + bedrooms: 1, + rooms: 1, + space: 50, + available: false, + }) + .expect(200) + .expect('Content-Type', /json/); + return expect(res.body.message).toBe('Estate updated successfully'); + }); +}); + +test('api/v1/estate/search ', async () => { + const res = await supertest(app) + .get('/api/v1/estate/search?type=rent') + .expect(200) + .expect('Content-Type', /json/); + return expect(res.body.data).toEqual([ + { + id: 2, + agent_id: 1, + title: 'Decorated house', + price: '242.89', + description: 'A unique private house with a high wooden ceiling, wide and lit spaces, a yard house surrounded by plants and fruit trees, seating and grass corners. In addition, the house contains games for children of various ages(box games, table tennis, etc.) The house is located in a carriage on a quiet street and within walking distance to a commercial center that Kemer kept open on Saturday as well as for recreational areas ("artists stables")', + type: 'rent', + category: 'apartment', + street: '100 Butternut Hill', + city: 'Bern', + region: 'Switzerland', + bathrooms: 1, + bedrooms: 2, + rooms: 2, + space: '226', + approved: true, + rate: '5', + available: true, + }, + { + id: 4, + agent_id: 2, + title: 'Ecologic mud house facing Mt Tabor', + price: '106.71', + description: 'n the pastoral KIBUTZ Beit Keshet you will find our special ecologic mud house. The house is 130 sqm, with a large garden facing Mt Tabor. 5 min walk will take you to the forest, 25 min driving you will be in the sea of galilee.', + type: 'rent', + category: 'villa', + street: '15918 Mcguire Point', + city: 'Ranong', + region: 'Thailand', + bathrooms: 2, + bedrooms: 4, + rooms: 4, + space: '244', + approved: true, + rate: '1', + available: true, + }, + { + id: 5, + agent_id: 2, + title: 'in faucibus orci luctus', + price: '116162.27', + description: 'parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum', + type: 'rent', + category: 'villa', + street: '898 Dixon Crossing', + city: 'Gelap', + region: 'Indonesia', + bathrooms: 3, + bedrooms: 2, + rooms: 4, + space: '150', + approved: false, + rate: '3', + available: false, + }, + ]); +}); diff --git a/server/utils/tokenFunction.js b/server/utils/tokenFunction.js index c7ac90e..d6d1b4a 100644 --- a/server/utils/tokenFunction.js +++ b/server/utils/tokenFunction.js @@ -1,4 +1,5 @@ const { verify, sign } = require('jsonwebtoken'); +require('env2')('.env'); const { env: { ACCESS_TOKEN_SECRET }, diff --git a/server/utils/validation/adminSchema.js b/server/utils/validation/adminSchema.js new file mode 100644 index 0000000..aa80037 --- /dev/null +++ b/server/utils/validation/adminSchema.js @@ -0,0 +1,7 @@ +const joi = require('joi'); + +module.exports = joi.object({ + username: joi.string().required(), + email: joi.string().email().required(), + password: joi.string().min(5).required(), +}); diff --git a/server/utils/validation/editAgentSchema.js b/server/utils/validation/editAgentSchema.js new file mode 100644 index 0000000..c404d63 --- /dev/null +++ b/server/utils/validation/editAgentSchema.js @@ -0,0 +1,9 @@ +const joi = require('joi'); + +module.exports = joi.object({ + username: joi.string().required(), + email: joi.string().email().required(), + phone: joi.string().min(9).required(), + avater: joi.string(), + userId: joi.number().min(1).required(), +});