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() {
{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() {
/>
addUser.mutate({ name, email, password })}
+ onPress={() => addUser.mutate({ name, email, password, from: "UserSignIn" })}
mt="2"
colorScheme="indigo"
disabled={!email || !password || !name}
@@ -93,6 +100,48 @@ export default function Register() {
+ {/* Google register */}
+
+
+ Or
+
+
+
+ {
+ promptAsync();
+ }}
+ colorScheme={"red"}
+ startIcon={
+
+ }
+ signInWithGoogle().then(async (res) => {
+ let { email, name } = res
+ if (email && name) {
+ addUser.mutate({ name, email, password: "", from: "GoogleSignIn" });
+ router.push("/sign-in")
+ } else {
+ console.log("Email and Name empty")
+ }
+ }).catch((err) => {
+ console.log(err)
+ })
+ }} mt="2"
+ colorScheme="red"
+ >
+ Sign up with Google
+
+
+ {/* 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