diff --git a/.gitignore b/.gitignore index 82adee2e0a..5b682232da 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ web_modules/ .env .env.test +.expo/ # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache diff --git a/client/.gitignore b/client/.gitignore index 0767da9258..7c3ecb2735 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,6 +1,6 @@ # For users to edit app.json - +.env .expo/ dist/ diff --git a/client/_layout.js b/client/_layout.js index f29df93fc4..56eb197a85 100644 --- a/client/_layout.js +++ b/client/_layout.js @@ -8,7 +8,10 @@ import { QueryClientProvider } from "@tanstack/react-query"; import { queryClient } from "../constants/queryClient"; import { NativeBaseProvider } from "native-base"; import store from "../store/store"; -import NavigationMobile from "../screens/NavigationMobile"; +import NavigationMobile from "./screens/NavigationMobile"; +import 'mapbox-gl/dist/mapbox-gl.css'; +import 'mapbox://mapbox.mapbox-streets-v7' + import { ProviderAuth } from "../auth/provider"; diff --git a/client/api/getGeoCode.js b/client/api/getGeoCode.js index 42dac03bff..8aeae226c7 100644 --- a/client/api/getGeoCode.js +++ b/client/api/getGeoCode.js @@ -1,26 +1,8 @@ -// import { Geoapify_Key } from "../constants/api"; -import { GEOAPIFY_KEY } from "@env"; +import { api } from "../constants/api"; -export const getGeoCode = async (addressArray = "Virginia") => { - const transform = addressArray.split(", ").join("%20").split(" ").join("%20"); - - const root = "https://api.geoapify.com/v1/geocode/search"; - - let params = `?`; - - // let addressParams = addressArray.join("%20").replace(/\s/g, "%20"); - - if (addressArray) params += `text=${transform}`; - - const api_key = `&apiKey=${GEOAPIFY_KEY}`; - - params += api_key; - - const url = root + params; - - let resultReturn; - - await fetch(url) +export const getGeoCode = async (addressArray) => { + let resultReturn + await fetch(`${api}/geocode?addressArray=${addressArray}`) .then((response) => response.json()) .then((result) => { resultReturn = result; diff --git a/client/api/getParks.js b/client/api/getParks.js index f657971d43..e645c9e244 100644 --- a/client/api/getParks.js +++ b/client/api/getParks.js @@ -1,34 +1,18 @@ -// import { NPS_API } from "../constants/api"; -import { NPS_API, X_RAPIDAPI_KEY } from "@env"; +import { api } from "../constants/api"; import abbrRegion from "../constants/convertStateToAbbr"; export const getParksRapid = async (state) => { let parksArray = []; - - const host = `https://jonahtaylor-national-park-service-v1.p.rapidapi.com/parks?stateCode=${ - abbrRegion(state, "abbr") ?? "" - }`; - - const options = { - method: "GET", - headers: { - "X-Api-Key": `${NPS_API}`, - "X-RapidAPI-Key": `${X_RAPIDAPI_KEY}`, - "X-RapidAPI-Host": "jonahtaylor-national-park-service-v1.p.rapidapi.com", - "User-Agent": "PackRat", - }, - }; - - await fetch(host, options) - .then((res) => res.json()) - .then((json) => { - // console.log("json.data:", json.data); - json.data.forEach((item) => { - parksArray.push(item); - }); - }) - .catch((err) => console.error("error:" + err)); - + const abbrState = abbrRegion(state, "abbr") ?? ""; + if (abbrState) { + await fetch(`${api}/getparks?abbrState=${abbrState}`) + .then((res) => res.json()) + .then((json) => { + json.data.forEach((item) => { + parksArray.push(item); + }); + }).catch((err) => console.error("error:" + err)); + } if (parksArray.length > 0) { parksArray = parksArray.map((park) => park.name); } else { diff --git a/client/api/getTrails.js b/client/api/getTrails.js index 283e394241..e78803de81 100644 --- a/client/api/getTrails.js +++ b/client/api/getTrails.js @@ -1,65 +1,16 @@ -import { X_RAPIDAPI_KEY } from "@env"; +import { api } from "../constants/api"; export const getTrailsRapid = async (locationObject, latParams, lonParams) => { let trailsArray = []; - let radiusParams = 25; - let activityParams = true; - - const { - administrative_area_level_1: state, - country, - locality: city, - } = locationObject; - - let paramsConditional = ""; - - const root = "https://trailapi-trailapi.p.rapidapi.com/trails/explore/?"; - - if (latParams) paramsConditional += `lat=${latParams}`; - if (lonParams) paramsConditional += `&lon=${lonParams}`; - - if (city) paramsConditional += `&q-city_cont=${city.replace(/\s/g, "")}`; - - if (radiusParams) paramsConditional += `&radius=${radiusParams}`; - if (activityParams) - paramsConditional += `&q-activities_activity_type_name_eq=hiking`; - - // const limitParams = `limit=${limit}` - - // const latParams = `lat=34.1` - - // const lonParams = `lon=-105.2` - - // const cityParams = `q-city_cont=${city}` - - // const radiusParams = `radius=50` - - // const activityParams = 'q-activities_activity_type_name_eq=hiking' - - // const params = `?${limitParams}&${latParams}&${lonParams}&${city ? cityParams : ''}&${radiusParams}&${activityParams}` - - // const params = `?${limitParams}&${latParams}&${lonParams}&${cityParams}&${radiusParams}&${activityParams}` - - const url1 = root + paramsConditional; - - // https://trailapi-trailapi.p.rapidapi.com/activity/?&lat=34.1&lon=-105.2&q-city_cont=Charlottesville&radius=50&q-activities_activity_type_name_eq=hiking - const url = - "https://trailapi-trailapi.p.rapidapi.com/activity/?lat=34.1&lon=-105.2&q-city_cont=Denver&radius=25&q-activities_activity_type_name_eq=hiking"; - // const url = 'https://trailapi-trailapi.p.rapidapi.com/activity?lat=34.1&limit=25&lon=-105.2&q-city_cont=Denver&q-country_cont=Australia&q-state_cont=California&radius=25&q-activities_activity_type_name_eq=hiking' - // const url = root + `${cityParams}&${activityParams}` - // const url = root + `?${activityParams}&${limitParams}` - - const options = { - method: "GET", + await fetch(api + "/gettrails", { + method: 'POST', headers: { - "X-RapidAPI-Key": `${X_RAPIDAPI_KEY}`, - "X-RapidAPI-Host": "trailapi-trailapi.p.rapidapi.com", + 'Accept': 'application/json', + 'Content-Type': 'application/json' }, - }; - - await fetch(url1, options) - .then((res) => res.json()) + body: JSON.stringify({ ...locationObject, latitude: latParams, longitude: lonParams }) + }).then((res) => res.json()) .then((json) => { Object.values(json).forEach((item) => { trailsArray.push(item); diff --git a/client/api/getWeather.js b/client/api/getWeather.js index f9ef3419af..d51ddf2f04 100644 --- a/client/api/getWeather.js +++ b/client/api/getWeather.js @@ -1,36 +1,19 @@ -// import { OpenWeather_Key } from "../constants/api"; -import { OPENWEATHER_KEY } from "@env"; +import { api } from "../constants/api"; export const getWeather = async (lat, lon, state) => { let weatherObject = {}; - const root = "https://api.openweathermap.org/data/2.5/weather"; - let params = `?`; - const latParams = lat; - const lonParams = lon; - const unitParams = "imperial"; - const apiParams = true; - - if (latParams) params += `lat=${latParams}`; - if (lonParams) params += `&lon=${lonParams}`; - if (unitParams) params += `&units=${unitParams}`; - if (apiParams) params += `&appid=${OPENWEATHER_KEY}`; + if (lat) params += `lat=${lat}`; + if (lon) params += `&lon=${lon}`; - const url = root + params; + const url = api + "/weather" + params; await fetch(url) .then((res) => res.json()) .then((json) => { weatherObject = json; - // for (const key of Object.keys(json)) { - // const {weatherObject[key]} = json[key] - // } - - // Object.values(json).forEach(item => { - // weatherArray.push(item) - // }) }) .catch((err) => { console.error("error:" + err); diff --git a/client/api/getWeatherWeek.js b/client/api/getWeatherWeek.js index 2f5a63cc0c..625a88ba34 100644 --- a/client/api/getWeatherWeek.js +++ b/client/api/getWeatherWeek.js @@ -1,25 +1,17 @@ -// import { OpenWeather_Key } from "../constants/api"; -import { OPENWEATHER_KEY } from "@env"; +import { api } from "../constants/api"; export const getWeatherWeek = async (lat, lon) => { let weatherObject = {}; - const root = "https://api.openweathermap.org/data/2.5/forecast"; - let params = `?`; const latParams = lat; const lonParams = lon; - const unitParams = "imperial"; - - const apiParams = true; if (latParams) params += `lat=${latParams}`; if (lonParams) params += `&lon=${lonParams}`; - if (unitParams) params += `&units=${unitParams}`; - if (apiParams) params += `&appid=${OPENWEATHER_KEY}`; - const url = root + params; + const url = api + "/weather/week" + params; await fetch(url) .then((res) => res.json()) diff --git a/client/app.example.json b/client/app.example.json new file mode 100644 index 0000000000..42542e0cbc --- /dev/null +++ b/client/app.example.json @@ -0,0 +1,44 @@ +{ + "expo": { + "name": "packrat-front", + "slug": "packrat-front", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "light", + "scheme": "./app/index.js", + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "scheme": "myapp" + }, + "assetBundlePatterns": [ + "**/*" + ], + "ios": { + "supportsTablet": true, + "bundleIdentifier": "com.andrewbierman.packratfront" + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#ffffff" + }, + "package": "com.andrewbierman.packratfront" + }, + "web": { + "favicon": "./assets/favicon.png", + "bundler": "metro" + }, + "plugins": [ + [ + "@rnmapbox/maps", + { + "RNMapboxMapsImpl": "mapbox", + "RNMapboxMapsDownloadToken": "sk..." + } + ] + ] + } +} \ No newline at end of file diff --git a/client/app/index.js b/client/app/index.js index e617cf13f8..16f661c2cd 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -137,7 +137,7 @@ export default function Index() { /> - + diff --git a/client/babel.config.js b/client/babel.config.js index fea838a288..3267bc02ff 100644 --- a/client/babel.config.js +++ b/client/babel.config.js @@ -11,6 +11,6 @@ module.exports = function (api) { safe: false, allowUndefined: true, }] - ], + ] }; }; diff --git a/client/components/Card.js b/client/components/Card.js index d7ef1adbf0..a190659d8f 100644 --- a/client/components/Card.js +++ b/client/components/Card.js @@ -22,10 +22,10 @@ export default function Card({ title, Icon, isMap, data, isSearch, isTrail }) { isSearch ? styles.searchContainer : isMap - ? styles.mapCard - : styles.containerMobile - ? styles.containerMobile - : styles.mutualStyles + ? styles.mapCard + : styles.containerMobile + ? styles.containerMobile + : styles.mutualStyles } > {isMap ? null : isSearch ? ( + // Search ) : ( { const [isLoadingMobile, setIsLoadingMobile] = useState(false); const dispatch = useDispatch(); - const lat = geoCode?.features[0]?.geometry?.coordinates[1]; const lon = geoCode?.features[0]?.geometry?.coordinates[0]; const state = geoCode?.features[0]?.properties.state; @@ -79,7 +78,7 @@ export const SearchInput = () => { { + setError(null) + Axios.post(`${api}/user/checkcode`, { email: email, code: code }).then((res) => { + if (res.data.message == "success") { + setStatus("confirm") + } else { + setError(res.data.message) + } + }).catch(() => { + setError("Error on your browser") + }) + } + const emailExists = () => { + setError(null) + Axios.post(`${api}/user/emailexists`, { email: email }).then((res) => { + if (res.data.message == "success") { + setStatus("verification") + } else { + setError(res.data.message) + } + }).catch(() => { + setError("Error on your browser") + }) + } + const updatePassword = () => { + setError(null) + Axios.post(`${api}/user/updatepassword`, { email: email, password: password }).then((res) => { + if (res.data.message == "success") { + setStatus("login") + } else { + setError(res.data.message) + } + }).catch(() => { + setError("Error on your browser") + }) + } + return (
- - - Welcome - - - Sign in to continue! - - - - - Email ID - setEmail(text)} /> - - - Password - setPassword(text)} - type="password" - /> - - - + Welcome + + + Sign in to continue! + + + + + Email ID + setEmail(text)} /> + + + Password + setPassword(text)} + type="password" + /> + { setStatus("email") }} > - I'm a new user. + forgot password - + + - Sign Up + I'm a new user. - - + + + Sign Up + + + + {/* Google Login starts*/} + + + Or + + + + + + {/* Google Login */} + + + } + < Box safeArea p="2" py="8" w="90%" maxW="290"> + + {status == "email" && + + {error} + + Enter Email ID + setEmail(text)} /> + + + + } + {status == "verification" && + + {error} + + Enter Verification code + setCode(text)} /> + + + } + {status == "confirm" && + + {error} + + Enter new password + setPassword(text)} /> + + + + } {loginUser.isSuccess && router.push("/")} -
+ ); + } diff --git a/client/screens/RegisterScreen.js b/client/screens/RegisterScreen.js index 8198aba3ce..8413adc079 100644 --- a/client/screens/RegisterScreen.js +++ b/client/screens/RegisterScreen.js @@ -8,11 +8,14 @@ import { Center, HStack, Text, + View } from "native-base"; -import { useState } from "react"; + +import { useState, useEffect } from "react"; import useRegister from "../hooks/useRegister"; import { useRouter } from "expo-router"; import { Link } from "expo-router"; +import { signInWithGoogle } from "./firebase"; export default function Register() { const [name, setName] = useState(""); @@ -64,7 +67,7 @@ export default function Register() { /> + + {/* Google register */}
{addUser.isSuccess && router.push("/sign-in")} diff --git a/client/screens/firebase.js b/client/screens/firebase.js new file mode 100644 index 0000000000..b1444c0e9b --- /dev/null +++ b/client/screens/firebase.js @@ -0,0 +1,29 @@ +import { initializeApp } from "firebase/app"; +import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; +import { FIREBASE_API_KEY } from "@env" + +const firebaseConfig = { + apiKey: FIREBASE_API_KEY, + authDomain: "auth-8b1ef.firebaseapp.com", + projectId: "auth-8b1ef", + storageBucket: "auth-8b1ef.appspot.com", + messagingSenderId: "445273762769", + appId: "1:445273762769:web:81f403cae7cb5fe3760ef0", +}; + +const app = initializeApp(firebaseConfig); +export const auth = getAuth(app); + +const provider = new GoogleAuthProvider(); + +export const signInWithGoogle = async () => { + return new Promise((resolve, reject) => { + signInWithPopup(auth, provider) + .then((result) => { + resolve({ name: result.user.displayName, email: result.user.email }) + }) + .catch(() => { + resolve({ message: "error" }) + }); + }) +}; diff --git a/server/controllers/auth.js b/server/controllers/auth.js new file mode 100644 index 0000000000..baab08947d --- /dev/null +++ b/server/controllers/auth.js @@ -0,0 +1,103 @@ +import User from "../models/userModel.js"; +import nodemailer from 'nodemailer' +import smtpTransport from 'nodemailer-smtp-transport' + + +export const checkCode = async (req, res) => { + const { email, code } = req.body; + try { + let user = await User.find({ + $and: [ + { "email": email.toLowerCase() }, + { "code": code } + ] + }) + if (user.length) { + res.status(200).json({ message: "success" }); + } else { + res.status(200).json({ message: "Incorrect code" }); + } + } catch (error) { + res.status(404).json({ message: "Server Error" }); + } +}; + +export const emailExists = async (req, res) => { + const { email } = req.body; + try { + let val = await User.find({ email: email.toLowerCase() }); + if (val.length) { + sendEmailNotice(email).then(async (result1) => { + if (result1.status) { + let { newcode } = result1 + User.findOneAndUpdate({ email: email.toLowerCase() }, { code: newcode }, { + returnOriginal: false, + }).then((result2) => { + console.log(result2.email) + if (result2.code) { + res.status(200).json({ message: "success" }); + } + }).catch(() => { + res.status(200).json({ message: "Unable to send code" }); + }) + } else { + res.status(200).json({ message: "Unable to send code" }); + } + }) + } else { + res.status(200).json({ message: "User not found" }); + } + } catch (error) { + res.status(404).json({ message: "Server Error" }); + } +}; + + +export const updatePassword = async (req, res) => { + const { email, password } = req.body; + + try { + let val = await User.findOneAndUpdate({ email: email.toLowerCase() }, { password: password }, { + returnOriginal: false, + }) + if (val.email) { + res.status(200).json({ message: "success" }); + } else { + res.status(200).json({ message: "Unable to update password" }); + } + } catch (error) { + res.status(404).json({ message: "Server Error" }); + } +}; + +async function sendEmailNotice(email) { + return new Promise((resolve, reject) => { + var newcode = Math.floor((Math.random() * 999999) + 111111); + var transporter = nodemailer.createTransport(smtpTransport({ + service: 'gmail', + host: 'smtp.gmail.com', + auth: { + user: process.env.EMAIL, + pass: process.env.APPPASSWORD + } + })); + + var mailOptions = { + from: 'Hailemelekot@gmail.com', + to: email, + subject: 'PackRat verification code', + text: 'Your verification code is ' + newcode + }; + + transporter.sendMail(mailOptions, function (error, info) { + if (error) { + resolve({ status: false }) + } else { + resolve({ status: true, newcode: newcode }) + } + }) + }) +} + + + diff --git a/server/controllers/geoCodeController.js b/server/controllers/geoCodeController.js new file mode 100644 index 0000000000..657bf13691 --- /dev/null +++ b/server/controllers/geoCodeController.js @@ -0,0 +1,26 @@ +import fetch from "node-fetch"; +import { oneEntity } from "../utils/oneEntity.js"; + +export const getGeoCode = async (req, res) => { + let addressArray = await oneEntity(req.query.addressArray) + const transform = addressArray.split(", ").join("%20").split(" ").join("%20"); + + const GEO_CODE_URL = process.env.GEO_CODE_URL; + const GEOAPIFY_KEY = process.env.GEOAPIFY_KEY; + + let params = `?`; + + if (addressArray) params += `text=${transform}`; + + const api_key = `&apiKey=${GEOAPIFY_KEY}`; + + params += api_key; + + const url = GEO_CODE_URL + params; + + await fetch(url) + .then((response) => response.json()) + .then((result) => { + res.send(result); + }).catch(() => res.send({ message: "Went wrong" })); +}; diff --git a/server/controllers/getOsmController.js b/server/controllers/getOsmController.js new file mode 100644 index 0000000000..a8620e01d2 --- /dev/null +++ b/server/controllers/getOsmController.js @@ -0,0 +1,71 @@ +import fetch from 'node-fetch'; +import osmtogeojson from 'osmtogeojson'; + +export const getOsm = async (req, res) => { + console.log('req', req); // log the request body to see what it looks like + + const overpassUrl = process.env.OSM_URI; + + const activityTypeTags = { + hiking: '["highway"~"path|footway"]', + skiing: '["piste:type"~"downhill|nordic"]', + climbing: '["sport"="climbing"]', + cycling: '["highway"~"cycleway(:left|:right)?"]', + canoeing: '["waterway"~"riverbank|canal|stream"]', + horseback_riding: '["highway"="bridleway"]', + kayaking: '["waterway"~"riverbank|canal|stream|rapids|waterfall"]', + rock_climbing: '["natural"="cliff"]', + sailing: '["waterway"~"riverbank|canal|harbour|basin"]', + }; + + async function formatOverpassQuery(activityType, startPoint, endPoint) { + const tagString = activityTypeTags[activityType]; + const overpassQuery = `[out:json][timeout:25]; + ( + way${tagString}(${startPoint.latitude},${startPoint.longitude},${endPoint.latitude},${endPoint.longitude}); + ); + (._;>;); + out skel qt;`; + + return overpassQuery; + } + + try { + + const { activityType, startPoint, endPoint } = req.body; + + if (!activityType || !startPoint || !endPoint) { + throw new Error('Invalid request parameters'); + } + + const overpassQuery = await formatOverpassQuery(activityType, startPoint, endPoint); + + console.log('overpassQuery', overpassQuery) + + const response = await fetch(overpassUrl, { + method: 'POST', + body: overpassQuery, + headers: { 'Content-Type': 'text/plain' }, + }); + + console.log('response', response) + + + if (response.ok) { + const responseFormat = await response.json(); + const geojsonData = osmtogeojson(responseFormat); + res.send(geojsonData); + // res.send(response.text()); + } else { + console.log(response.status, response.statusText); + res.send({ message: 'Something went wrong' }); + } + } catch (error) { + console.error(error); + res.status(500).send({ message: 'Something went wrong' }); + } +}; + + + + diff --git a/server/controllers/getParksController.js b/server/controllers/getParksController.js new file mode 100644 index 0000000000..a655a31e77 --- /dev/null +++ b/server/controllers/getParksController.js @@ -0,0 +1,29 @@ +import fetch from "node-fetch"; +import { oneEntity } from "../utils/oneEntity.js"; + +export const getParks = async (req, res) => { + let abbrState = await oneEntity(req.query.abbrState) + + const X_RAPIDAPI_KEY = process.env.X_RAPIDAPI_KEY; + const NPS_API = process.env.NPS_API; + const PARKS_HOST = process.env.PARKS_HOST + + const host = `${PARKS_HOST}?stateCode=${abbrState}`; + + const options = { + method: "GET", + headers: { + "X-Api-Key": `${NPS_API}`, + "X-RapidAPI-Key": `${X_RAPIDAPI_KEY}`, + "X-RapidAPI-Host": "jonahtaylor-national-park-service-v1.p.rapidapi.com", + "User-Agent": "PackRat", + }, + }; + + await fetch(host, options) + .then((res) => res.json()) + .then((json) => { + res.send(json) + }).catch(() => res.send({ message: "Went wrong" })); +}; + diff --git a/server/controllers/getTrailController.js b/server/controllers/getTrailController.js new file mode 100644 index 0000000000..f52c5c8b49 --- /dev/null +++ b/server/controllers/getTrailController.js @@ -0,0 +1,53 @@ +import fetch from "node-fetch"; + +export const getTrails = async (req, res) => { + + let radiusParams = 25; + let activityParams = true; + let X_RAPIDAPI_KEY = process.env.X_RAPIDAPI_KEY + const { + administrative_area_level_1, + country, + locality, + latitude, + longitude + } = req.body; + let state = administrative_area_level_1; + let city = locality; + + + let paramsConditional = ""; + + const root = process.env.GET_TRAIL_ROOT_URL; + + if (latitude) paramsConditional += `lat=${latitude}`; + if (longitude) paramsConditional += `&lon=${longitude}`; + + if (city) paramsConditional += `&q-city_cont=${city.replace(/\s/g, "")}`; + + if (radiusParams) paramsConditional += `&radius=${radiusParams}`; + if (activityParams) + paramsConditional += `&q-activities_activity_type_name_eq=hiking`; + + const url1 = root + paramsConditional; + + const url = + "https://trailapi-trailapi.p.rapidapi.com/activity/?lat=34.1&lon=-105.2&q-city_cont=Denver&radius=25&q-activities_activity_type_name_eq=hiking"; + + const options = { + method: "GET", + headers: { + "X-RapidAPI-Key": `${X_RAPIDAPI_KEY}`, + "X-RapidAPI-Host": "trailapi-trailapi.p.rapidapi.com", + }, + }; + + await fetch(url1, options) + .then((res) => res.json()) + .then((json) => { + res.send(json) + }) + .catch((_err) => { + res.send({ message: "Went wrong" }); + }); +} \ No newline at end of file diff --git a/server/controllers/itemController.js b/server/controllers/itemController.js index 5fc333b77b..6eb9de4c62 100644 --- a/server/controllers/itemController.js +++ b/server/controllers/itemController.js @@ -1,8 +1,10 @@ import Item from "../models/itemModel.js"; +import { itemValidation } from "../utils/item.js"; +import { oneEntity } from "../utils/oneEntity.js" import Pack from "../models/packModel.js"; export const getItems = async (req, res) => { - const { packId } = req.params; + const { packId } = await oneEntity(req.params); try { const items = await Item.find({ packId }); @@ -14,7 +16,7 @@ export const getItems = async (req, res) => { }; export const getItemById = async (req, res) => { - const { _id } = req.body; + const { _id } = await oneEntity(req.body._id); try { const item = await Item.findById({ _id }); @@ -27,8 +29,7 @@ export const getItemById = async (req, res) => { export const addItem = async (req, res) => { try { - const newItem = await Item.create(req.body); - + const newItem = await itemValidation(req.body); await Pack.updateOne( { _id: req.body.packId }, { $push: { items: newItem._id } } @@ -40,7 +41,7 @@ export const addItem = async (req, res) => { }; export const editItem = async (req, res) => { - const { _id } = req.body; + const { _id } = await oneEntity(req.body._id); try { const newItem = await Item.findOneAndUpdate({ _id }, req.body, { @@ -54,7 +55,8 @@ export const editItem = async (req, res) => { }; export const deleteItem = async (req, res) => { - const { itemId } = req.body; + const { itemId } = await oneEntity(req.body.itemId); + try { await Item.findOneAndDelete({ _id: itemId }); diff --git a/server/controllers/packController.js b/server/controllers/packController.js index 0815400336..7cfe7049b3 100644 --- a/server/controllers/packController.js +++ b/server/controllers/packController.js @@ -1,5 +1,7 @@ import Pack from "../models/packModel.js"; import mongoose from "mongoose"; +import { oneEntity } from "../utils/oneEntity.js" +import { packValidation } from "../utils/pack.js" export const getPublicPacks = async (req, res) => { const { queryBy } = req.query; @@ -59,7 +61,7 @@ export const getPublicPacks = async (req, res) => { }; export const getPacks = async (req, res) => { - const { ownerId } = req.params; + const { ownerId } = await oneEntity(req.params); try { const aggr = await Pack.aggregate([ @@ -91,7 +93,7 @@ export const getPacks = async (req, res) => { }; export const getPackById = async (req, res) => { - const { _id } = req.body; + const { _id } = await oneEntity(req.body._id) try { const pack = await Pack.findById({ _id }).populate("total_weight"); @@ -125,8 +127,9 @@ export const getPackById = async (req, res) => { }; export const addPack = async (req, res) => { + const packBody = packValidation(req.body) const newPack = { - ...req.body, + ...packBody, items: [], is_public: false, favorited_by: [], @@ -149,7 +152,7 @@ export const addPack = async (req, res) => { }; export const editPack = async (req, res) => { - const { _id } = req.body; + const { _id } = await oneEntity(req.body._id) try { const newPack = await Pack.findOneAndUpdate({ _id }, req.body, { @@ -163,7 +166,8 @@ export const editPack = async (req, res) => { }; export const deletePack = async (req, res) => { - const { packId } = req.body; + const { packId } = await oneEntity(req.body.packId) + try { await Pack.findOneAndDelete({ _id: packId }); res.status(200).json({ msg: "pack was deleted successfully" }); diff --git a/server/controllers/tripController.js b/server/controllers/tripController.js index 6b0caae9a3..2cfcbbec74 100644 --- a/server/controllers/tripController.js +++ b/server/controllers/tripController.js @@ -1,7 +1,9 @@ import Trip from "../models/tripModel.js"; +import { oneEntity } from "../utils/oneEntity.js" +import { tripValidation } from "../utils/trip.js" export const getTrips = async (req, res) => { - const { owner_id } = req.body; + const { owner_id } = await oneEntity(req.body.owner_id) try { const trips = await Trip.find({ owner_id }).populate("packs"); @@ -13,7 +15,7 @@ export const getTrips = async (req, res) => { }; export const getTripById = async (req, res) => { - const { tripId } = req.body; + const { tripId } = await oneEntity(req.body.tripId) try { const trip = await Trip.findById({ _id: tripId }).populate("packs"); @@ -25,7 +27,7 @@ export const getTripById = async (req, res) => { }; export const addTrip = async (req, res) => { - let newDocument = req.body; + let newDocument = tripValidation(req.body); try { await Trip.create(newDocument); @@ -36,7 +38,7 @@ export const addTrip = async (req, res) => { }; export const editTrip = async (req, res) => { - const { _id } = req.body; + const { _id } = await oneEntity(req.body._id) try { const newTrip = await Trip.findOneAndUpdate({ _id }, req.body, { @@ -50,7 +52,8 @@ export const editTrip = async (req, res) => { }; export const deleteTrip = async (req, res) => { - const { tripId } = req.body; + const { tripId } = await oneEntity(req.body.tripId) + try { await Trip.findOneAndDelete({ _id: tripId }); res.status(200).json({ msg: "trip was deleted successfully" }); diff --git a/server/controllers/weatherController.js b/server/controllers/weatherController.js new file mode 100644 index 0000000000..ee6a243176 --- /dev/null +++ b/server/controllers/weatherController.js @@ -0,0 +1,54 @@ +import fetch from "node-fetch"; + +export const getWeatherWeek = async (req, res) => { + const root = process.env.WEATHER_WEEK_URL + const OPENWEATHER_KEY = process.env.OPENWEATHER_KEY + let params = `?`; + const latParams = req.query.lat; + const lonParams = req.query.lon; + const unitParams = "imperial"; + + const apiParams = true; + + if (latParams) params += `lat=${latParams}`; + if (lonParams) params += `&lon=${lonParams}`; + if (unitParams) params += `&units=${unitParams}`; + if (apiParams) params += `&appid=${OPENWEATHER_KEY}`; + + const url = root + params; + + await fetch(url) + .then((res) => res.json()) + .then((json) => { + res.send(json) + }).catch(() => { + res.send({ message: "Went wrong" }); + }); +}; + +export const getWeather = async (req, res) => { + + const root = process.env.WEATHER_URL + const OPENWEATHER_KEY = process.env.OPENWEATHER_KEY + + let params = `?`; + const latParams = req.query.lat; + const lonParams = req.query.lon; + const unitParams = "imperial"; + const apiParams = true; + + if (latParams) params += `lat=${latParams}`; + if (lonParams) params += `&lon=${lonParams}`; + if (unitParams) params += `&units=${unitParams}`; + if (apiParams) params += `&appid=${OPENWEATHER_KEY}`; + + const url = root + params; + + await fetch(url) + .then((res) => res.json()) + .then((json) => { + res.send(json) + }).catch(() => { + res.send({ message: "Went wrong" }); + }); +}; \ No newline at end of file diff --git a/server/env.example b/server/env.example index 3a1fc46347..f2e1c91386 100644 --- a/server/env.example +++ b/server/env.example @@ -1 +1,25 @@ -MONGODB_URI="your mongodb URI" \ No newline at end of file +MONGODB_URI="MONGO URI" + +------------------------------------- +Create .env file and put them on .env +------------------------------------- +OPENWEATHER_KEY='OpenWeather API key' +WEATHER_WEEK_URL=https://api.openweathermap.org/data/2.5/forecast +WEATHER_URL=https://api.openweathermap.org/data/2.5/weather + + +GEO_CODE_URL=https://api.geoapify.com/v1/geocode/search +GEOAPIFY_KEY ="Your API URI" + +X_RAPIDAPI_KEY = "Your API URI" +NPS_API = "Your API URI" + +PARKS_HOST=https://jonahtaylor-national-park-service-v1.p.rapidapi.com/parks +GET_TRAIL_ROOT_URL=https://trailapi-trailapi.p.rapidapi.com/trails/explore/? + +OSM_URI=https://overpass-api.de/api/interpreter + +APPPASSWORD="APP PASSWORD GENERATED FROM YOUR GOOGLE AUTH" +EMAIL="SENDER EMAIL ADRESS" + + diff --git a/server/index.js b/server/index.js index c8bd91ecb8..0eca1c46b9 100644 --- a/server/index.js +++ b/server/index.js @@ -17,12 +17,29 @@ import userRoutes from "./routes/userRoutes.js"; import packRoutes from "./routes/packRoutes.js"; import itemRoutes from "./routes/itemRoutes.js"; import tripRoutes from "./routes/tripRoutes.js"; +import weatherRoutes from "./routes/weatherRoutes.js"; +import geoCodeRoutes from "./routes/geoCodeRoutes.js"; +import getParkRoutes from "./routes/getParkRoutes.js"; +import getTrailRoutes from "./routes/getTrailRoutes.js"; +import osmRoutes from "./routes/osmRoutes.js"; + +const logger = (req, res, next) => { + console.log(`${req.method} ${req.path}`); + next(); +}; + +app.use(logger); // use routes app.use("/user", userRoutes); app.use("/pack", packRoutes); app.use("/item", itemRoutes); app.use("/trip", tripRoutes); +app.use("/weather", weatherRoutes); +app.use("/geocode", geoCodeRoutes); +app.use("/getparks", getParkRoutes); +app.use("/gettrails", getTrailRoutes); +app.use("/osm", osmRoutes); mongoose.connect(connectionString).then(() => console.log("connected")); diff --git a/server/models/userModel.js b/server/models/userModel.js index 35782e0ecd..4ab164cdb5 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -6,8 +6,11 @@ const { Schema } = mongoose; const UserSchema = new Schema({ name: { type: String, required: true }, - password: { type: String, required: true }, - email: { type: String, required: true }, + password: { type: String }, + email: { + type: String, required: true, lowercase: true + }, + code: { type: String }, trips: [{ type: Schema.Types.ObjectId, ref: Trip }], is_certified_guide: { type: Boolean }, favorites: [{ type: Schema.Types.ObjectId, ref: Pack }], diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/package.json b/server/package.json index 368fbcca7a..eb08c9b3f7 100644 --- a/server/package.json +++ b/server/package.json @@ -11,11 +11,18 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^1.3.4", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", + "i": "^0.3.7", "mongoose": "^7.0.1", - "nodemon": "^2.0.21" + "node-fetch": "^3.3.1", + "nodemailer": "^6.9.1", + "nodemailer-smtp-transport": "^2.7.4", + "nodemon": "^2.0.21", + "npm": "^9.6.3", + "osmtogeojson": "^3.0.0-beta.5" } } diff --git a/server/routes/geoCodeRoutes.js b/server/routes/geoCodeRoutes.js new file mode 100644 index 0000000000..f7a216dbe7 --- /dev/null +++ b/server/routes/geoCodeRoutes.js @@ -0,0 +1,8 @@ +import express from "express"; +import { getGeoCode } from "../controllers/geoCodeController.js"; + +const router = express.Router(); + +router.get("/", getGeoCode); + +export default router; \ No newline at end of file diff --git a/server/routes/getParkRoutes.js b/server/routes/getParkRoutes.js new file mode 100644 index 0000000000..8ca05501d2 --- /dev/null +++ b/server/routes/getParkRoutes.js @@ -0,0 +1,8 @@ +import express from "express"; +import { getParks } from "../controllers/getParksController.js"; + +const router = express.Router(); + +router.get("/", getParks); + +export default router; diff --git a/server/routes/getTrailRoutes.js b/server/routes/getTrailRoutes.js new file mode 100644 index 0000000000..42b0026697 --- /dev/null +++ b/server/routes/getTrailRoutes.js @@ -0,0 +1,8 @@ +import express from "express"; +import { getTrails } from "../controllers/getTrailController.js"; + +const router = express.Router(); + +router.post("/", getTrails); + +export default router; \ No newline at end of file diff --git a/server/routes/osmRoutes.js b/server/routes/osmRoutes.js new file mode 100644 index 0000000000..0f02b4755f --- /dev/null +++ b/server/routes/osmRoutes.js @@ -0,0 +1,11 @@ +import express from "express"; +import { getOsm } from "../controllers/getOsmController.js"; + +const router = express.Router(); + +router.use(express.text({ limit: '50mb' })); // use text middleware to parse plain text request body + +router.post("/", getOsm); + +export default router; + diff --git a/server/routes/userRoutes.js b/server/routes/userRoutes.js index 4ee43740fb..ae2547ff62 100644 --- a/server/routes/userRoutes.js +++ b/server/routes/userRoutes.js @@ -9,6 +9,8 @@ import { addToFavorite, } from "../controllers/userController.js"; +import { checkCode, emailExists, updatePassword } from '../controllers/auth.js' + const router = express.Router(); router.get("/", getUsers); @@ -19,4 +21,8 @@ router.post("/favorite", addToFavorite); router.put("/", editUser); router.delete("/", deleteUser); +router.post("/checkcode", checkCode); +router.post("/updatepassword", updatePassword); +router.post("/emailexists", emailExists); + export default router; diff --git a/server/routes/weatherRoutes.js b/server/routes/weatherRoutes.js new file mode 100644 index 0000000000..5ad8b629ee --- /dev/null +++ b/server/routes/weatherRoutes.js @@ -0,0 +1,9 @@ +import express from "express"; +import { getWeatherWeek, getWeather } from "../controllers/weatherController.js"; + +const router = express.Router(); + +router.get("/", getWeather); +router.get("/week", getWeatherWeek); + +export default router; diff --git a/server/utils/item.js b/server/utils/item.js new file mode 100644 index 0000000000..7dc91651e3 --- /dev/null +++ b/server/utils/item.js @@ -0,0 +1,18 @@ +import Item from "../models/itemModel.js"; + +export const itemValidation = async ({ name, weight, quantity, unit, packId }) => { + + if (!name || !weight || !quantity || !unit || !packId) { + throw new Error("All fields must be filled"); + } + + const item = await Item.create({ + name, + weight, + quantity, + unit, + packId + }); + + return item; +}; \ No newline at end of file diff --git a/server/utils/loginUser.js b/server/utils/loginUser.js index 31a6d6e9b9..1a9f2a76fa 100644 --- a/server/utils/loginUser.js +++ b/server/utils/loginUser.js @@ -1,13 +1,32 @@ import User from "../models/userModel.js"; -export const loginUser = async ({ email, password }) => { - if (!email || !password) { - throw new Error("All fields must be filled"); +export const loginUser = async ({ email, password, from }) => { + let user = [] + if (from === "UserSignIn") { + user = await User.find({ + $and: [ + { "email": email.toLowerCase() }, + { "password": password } + ] + }).select("-password"); + + if (!email || !password) { + throw new Error("All fields must be filled"); + } } - const user = await User.findOne({ email }).select("-password"); + if (from === "GoogleSignIn") { + user = await User.findOne({ "email": email.toLowerCase() }).select("-password"); + if (!email) { + throw new Error("All fields must be filled"); + } + } + //Out of two signin methods. + if (!(from === "GoogleSignIn" || from === "UserSignIn")) { + throw new Error("Something went wrong"); + } - if (!user) { + if (user.length == 0) { throw new Error("Wrong email"); } diff --git a/server/utils/oneEntity.js b/server/utils/oneEntity.js new file mode 100644 index 0000000000..8ba84e4b95 --- /dev/null +++ b/server/utils/oneEntity.js @@ -0,0 +1,6 @@ +export const oneEntity = async (val) => { + if (!val) { + throw new Error("Required"); + } + return val; +}; \ No newline at end of file diff --git a/server/utils/pack.js b/server/utils/pack.js new file mode 100644 index 0000000000..83e5c61eff --- /dev/null +++ b/server/utils/pack.js @@ -0,0 +1,20 @@ +import Pack from "../models/packModel.js"; + +export const packValidation = async ({ name, items, owner_id, is_public, favorited_by, favorites_count, createdAt }) => { + + if (!name || !owner_id) { + throw new Error("All fields must be filled"); + } + + const pack = await Pack.create({ + name, + items, + owner_id, + is_public, + favorited_by, + favorites_count, + createdAt + }) + + return pack; +}; \ No newline at end of file diff --git a/server/utils/registerUser.js b/server/utils/registerUser.js index 691577061f..095d81b7aa 100644 --- a/server/utils/registerUser.js +++ b/server/utils/registerUser.js @@ -1,8 +1,21 @@ import User from "../models/userModel.js"; -export const register = async ({ email, password, name }) => { - if (!email || !password || !name) { - throw new Error("All fields must be filled"); +export const register = async ({ email, password, name, from }) => { + + if (from === "UserSignIn") { + if (!email || !password || !name) { + throw new Error("All fields must be filled"); + } + } + + if (from === "GoogleSignIn") { + if (!email || !name) { + throw new Error("All fields must be filled"); + } + } + + if (!(from === "GoogleSignIn" || from === "UserSignIn")) { + throw new Error("Something went wrong"); } const exist = await User?.findOne({ email }); diff --git a/server/utils/trip.js b/server/utils/trip.js new file mode 100644 index 0000000000..60a0f41ade --- /dev/null +++ b/server/utils/trip.js @@ -0,0 +1,23 @@ +import Trip from "../models/tripModel.js"; + +export const tripValidation = + async ({ name, duration, weather, start_date, end_date, destination, owner_id, packs, is_public }) => { + + if (!name || !duration || !weather || !start_date || !end_date || !destination || !owner_id || !packs || !is_public) { + throw new Error("All fields must be filled"); + } + + const trip = await Trip.create({ + name, + duration, + weather, + start_date, + end_date, + destination, + owner_id, + packs, + is_public + }) + + return trip + } \ No newline at end of file