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..620d2d5d35 100644 --- a/client/_layout.js +++ b/client/_layout.js @@ -8,7 +8,7 @@ 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 { 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/auth/firebase.js b/client/auth/firebase.js new file mode 100644 index 0000000000..7c02b2c4a8 --- /dev/null +++ b/client/auth/firebase.js @@ -0,0 +1,32 @@ +import { initializeApp } from "firebase/app"; +import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; +import { FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID, FIREBASE_STORAGE_BUCKET, FIREBASE_MESSAGING_SENDER_ID, FIREBASE_APP_ID, FIREBASE_MEASUREMENT_ID } from "@env" + +const firebaseConfig = { + apiKey: FIREBASE_API_KEY, + authDomain: FIREBASE_AUTH_DOMAIN, + projectId: FIREBASE_PROJECT_ID, + storageBucket: FIREBASE_STORAGE_BUCKET, + messagingSenderId: FIREBASE_MESSAGING_SENDER_ID, + appId: FIREBASE_APP_ID, + measurementId: FIREBASE_MEASUREMENT_ID +}; + +export 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) => { + console.log({ name: result.user.displayName, email: result.user.email }) + resolve({ name: result.user.displayName, email: result.user.email }) + }) + .catch(() => { + resolve({ message: "error" }) + }); + + }) +}; 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 [searchString, setSearchString] = useState("Virginia US"); - const [geoCode, setGeoCode] = useState(); + const [geoCode, setGeoCode] = useState({}); 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; + // const lat = geoCode?.features[0]?.geometry?.coordinates[1]; + // const lon = geoCode?.features[0]?.geometry?.coordinates[0]; + // const state = geoCode?.features[0]?.properties?.state; useEffect(() => { const getCode = async () => { setIsLoadingMobile(true); const code = await getGeoCode(searchString); + console.log("code:", code); setIsLoadingMobile(false); setGeoCode(code); }; @@ -67,19 +67,25 @@ export const SearchInput = () => { const weeekArray = await getWeatherWeek(lat, lon); dispatch(addWeek(weeekArray)); }; - - if (lat && lon) { - getWeatherObject(); - getWeek(); + + if (geoCode?.features) { + const lat = geoCode.features[0]?.geometry?.coordinates?.[1]; + const lon = geoCode.features[0]?.geometry?.coordinates?.[0]; + const state = geoCode.features[0]?.properties?.state; + + if (lat && lon) { + getWeatherObject(); + getWeek(); + } } - }, [lat, lon, state]); + }, [geoCode, dispatch]); return Platform.OS === "web" ? ( { - console.log("StyleURL:", Mapbox?.StyleURL); - }, []); + if (mapViewLoaded) { + handleShapeSourceLoad(); + } + }, [mapViewLoaded]); const [lng, setLng] = useState(103.8519599); const [lat, setLat] = useState(1.29027); @@ -39,6 +42,7 @@ export function CustomizedMap() { function handleMapViewLayout() { setMapViewLoaded(true); + } const handleStyleChange = (value) => { @@ -106,22 +110,26 @@ export function CustomizedMap() { const bounds = getShapeSourceBounds(shape); - mapViewRef.current.fitBounds(bounds, { + mapViewRef?.current?.fitBounds(bounds, { edgePadding: { - top: 5, - right: 5, - bottom: 5, - left: 5 + top: 100, + right: 100, + bottom: 100, + left: 100 } + }, () => { + const centerLng = (bounds[0][0] + bounds[1][0]) / 2; + const centerLat = (bounds[0][1] + bounds[1][1]) / 2; + + mapViewRef.current.setCamera({ + centerCoordinate: [centerLng, centerLat], + minZoomLevel: 10, + }); }); - mapViewRef.current.setCamera({ - centerCoordinate: mapViewRef.current.getCenter(), - zoomLevel: Math.min( - mapViewRef.current.zoomLevel, - mapViewRef.current.getZoomForBounds(bounds, { padding: 50 }) - ) - }); + console.log('shape:', shape); + console.log('bounds:', bounds); + console.log('mapViewRef:', mapViewRef); } @@ -174,6 +182,7 @@ export function CustomizedMap() { > @@ -241,7 +250,7 @@ export function MapContainer() { return ( Map - Basic - + {/* */} Map - Customized diff --git a/client/env.example b/client/env.example index 18598ad271..40f8e43d29 100644 --- a/client/env.example +++ b/client/env.example @@ -4,10 +4,12 @@ ENVIRONMENT='development' API_URL='your api url' NPS_API = "your api key" GEOAPIFY_KEY = "your api key" -OPENWEATHER_KEY = "your api key" GOOGLE_PLACES_API_KEY = 'your api key' MAPBOX_API_KEY = "your api key, starts with pk..." X_RAPIDAPI_KEY = "your api key" MAPBOX_ACCESS_TOKEN = "your api key, starts with pk..." -MAPBOX_DOWNLOADS_TOKEN="your api key, starts with sk..." \ No newline at end of file +MAPBOX_DOWNLOADS_TOKEN="your api key, starts with sk..." + +WEB_CLIENT_ID="962352557394-..................apps.googleusercontent.com" +ANDROID_CLIENT_ID="962352557394-........................apps.googleusercontent.com" diff --git a/client/hooks/useLogin.js b/client/hooks/useLogin.js index e17af3a2d3..df27356e96 100644 --- a/client/hooks/useLogin.js +++ b/client/hooks/useLogin.js @@ -3,15 +3,31 @@ import { api } from "../constants/api"; import { useMutation } from "@tanstack/react-query"; import { queryClient } from "../constants/queryClient"; import { useAuth } from "../auth/provider"; +import { app, auth } from "../auth/firebase"; +import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; const loginUser = async (user) => { - return await fetcher(`${api}/user/login`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(user), - }); + try { + const { email, password } = user; + signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + // ... + const token = user.getIdToken(); + + return user; + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + }); + + + } catch (error) { + console.log(error); + } + }; export default function useLogin() { @@ -22,11 +38,10 @@ export default function useLogin() { return loginUser(user); }, onSuccess: (data, variables, context) => { - // Invalidate and refetch - signIn(data.user); + signIn(data); queryClient.invalidateQueries({ queryKey: ["user"] }); }, }); return { loginUser: mutation }; -} +} \ No newline at end of file diff --git a/client/hooks/useRegister.js b/client/hooks/useRegister.js index 3790479ed3..892c363725 100644 --- a/client/hooks/useRegister.js +++ b/client/hooks/useRegister.js @@ -4,13 +4,18 @@ import { useMutation } from "@tanstack/react-query"; import { queryClient } from "../constants/queryClient"; const addUser = async (newUser) => { - return await fetcher(`${api}/user/`, { + const response = await fetch(`${api}/user/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(newUser), }); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error.message); + } + return data; }; export default function useRegister() { @@ -18,7 +23,7 @@ export default function useRegister() { mutationFn: async (newUser) => { return addUser(newUser); }, - onSuccess: () => { + onSuccess: (data, variables, context) => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ["user"] }); }, diff --git a/client/ios/packratfront.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/client/ios/packratfront.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/client/ios/packratfront.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/client/package.json b/client/package.json index c502eb260f..b7253d5b17 100644 --- a/client/package.json +++ b/client/package.json @@ -26,12 +26,16 @@ "date-fns": "^2.29.3", "dotenv": "^16.0.3", "expo": "~48.0.7", + "expo-application": "~5.1.1", "expo-checkbox": "~2.3.1", "expo-constants": "~14.2.1", + "expo-crypto": "~12.2.1", "expo-router": "^1.2.2", "expo-splash-screen": "~0.18.1", "expo-status-bar": "~1.4.4", "mapbox-gl": "^2.13.0", + "expo-web-browser": "~12.1.1", + "firebase": "^9.19.1", "native-base": "^3.4.28", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/client/screens/LoginScreen.js b/client/screens/LoginScreen.js index 28df7e99c8..58980cac27 100644 --- a/client/screens/LoginScreen.js +++ b/client/screens/LoginScreen.js @@ -11,12 +11,21 @@ import { NativeBaseProvider, } from "native-base"; -import { useState } from "react"; + +import { FontAwesome } from "@expo/vector-icons"; + +import * as WebBrowser from 'expo-web-browser'; +import * as Google from 'expo-auth-session/providers/google'; +import { useState, useEffect } from "react"; import useLogin from "../hooks/useLogin"; import { useAuth } from "../auth/provider"; import { Link } from "expo-router"; import { useRouter } from "expo-router"; import { theme } from "../theme"; +import { signInWithGoogle } from "../auth/firebase"; + + + export default function Login() { const [email, setEmail] = useState(""); @@ -68,8 +77,9 @@ export default function Login() { + + {/* Google Login */} {loginUser.isSuccess && router.push("/")} diff --git a/client/screens/RegisterScreen.js b/client/screens/RegisterScreen.js index 8198aba3ce..2523bb3882 100644 --- a/client/screens/RegisterScreen.js +++ b/client/screens/RegisterScreen.js @@ -8,11 +8,18 @@ import { Center, HStack, Text, + View } from "native-base"; -import { useState } from "react"; + +import { FontAwesome } from '@expo/vector-icons'; +import * as WebBrowser from 'expo-web-browser'; +import * as Google from 'expo-auth-session/providers/google'; + +import { useState, useEffect } from "react"; import useRegister from "../hooks/useRegister"; import { useRouter } from "expo-router"; import { Link } from "expo-router"; +import { signInWithGoogle } from "../auth/firebase"; export default function Register() { const [name, setName] = useState(""); @@ -64,7 +71,7 @@ export default function Register() { /> + + {/* Google register */} {addUser.isSuccess && router.push("/sign-in")} diff --git a/server/config.js b/server/config.js index a58231c85b..b11ce334bb 100644 --- a/server/config.js +++ b/server/config.js @@ -4,7 +4,3 @@ import { config } from 'dotenv'; config(); export const MONGODB_URI = process.env.MONGODB_URI -// module.exports = { -// MONGODB_URI: process.env.MONGODB_URI - -// } \ No newline at end of file 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/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/userController.js b/server/controllers/userController.js index 6a602c191b..5772fd89c2 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -3,6 +3,19 @@ import { register } from "../utils/registerUser.js"; import { loginUser } from "../utils/loginUser.js"; import Pack from "../models/packModel.js"; import { ObjectId } from "mongoose"; +import firebase from "firebase-admin"; + +// Middleware to check if user is authenticated +export const isAuthenticated = async (req, res, next) => { + const token = req.headers.authorization.split(" ")[1]; + try { + const decodedToken = await firebase.auth().verifyIdToken(token); + req.userData = decodedToken; + next(); + } catch (error) { + res.status(401).json({ error: "Unauthorized" }); + } +}; export const getUsers = async (req, res) => { try { @@ -28,7 +41,11 @@ export const getUserById = async (req, res) => { export const addUser = async (req, res) => { try { - const user = await register(req.body); + const { email, password } = req.body; + const userRecord = await firebase.auth().createUser({ + email: email, + password: password, + }); res.status(200).json({ message: "Successfully signed up" }); } catch (error) { @@ -38,11 +55,18 @@ export const addUser = async (req, res) => { export const login = async (req, res) => { try { - const user = await loginUser(req.body); + const { email, password } = req.body; + + const userRecord = await firebase.auth().getUserByEmail(email); + const uid = userRecord.uid; + + await firebase.auth().signInWithEmailAndPassword(email, password); + + res.status(200).json({ - message: "Successfully loged in", - user, + message: "Successfully logged in", + user: userRecord, }); } catch (error) { res.status(400).json({ error: error.message }); 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..5684d9eaf7 100644 --- a/server/env.example +++ b/server/env.example @@ -1 +1,18 @@ -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/? diff --git a/server/index.js b/server/index.js index c8bd91ecb8..798c574285 100644 --- a/server/index.js +++ b/server/index.js @@ -3,6 +3,14 @@ import mongoose from "mongoose"; import cors from "cors"; import { MONGODB_URI } from "./config.js"; +import firebase from "firebase-admin"; +import serviceAccountKey from "./serviceAccountKey.json" assert { type: "json" }; + +// Initialize Firebase +firebase.initializeApp({ + credential: firebase.credential.cert(serviceAccountKey) +}); + // express items const app = express(); app.use(cors()); @@ -17,12 +25,20 @@ 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"; // 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); mongoose.connect(connectionString).then(() => console.log("connected")); diff --git a/server/models/userModel.js b/server/models/userModel.js index 35782e0ecd..6785c8c6ce 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -6,7 +6,7 @@ const { Schema } = mongoose; const UserSchema = new Schema({ name: { type: String, required: true }, - password: { type: String, required: true }, + password: { type: String }, email: { type: String, required: true }, trips: [{ type: Schema.Types.ObjectId, ref: Trip }], is_certified_guide: { type: Boolean }, diff --git a/server/package.json b/server/package.json index 368fbcca7a..7541b50579 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,12 @@ "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", + "firebase": "^9.19.1", + "firebase-admin": "^11.5.0", + "i": "^0.3.7", "mongoose": "^7.0.1", - "nodemon": "^2.0.21" + "node-fetch": "^3.3.1", + "nodemon": "^2.0.21", + "npm": "^9.6.3" } } 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/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/serviceAccountKey.example.json b/server/serviceAccountKey.example.json new file mode 100644 index 0000000000..078aa16fdd --- /dev/null +++ b/server/serviceAccountKey.example.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "your-project-id", + "private_key_id": "your-private-key-id", + "private_key": "-----BEGIN PRIVATE KEY-----\nYOUR-PRIVATE-KEY-HERE\n-----END PRIVATE KEY-----\n", + "client_email": "your-client-email@your-project-id.iam.gserviceaccount.com", + "client_id": "your-client-id", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-client-email%40your-project-id.iam.gserviceaccount.com" +} \ No newline at end of file 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..fbe056850b 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 }, + { "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 }).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..e9c30c7e8c --- /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 || !items || !owner_id || !is_public || !favorites_count || !createdAt) { + 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