diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 178bc0540..3dc4d3c52 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,6 +61,7 @@ jobs: - name: Check for private.json and .env files run: | if [ -e ".env" ] || [ -e "server/firebase-secret.json" ]; then + echo "Error: Found .env or firebase-secret.json in the pull request. Please remove them before merging."; exit 1; fi diff --git a/client/assets/apple_logo.png b/client/assets/apple_logo.png new file mode 100644 index 000000000..b74350d6a Binary files /dev/null and b/client/assets/apple_logo.png differ diff --git a/client/assets/facebook_logo.png b/client/assets/facebook_logo.png new file mode 100644 index 000000000..03056266f Binary files /dev/null and b/client/assets/facebook_logo.png differ diff --git a/client/assets/fonts/Quicksand-Bold.ttf b/client/assets/fonts/Quicksand-Bold.ttf new file mode 100644 index 000000000..07d5127c0 Binary files /dev/null and b/client/assets/fonts/Quicksand-Bold.ttf differ diff --git a/client/assets/fonts/Quicksand-Medium.ttf b/client/assets/fonts/Quicksand-Medium.ttf new file mode 100644 index 000000000..f4634cd7c Binary files /dev/null and b/client/assets/fonts/Quicksand-Medium.ttf differ diff --git a/client/assets/github_logo.png b/client/assets/github_logo.png new file mode 100644 index 000000000..6cb3b705d Binary files /dev/null and b/client/assets/github_logo.png differ diff --git a/client/assets/google_logo.png b/client/assets/google_logo.png new file mode 100644 index 000000000..a4a9918da Binary files /dev/null and b/client/assets/google_logo.png differ diff --git a/client/src/components/Auth/LoginScreen.tsx b/client/src/components/Auth/LoginScreen.tsx index d6bb18018..899a73364 100644 --- a/client/src/components/Auth/LoginScreen.tsx +++ b/client/src/components/Auth/LoginScreen.tsx @@ -9,26 +9,38 @@ import { Platform, TouchableWithoutFeedback, Keyboard, + TouchableOpacity, } from "react-native"; import { LogInEmailInput, LogInPasswordInput } from "../Common/CustomInputs"; -import { LogInButton } from "../Common/AuthButtons"; +import { LogInButton, ExternalLoginButton } from "../Common/AuthButtons"; import { useLocalSearchParams, useRouter } from "expo-router"; import { appSignIn } from "../../services/store"; -import { AuthenticationErrorMessage, AuthenticationResponse } from "./AuthenticationResponse"; +import { + AuthenticationErrorMessage, + AuthenticationResponse, +} from "./AuthenticationResponse"; +import { ArrowLeftCircle } from "react-native-feather"; const LoginScreen = () => { const router = useRouter(); const [fontsLoaded, fontError] = useFonts({ - "Gilroy-ExtraBold": require("../../../assets/fonts/Gilroy-ExtraBold.otf"), - "Gilroy-Light": require("../../../assets/fonts/Gilroy-Light.otf"), + "Quicksand-Bold": require("../../../assets/fonts/Quicksand-Bold.ttf"), + "Quicksand-Medium": require("../../../assets/fonts/Quicksand-Medium.ttf"), }); const { inputEmail } = useLocalSearchParams(); const [email, setEmail] = React.useState(""); const [password, setPassword] = React.useState(""); - const [authResponse, setAuthResponse] = React.useState(); + const [authResponse, setAuthResponse] = + React.useState(); const [invalidLogin, invalidateLogin] = React.useState(false); // Possbily change this? + const externalLoginIcons = { + google: require("../../../assets/google_logo.png"), + facebook: require("../../../assets/facebook_logo.png"), + apple: require("../../../assets/apple_logo.png"), + }; + // Sign in function with email and password const onHandleSubmit = async () => { Keyboard.dismiss(); @@ -43,7 +55,7 @@ const LoginScreen = () => { console.log(authResponse.error); invalidateLogin(true); } - }, [authResponse]) + }, [authResponse]); useEffect(() => { setEmail(inputEmail?.toString() || ""); // On load of the page, set the email to the inputEmail if they entered it! @@ -53,41 +65,117 @@ const LoginScreen = () => { return null; } + const handleGoogleSignIn = async () => { + console.log("Google Sign In"); + }; + + const handleFacebookSignIn = async () => { + console.log("Facebook Sign In"); + }; + + const handleAppleSignIn = async () => { + console.log("Apple Sign In"); + }; + + const handleGithubSignIn = async () => { + console.log("Github Sign In"); + }; + return ( - - - - Welcome back! - - - setEmail(text)} - invalid={invalidLogin} - /> - setPassword(text)} - invalid={invalidLogin} - /> - - - + + router.back()} + style={styles.back_button} + > + + + + Welcome back! + How have you been? + + + setEmail(text)} + invalid={invalidLogin} + /> + setPassword(text)} + invalid={invalidLogin} + /> + + + + + + + Forgot password? + + + + + + + Or Login With + + - - - { - setAuthResponse(undefined) - invalidateLogin(false) - }} /> + + + + + + + + Don't have an account? + router.replace({ pathname: "/signup" })} + > + + Sign up + + + + + { + setAuthResponse(undefined); + invalidateLogin(false); + }} + /> + @@ -102,8 +190,12 @@ const styles = StyleSheet.create({ display: "flex", height: "100%", width: "100%", - justifyContent: "center", + justifyContent: "flex-start", alignItems: "center", + paddingHorizontal: Dimensions.get("window").width * 0.11, + paddingVertical: Dimensions.get("window").height * 0.01, + backgroundColor: "white", + gap: Dimensions.get("window").height * 0.029, }, //This is an example of where the error message could be @@ -112,7 +204,7 @@ const styles = StyleSheet.create({ justifyContent: "space-around", alignItems: "center", width: "100%", - bottom: Dimensions.get("window").height * 0.10, + top: Dimensions.get("window").height * 0.1, position: "absolute", }, @@ -134,13 +226,60 @@ const styles = StyleSheet.create({ header_container: { display: "flex", justifyContent: "center", - alignItems: "center", + alignItems: "flex-start", width: "100%", + marginBottom: Dimensions.get("window").height * 0.019, + marginTop: Dimensions.get("window").height * 0.23, }, header_text: { - fontFamily: "Gilroy-ExtraBold", - fontSize: 30, + fontFamily: "Quicksand-Bold", + fontSize: 37, + marginBottom: Dimensions.get("window").height * 0.01, + }, + subheader_text: { + fontFamily: "Quicksand-Medium", + fontSize: 20, + }, + regular_text: { + fontFamily: "Quicksand-Medium", + color: "#8E8E8E", + }, + horizontal_line: { + flex: 1, + height: 1, + backgroundColor: "#8E8E8E", + }, + externalLinkContainer: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + width: "100%", + }, + footer_text: { + fontFamily: "Quicksand-Bold", + color: "black", + fontSize: 15, + }, + footer_text_container: { + display: "flex", + flexDirection: "row", + justifyContent: "center", + alignItems: "flex-end", + gap: 7, + flex: 1, + paddingBottom: Dimensions.get("window").height * 0.01, + }, + divider_container: { + flexDirection: "row", + alignItems: "center", + marginVertical: Dimensions.get("window").height * 0.011, + }, + back_button: { + position: "absolute", + top: Dimensions.get("window").height * 0.075, + left: Dimensions.get("window").width * 0.075, }, }); diff --git a/client/src/components/Common/AuthButtons.tsx b/client/src/components/Common/AuthButtons.tsx index ef940944a..973611948 100644 --- a/client/src/components/Common/AuthButtons.tsx +++ b/client/src/components/Common/AuthButtons.tsx @@ -1,5 +1,15 @@ import React, { useState } from "react"; -import { StyleSheet, Text, TouchableOpacity, Dimensions, Alert } from "react-native"; +import { + StyleSheet, + Text, + TouchableOpacity, + Dimensions, + Alert, + Image, + View, + ImageSourcePropType, + Platform, +} from "react-native"; import { router, useRouter } from "expo-router"; import { useFonts } from "expo-font"; import { appSignOut } from "../../services/store"; @@ -35,7 +45,10 @@ export const SignOutButton: React.FC = () => { router.replace("/"); } else if (response?.error) { console.log(response.error); - Alert.alert("Sign Out Failed", "An error occurred during sign out. Please try again."); + Alert.alert( + "Sign Out Failed", + "An error occurred during sign out. Please try again." + ); } }, }, @@ -49,16 +62,22 @@ export const SignOutButton: React.FC = () => { } return ( - - Sign Out + + Sign Out ); }; -export const LogInButton: React.FC<{ onPress?: () => void }> = ({ onPress }) => { +export const LogInButton: React.FC<{ onPress?: () => void }> = ({ + onPress, +}) => { const [fontsLoaded, fontError] = useFonts({ - 'Gilroy-ExtraBold': require('../../../assets/fonts/Gilroy-ExtraBold.otf'), - 'Gilroy-Light': require('../../../assets/fonts/Gilroy-Light.otf'), + "Gilroy-ExtraBold": require("../../../assets/fonts/Gilroy-ExtraBold.otf"), + "Gilroy-Light": require("../../../assets/fonts/Gilroy-Light.otf"), }); if (!fontsLoaded && !fontError) { @@ -66,27 +85,63 @@ export const LogInButton: React.FC<{ onPress?: () => void }> = ({ onPress }) => } return ( - + Login ); }; +export const ExternalLoginButton: React.FC<{ + onPress?: () => void; + companyName: string; + imageSource?: ImageSourcePropType; +}> = ({ onPress, companyName, imageSource }) => { + const images: { [key: string]: ImageSourcePropType } = { + google: require("../../../assets/google_logo.png"), + apple: require("../../../assets/apple_logo.png"), + github: require("../../../assets/github_logo.png"), + facebook: require("../../../assets/facebook_logo.png"), + }; + + const clicked = () => { + onPress && onPress(); + }; + + return ( + + + + ); +}; + const styles = StyleSheet.create({ - button: { + login_button: { backgroundColor: "#5dbea3", width: Dimensions.get("window").width * 0.5, height: Dimensions.get("window").height * 0.05, display: "flex", justifyContent: "center", alignItems: "center", - borderRadius: 10, + borderRadius: Dimensions.get("window").height / 2, + shadowColor: "#8E8E8E", + shadowRadius: 2, + shadowOpacity: 0.7, + shadowOffset: { + width: 0, + height: 2, + }, + elevation: 2, }, button_text: { color: "white", - fontFamily: "Gilroy-ExtraBold", - fontSize: Dimensions.get("window").height * 0.03, + fontFamily: "Quicksand-Medium", + fontSize: Dimensions.get("window").height * 0.027, }, sign_out_button: { @@ -99,4 +154,29 @@ const styles = StyleSheet.create({ borderRadius: 8, padding: 5, }, + external_login_button: { + display: "flex", + justifyContent: "center", + alignItems: "center", + backgroundColor: "white", + height: Dimensions.get("window").height * 0.068, + width: Dimensions.get("window").height * 0.068, + borderRadius: 10, + padding: 10, + shadowColor: "#8E8E8E", + shadowRadius: 2, + shadowOpacity: 0.7, + shadowOffset: { + width: 0, + height: 2, + }, + elevation: 2, + borderWidth: 1, + borderColor: "#8E8E8E", + }, + login_logo: { + resizeMode: "contain", + height: "100%", + width: "100%", + }, }); diff --git a/client/src/components/Common/CustomInputs.tsx b/client/src/components/Common/CustomInputs.tsx index 71871be41..336530378 100644 --- a/client/src/components/Common/CustomInputs.tsx +++ b/client/src/components/Common/CustomInputs.tsx @@ -1,148 +1,184 @@ -import React from 'react' -import { TextInput, View, StyleSheet, Dimensions, Platform } from 'react-native' -import { ChatSendButton } from './CustomButtons' +import React from "react"; +import { + TextInput, + View, + StyleSheet, + Dimensions, + Platform, +} from "react-native"; +import { useFonts } from "expo-font"; +import { ChatSendButton } from "./CustomButtons"; interface ChatInputProps { - value?: string, - onChangeText?: (text: string) => void - invalid?: boolean, - onSend?: () => void, + value?: string; + onChangeText?: (text: string) => void; + invalid?: boolean; + onSend?: () => void; } -export const WelcomeEmailInput: React.FC = ({ value, onChangeText }) => { - return ( - - ) -} +export const WelcomeEmailInput: React.FC = ({ + value, + onChangeText, +}) => { + return ( + + ); +}; // Maybe will put LogInEmailInput & LogInPasswordInput two together into a single component -export const LogInEmailInput: React.FC = ({ value, onChangeText, invalid }) => { - return ( - - ) -} +export const LogInEmailInput: React.FC = ({ + value, + onChangeText, + invalid, +}) => { + return ( + + ); +}; -export const LogInPasswordInput: React.FC = ({ value, onChangeText, invalid }) => { - return ( - - ) -} +export const LogInPasswordInput: React.FC = ({ + value, + onChangeText, + invalid, +}) => { + return ( + + ); +}; // sign up screen inputs -export const SignUpEmailInput: React.FC = ({ value, onChangeText }) => { - return ( - - ) -} +export const SignUpEmailInput: React.FC = ({ + value, + onChangeText, +}) => { + return ( + + ); +}; -export const SignUpPasswordInput: React.FC = ({ value, onChangeText}) => { - return ( - - ) -} +export const SignUpPasswordInput: React.FC = ({ + value, + onChangeText, +}) => { + return ( + + ); +}; + +export const SignUpConfirmPasswordInput: React.FC = ({ + value, + onChangeText, +}) => { + return ( + + ); +}; -export const SignUpConfirmPasswordInput: React.FC = ({ value, onChangeText}) => { - return ( - = ({ + value, + onChangeText, + onSend, +}) => { + return ( + + - ) -} - -export const ChatInput: React.FC = ({ value, onChangeText, onSend }) => { - + maxLength={500} + style={styles.messageInput} + /> - - return ( - - - - - - - ) + + + ); }; const styles = StyleSheet.create({ - - welcomeEmailInput: { - height: '100%', - borderWidth: 1, - borderRadius: 30, - paddingLeft: 15, - paddingRight: 15, - flex: 1, - }, - - loginInput: { - width: Dimensions.get('window').width * 0.75, - minHeight: Dimensions.get('window').height * 0.055, - borderWidth: 1, - borderRadius: 30, - paddingTop: Platform.OS === 'ios' ? 15 : 0, - paddingBottom: Platform.OS === 'ios' ? 15 : 0, - paddingLeft: 15, - paddingRight: 15, - }, - messsageContainer: { - width: Dimensions.get('window').width * 0.75, - borderWidth: 1, - borderRadius: 30, - paddingTop: Dimensions.get('window').height * 0.006, - paddingBottom: Dimensions.get('window').height * 0.006, - paddingLeft: 15, - paddingRight: Dimensions.get('window').height * 0.006, - flexDirection: 'row', - flex: 1, - justifyContent: 'space-between', - alignItems: 'center', - borderColor: "#8E8E8E" - }, - messageInput: { - width: Dimensions.get('window').height * 0.35, - fontSize: 16, - }, - invalidLoginInput: { - borderColor: 'red', - }, - -}); \ No newline at end of file + welcomeEmailInput: { + height: "100%", + borderWidth: 1, + borderRadius: 30, + paddingLeft: 15, + paddingRight: 15, + flex: 1, + }, + + loginInput: { + width: "100%", + minHeight: Dimensions.get("window").height * 0.055, + borderWidth: 1.5, + borderColor: "#D6D6D6", + borderRadius: Dimensions.get("window").height * 0.015, + paddingTop: Platform.OS === "ios" ? 15 : 0, + paddingBottom: Platform.OS === "ios" ? 15 : 0, + paddingLeft: 20, + paddingRight: 20, + backgroundColor: "#F7F7F7", + fontFamily: "Quicksand-Medium", + elevation: 2, + }, + messsageContainer: { + width: Dimensions.get("window").width * 0.75, + borderWidth: 1, + borderRadius: 30, + paddingTop: Dimensions.get("window").height * 0.006, + paddingBottom: Dimensions.get("window").height * 0.006, + paddingLeft: 15, + paddingRight: Dimensions.get("window").height * 0.006, + flexDirection: "row", + flex: 1, + justifyContent: "space-between", + alignItems: "center", + borderColor: "#8E8E8E", + }, + messageInput: { + width: Dimensions.get("window").height * 0.35, + fontSize: 16, + }, + invalidLoginInput: { + borderColor: "red", + }, +}); diff --git a/server/package-lock.json b/server/package-lock.json index 564e301cd..a085f31ee 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -15,6 +15,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "geofire-common": "^6.0.0", + "mailgun.js": "^3.7.2", "socket.io": "^4.7.4", "uuid": "^9.0.1" }, @@ -1966,97 +1967,6 @@ "node": ">=10.0.0" } }, - "node_modules/@firebase/webchannel-wrapper": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.5.tgz", - "integrity": "sha512-eSkJsnhBWv5kCTSU1tSUVl9mpFu+5NXXunZc83le8GMjMlsWwQArSc7cJJ4yl+aDFY0NGLi0AjZWMn1axOrkRg==" - }, - "node_modules/@google-cloud/firestore": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.3.0.tgz", - "integrity": "sha512-2IftQLAbCuVp0nTd3neeu+d3OYIegJpV/V9R4USQj51LzJcXPe8h8jZ7j3+svSNhJVGy6JsN0T1QqlJdMDhTwg==", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^4.0.4", - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.0.tgz", - "integrity": "sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", - "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", - "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.7.0.tgz", - "integrity": "sha512-EMCEY+6JiIkx7Dt8NXVGGjy1vRdSGdHkoqZoqjJw7cEBkT7ZkX0c7puedfn1MamnzW5SX4xoa2jVq5u7OWBmkQ==", - "optional": true, - "dependencies": { - "@google-cloud/paginator": "^5.0.0", - "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "^4.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "fast-xml-parser": "^4.3.0", - "gaxios": "^6.0.2", - "google-auth-library": "^9.0.0", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "retry-request": "^7.0.0", - "teeny-request": "^9.0.0", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/storage/node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@google-cloud/storage/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2066,18 +1976,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.9.14", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.14.tgz", - "integrity": "sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw==", - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, "node_modules/@grpc/proto-loader": { "version": "0.7.10", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", @@ -3189,7 +3087,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -3566,6 +3463,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3612,6 +3514,11 @@ "node": ">=8" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -3868,6 +3775,19 @@ "node": ">=12" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4102,6 +4022,14 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "engines": { + "node": ">= 6" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4426,7 +4354,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true, "engines": { "node": ">=6" } @@ -4593,6 +4520,19 @@ "bser": "2.1.1" } }, + "node_modules/fetch-blob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz", + "integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==", + "engines": { + "node": "^10.17.0 || >=12.3.0" + }, + "peerDependenciesMeta": { + "domexception": { + "optional": true + } + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4681,37 +4621,12 @@ "node": ">=14" } }, - "node_modules/firebase-admin": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.0.0.tgz", - "integrity": "sha512-wBrrSSsKV++/+O8E7O/C7/wL0nbG/x4Xv4yatz/+sohaZ+LsnWtYUcrd3gZutO86hLpDex7xgyrkKbgulmtVyQ==", - "dependencies": { - "@fastify/busboy": "^1.2.1", - "@firebase/database-compat": "^1.0.2", - "@firebase/database-types": "^1.0.0", - "@types/node": "^20.10.3", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.0.1", - "node-forge": "^1.3.1", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@google-cloud/firestore": "^7.1.0", - "@google-cloud/storage": "^7.7.0" - } - }, - "node_modules/firebase-admin/node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", - "dependencies": { - "text-decoding": "^1.0.0" - }, - "engines": { - "node": ">=14" + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" } }, "node_modules/follow-redirects": { @@ -5001,80 +4916,6 @@ "node": "^8.13.0 || >=10.10.0" } }, - "node_modules/google-auth-library": { - "version": "9.6.3", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz", - "integrity": "sha512-4CacM29MLC2eT9Cey5GDVK4Q8t+MMp8+OEdOaqD9MG6b0dOyLORaaeJMPQ7EESVgm/+z5EKYyFLxgzBJlJgyHQ==", - "optional": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/google-auth-library/node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/google-auth-library/node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/google-gax": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.1.tgz", - "integrity": "sha512-qpSfslpwqToIgQ+Tf3MjWIDjYK4UFIZ0uz6nLtttlW9N1NQA4PhGf9tlGo6KDYJ4rgL2w4CjXVd0z5yeNpN/Iw==", - "optional": true, - "dependencies": { - "@grpc/grpc-js": "~1.10.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.0", - "protobufjs": "7.2.6", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/google-gax/node_modules/@grpc/grpc-js": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.1.tgz", - "integrity": "sha512-55ONqFytZExfOIjF1RjXPcVmT/jJqFzbbDqxK9jmRV4nxiYWtL9hENSW1Jfx0SdZfrvoqd44YJ/GJTqfRrawSQ==", - "optional": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5399,6 +5240,17 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5417,6 +5269,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -7266,6 +7126,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7275,6 +7143,57 @@ "node": ">=6" } }, + "node_modules/ky": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz", + "integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/ky-universal": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz", + "integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==", + "dependencies": { + "abort-controller": "^3.0.0", + "node-fetch": "3.0.0-beta.9" + }, + "engines": { + "node": ">=10.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky-universal?sponsor=1" + }, + "peerDependencies": { + "ky": ">=0.17.0", + "web-streams-polyfill": ">=2.0.0" + }, + "peerDependenciesMeta": { + "web-streams-polyfill": { + "optional": true + } + } + }, + "node_modules/ky-universal/node_modules/node-fetch": { + "version": "3.0.0-beta.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", + "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", + "dependencies": { + "data-uri-to-buffer": "^3.0.1", + "fetch-blob": "^2.1.1" + }, + "engines": { + "node": "^10.17 || >=12.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -7318,11 +7237,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -7402,6 +7316,21 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/mailgun.js": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-3.7.2.tgz", + "integrity": "sha512-vPU6iz32/j7vZ8LN+hN5cim8lBke71Qm83rbPN2Gh+JPJ6ClqdxUMKFrAW36mOodnHVZKpTWrSATNXGxT3cHVQ==", + "dependencies": { + "base-64": "^1.0.0", + "bluebird": "^3.7.2", + "ky": "^0.25.1", + "ky-universal": "^0.8.2", + "url": "^0.11.0", + "url-join": "0.0.1", + "web-streams-polyfill": "^3.0.1", + "webpack-merge": "^5.4.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -8029,6 +7958,11 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -8372,6 +8306,17 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9007,6 +8952,34 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url-join": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", + "integrity": "sha512-H6dnQ/yPAAVzMQRvEvyz01hhfQL5qRWSEt7BX8t9DqnPw9BjMb64fjIRq76Uvf1hkHp+mTZvEVJ5guXOT0Xqaw==" + }, + "node_modules/url/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9070,12 +9043,33 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "optional": true }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -9122,6 +9116,11 @@ "node": ">= 8" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/server/package.json b/server/package.json index 9af60ca12..093c72eb6 100644 --- a/server/package.json +++ b/server/package.json @@ -30,6 +30,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "geofire-common": "^6.0.0", + "mailgun.js": "^3.7.2", "socket.io": "^4.7.4", "uuid": "^9.0.1" }, diff --git a/server/src/index.ts b/server/src/index.ts index c2e4932b2..bb247896e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,270 +1,324 @@ -import express from 'express' -import 'dotenv/config' -import 'geofire-common' -import { Message } from './types/Message'; -import { createMessage } from './actions/createMessage' -import { createUser } from './actions/createConnectedUser' -import { toggleUserConnectionStatus, updateUserLocation } from './actions/updateConnectedUser' -import { deleteConnectedUserByUID } from './actions/deleteConnectedUser' -import {geohashForLocation} from 'geofire-common'; -import { findNearbyUsers } from './actions/getConnectedUsers' -import { ConnectedUser } from './types/User'; -import { getAuth } from 'firebase-admin/auth'; - - -const { createServer } = require('http') -const { Server } = require('socket.io') -const socket_port = process.env.socket_port -const express_port = process.env.express_port -const app = express() +import express from "express"; +import "dotenv/config"; +import "geofire-common"; +import { Message } from "./types/Message"; +import { createMessage } from "./actions/createMessage"; +import { createUser } from "./actions/createConnectedUser"; +import { + toggleUserConnectionStatus, + updateUserLocation, +} from "./actions/updateConnectedUser"; +import { deleteConnectedUserByUID } from "./actions/deleteConnectedUser"; +import { geohashForLocation } from "geofire-common"; +import { findNearbyUsers } from "./actions/getConnectedUsers"; +import { ConnectedUser } from "./types/User"; +import { getAuth } from "firebase-admin/auth"; +import Mailgun from "mailgun.js"; + +const { createServer } = require("http"); +const { Server } = require("socket.io"); +const socket_port = process.env.socket_port; +const express_port = process.env.express_port; +const app = express(); // Middleware -app.use(express.json()) -app.use(express.urlencoded({ extended: true })) - - +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); // === SOCKET API === -const socketServer = createServer() +const socketServer = createServer(); const io = new Server(socketServer, { cors: { - origin: '*', - methods: ['GET', 'POST'], + origin: "*", + methods: ["GET", "POST"], }, }); // Firebase JWT Authorization Custom Middleware io.use(async (socket, next) => { const token = socket.handshake.auth.token; - console.log(`[WS] Recieved token: ${token}`) + console.log(`[WS] Recieved token: ${token}`); if (token) { const decodedToken = await getAuth().verifyIdToken(token); const userId = decodedToken.uid; console.log(`[WS] User <${userId}> authenticated.`); - console.log(decodedToken) + console.log(decodedToken); next(); } else { - console.error("[WS] User not authenticated.") - next(new Error('User not authenticated.')); + console.error("[WS] User not authenticated."); + next(new Error("User not authenticated.")); } -}) - +}); -io.on('connection', async (socket: any) => { +io.on("connection", async (socket: any) => { console.log(`[WS] User <${socket.id}> connected.`); const defaultConnectedUser: ConnectedUser = { - uid: "UID", - socketId: socket.id, - displayName: "DISPLAY NAME", - userIcon: { - foregroundImage: "FOREGROUND IMG", - backgroundImage: "BACKGROUND IMG" - }, - location: { - lat: 9999, - lon: 9999, - geohash: "F" - } - } // TODO: Send this info from client on connection - await createUser(defaultConnectedUser) - await toggleUserConnectionStatus(socket.id) - - socket.on('disconnect', () => { - console.log(`[WS] User <${socket.id}> exited.`); - deleteConnectedUserByUID(socket.id) - }) - socket.on('ping', (ack) => { - // The (ack) parameter stands for "acknowledgement." This function sends a message back to the originating socket. - console.log(`[WS] Recieved ping from user <${socket.id}>.`) - if (ack) ack('pong') - }) - socket.on('message', async (message: Message, ack) => { + uid: "UID", + socketId: socket.id, + displayName: "DISPLAY NAME", + userIcon: { + foregroundImage: "FOREGROUND IMG", + backgroundImage: "BACKGROUND IMG", + }, + location: { + lat: 9999, + lon: 9999, + geohash: "F", + }, + }; // TODO: Send this info from client on connection + await createUser(defaultConnectedUser); + await toggleUserConnectionStatus(socket.id); + + socket.on("disconnect", () => { + console.log(`[WS] User <${socket.id}> exited.`); + deleteConnectedUserByUID(socket.id); + }); + socket.on("ping", (ack) => { + // The (ack) parameter stands for "acknowledgement." This function sends a message back to the originating socket. + console.log(`[WS] Recieved ping from user <${socket.id}>.`); + if (ack) ack("pong"); + }); + socket.on("message", async (message: Message, ack) => { // message post - when someone sends a message - - console.log(`[WS] Recieved message from user <${socket.id}>.`) - console.log(message) - try { - if(isNaN(message.timeSent)) throw new Error("The timeSent parameter must be a valid number.") - if(isNaN(message.location.lat)) throw new Error("The lat parameter must be a valid number.") - if(isNaN(message.location.lon)) throw new Error("The lon parameter must be a valid number.") - if (message.location.geohash == undefined || message.location.geohash === "") { - message.location.geohash = geohashForLocation([Number(message.location.lat), Number(message.location.lon)]) - console.log(`New geohash generated: ${message.location.geohash}`) + console.log(`[WS] Recieved message from user <${socket.id}>.`); + console.log(message); + try { + if (isNaN(message.timeSent)) + throw new Error("The timeSent parameter must be a valid number."); + if (isNaN(message.location.lat)) + throw new Error("The lat parameter must be a valid number."); + if (isNaN(message.location.lon)) + throw new Error("The lon parameter must be a valid number."); + + if ( + message.location.geohash == undefined || + message.location.geohash === "" + ) { + message.location.geohash = geohashForLocation([ + Number(message.location.lat), + Number(message.location.lon), + ]); + console.log(`New geohash generated: ${message.location.geohash}`); } - const status = await createMessage(message); - if(status === false) throw new Error("Error creating message: ") + const status = await createMessage(message); + if (status === false) throw new Error("Error creating message: "); // Get nearby users and push the message to them. - const nearbyUserSockets = await findNearbyUsers(Number(message.location.lat), Number(message.location.lon), Number(process.env.message_outreach_radius)) + const nearbyUserSockets = await findNearbyUsers( + Number(message.location.lat), + Number(message.location.lon), + Number(process.env.message_outreach_radius) + ); for (const recievingSocket of nearbyUserSockets) { // Don't send the message to the sender (who will be included in list of nearby users). if (recievingSocket === socket.id) { - continue + continue; } else { - console.log(`Sending new message to socket ${recievingSocket}`) - socket.broadcast.to(recievingSocket).emit("message", message) + console.log(`Sending new message to socket ${recievingSocket}`); + socket.broadcast.to(recievingSocket).emit("message", message); } } - if (ack) ack("message recieved") - - } catch(error) { - console.error("[WS] Error sending message:", error.message) + if (ack) ack("message recieved"); + } catch (error) { + console.error("[WS] Error sending message:", error.message); } - }) - socket.on('updateLocation', async (location, ack) => { - console.log(`[WS] Recieved new location from user <${socket.id}>.`) + }); + socket.on("updateLocation", async (location, ack) => { + console.log(`[WS] Recieved new location from user <${socket.id}>.`); try { - const lat = Number(location.lat) - const lon = Number(location.lon) - const success = await updateUserLocation(socket.id, lat, lon) + const lat = Number(location.lat); + const lon = Number(location.lon); + const success = await updateUserLocation(socket.id, lat, lon); if (success) { - console.log("[WS] Location updated in database successfully.") - if (ack) ack("location updated") + console.log("[WS] Location updated in database successfully."); + if (ack) ack("location updated"); } else { - throw new Error("updateUserLocation() failed.") + throw new Error("updateUserLocation() failed."); } } catch (error) { - console.error("[WS] Error calling updateLocation:", error.message) + console.error("[WS] Error calling updateLocation:", error.message); } - }) -}) + }); +}); socketServer.listen(socket_port, () => { - console.log(`[WS] Listening for new connections on port ${socket_port}.`) -}) + console.log(`[WS] Listening for new connections on port ${socket_port}.`); +}); // === REST APIs === -app.get('/', (req, res) => { - res.send("Echologator API") -}) +app.get("/", (req, res) => { + res.send("Echologator API"); +}); -app.get('/users', async (req, res) => { - let query = '' +app.get("/users", async (req, res) => { + let query = ""; try { if (req.query.lat && req.query.lon && req.query.radius) { // Looks up all users close to a geographic location extended by a radius (in meters). - query = "?lat&lon&radius" - - const lat = Number(req.query.lat) - const lon = Number(req.query.lon) - const radius = Number(req.query.radius) - - const userIds = await findNearbyUsers(lat, lon, radius) - console.log(userIds) - res.json(userIds) + query = "?lat&lon&radius"; + + const lat = Number(req.query.lat); + const lon = Number(req.query.lon); + const radius = Number(req.query.radius); + + const userIds = await findNearbyUsers(lat, lon, radius); + console.log(userIds); + res.json(userIds); } - } catch(error) { - console.error(`[EXP] Error returning request :\n`, error.message) - res.json(`Operation failed.`) + } catch (error) { + console.error( + `[EXP] Error returning request :\n`, + error.message + ); + res.json(`Operation failed.`); } -}) +}); -app.post('/users', async (req, res) => { - try { - const status = await createUser({ - uid: req.body.uid, - socketId: req.body.socketId, - displayName: req.body.displayName, - userIcon: { - foregroundImage: req.body.userIcon.foregroundImage, - backgroundImage: req.body.userIcon.backgroundImage, - }, - location: { - lat: Number(req.body.location.lat), - lon: Number(req.body.location.lon), - geohash: req.body.location.geohash, - } - }) - if (status === false) throw new Error("Error creating user: ") - res.json("Operation was handled successfully.") - console.log("[EXP] Request returned successfully.") - } catch (error) { - console.error("[EXP] Error returning request :\n", error.message) - res.json(`Operation failed.`) - } -}) +app.post("/users", async (req, res) => { + try { + const status = await createUser({ + uid: req.body.uid, + socketId: req.body.socketId, + displayName: req.body.displayName, + userIcon: { + foregroundImage: req.body.userIcon.foregroundImage, + backgroundImage: req.body.userIcon.backgroundImage, + }, + location: { + lat: Number(req.body.location.lat), + lon: Number(req.body.location.lon), + geohash: req.body.location.geohash, + }, + }); + if (status === false) throw new Error("Error creating user: "); + res.json("Operation was handled successfully."); + console.log("[EXP] Request returned successfully."); + } catch (error) { + console.error( + "[EXP] Error returning request :\n", + error.message + ); + res.json(`Operation failed.`); + } +}); -app.put('/users', async (req, res) => { - let query = "" +app.put("/users", async (req, res) => { + let query = ""; try { if (req.query.userId && req.query.toggleConnection) { // Note: toggleConnection should be assigned 'true', but it at least needs to contain any value. We don't perform a check on this parameter for this reason. - query = "?userId&toggleConnection" - const userId = req.query.userId - if (typeof userId != "string") throw Error(" [userId] is not a string.") - - const success = await toggleUserConnectionStatus(userId) - if (!success) throw Error(" toggleUserConnectionStatus() failed.") - } - else if(req.query.userId && req.query.lat && req.query.lon) { - query = "?userId&lat&lon" - const userId = req.query.userId - const lat = Number(req.query.lat) - const lon = Number(req.query.lon) - if (typeof userId != "string") throw Error(" [userId] is not a string.") - if (typeof lat != "number") throw Error(" [lat] is not a number.") - if (typeof lon != "number") throw Error(" [lon] is not a number.") - - const success = await updateUserLocation(userId, lat, lon) - if (!success) throw Error(" toggleUserConnectionStatus() failed.") + query = "?userId&toggleConnection"; + const userId = req.query.userId; + if (typeof userId != "string") throw Error(" [userId] is not a string."); + + const success = await toggleUserConnectionStatus(userId); + if (!success) throw Error(" toggleUserConnectionStatus() failed."); + } else if (req.query.userId && req.query.lat && req.query.lon) { + query = "?userId&lat&lon"; + const userId = req.query.userId; + const lat = Number(req.query.lat); + const lon = Number(req.query.lon); + if (typeof userId != "string") throw Error(" [userId] is not a string."); + if (typeof lat != "number") throw Error(" [lat] is not a number."); + if (typeof lon != "number") throw Error(" [lon] is not a number."); + + const success = await updateUserLocation(userId, lat, lon); + if (!success) throw Error(" toggleUserConnectionStatus() failed."); } - console.log(`[EXP] Request returned successfully.`) - res.json(`Operation was handled successfully.`) - + console.log(`[EXP] Request returned successfully.`); + res.json(`Operation was handled successfully.`); } catch (error) { - console.error(`[EXP] Error returning request :\n`, error.message) - res.json(`Operation failed.`) + console.error( + `[EXP] Error returning request :\n`, + error.message + ); + res.json(`Operation failed.`); } -}) +}); -app.delete('/users', async (req, res) => { - let query = "" +app.delete("/users", async (req, res) => { + let query = ""; try { - query = "?userId" - const userId = req.query.userId - if (typeof userId != "string") throw Error(" [userId] is not a string.") + query = "?userId"; + const userId = req.query.userId; + if (typeof userId != "string") throw Error(" [userId] is not a string."); + + const success = await deleteConnectedUserByUID(userId); + if (!success) throw Error(" deleteUserById() failed."); - const success = await deleteConnectedUserByUID(userId) - if (!success) throw Error(" deleteUserById() failed.") + console.log(`[EXP] Request returned successfully.`); + res.json(`Operation was handled successfully.`); + } catch (error) { + console.error( + `[EXP] Error returning request :\n`, + error.message + ); + res.json(`Operation failed.`); + } +}); - console.log(`[EXP] Request returned successfully.`) - res.json(`Operation was handled successfully.`) +app.post("/verify", async (req, res) => { + let query = ""; + try { + if (req.query.email) { + query = "?email"; + const email = req.query.email; + const mailgun = new Mailgun(FormData); + const mg = mailgun.client({ + username: "api", + key: process.env.MAILGUN_API_KEY || "key-yourkeyhere", + }); + const data = { + from: "Mailgun Sandbox ", + to: email, + subject: "Verify your email for echologator", + template: "app email verification" + }; + const verifyEmailResponse = await mg.messages.create("sandboxf8629624c26849cf8546cd0bc01ee862.mailgun.org", data) + console.log(`[EXP] Request returned successfully.`); + res.json(verifyEmailResponse); + } } catch (error) { - console.error(`[EXP] Error returning request :\n`, error.message) - res.json(`Operation failed.`) + console.error( + `[EXP] Error returning request :\n`, + error + ); + res.json(`Operation failed.`); } -}) +}); // Error handling -app.get('*', (req, res) => { - res.json("404: Path could not be found! COULD NOT {GET}") - res.status(404) -}) - -app.post('*', (req, res) => { - res.json("404: Path could not be found! COULD NOT {POST}") - res.status(404) -}) - -app.put('*', (req, res) => { - res.json("404: Path could not be found! COULD NOT {PUT}") - res.status(404) -}) - -app.delete('*', (req, res) => { - res.json("404: Path could not be found! COULD NOT {DELETE}") - res.status(404) -}) +app.get("*", (req, res) => { + res.json("404: Path could not be found! COULD NOT {GET}"); + res.status(404); +}); + +app.post("*", (req, res) => { + res.json("404: Path could not be found! COULD NOT {POST}"); + res.status(404); +}); + +app.put("*", (req, res) => { + res.json("404: Path could not be found! COULD NOT {PUT}"); + res.status(404); +}); + +app.delete("*", (req, res) => { + res.json("404: Path could not be found! COULD NOT {DELETE}"); + res.status(404); +}); app.listen(express_port, () => { - return console.log(`[EXP] Listening for requests at http://localhost:${express_port}.`) -}) + return console.log( + `[EXP] Listening for requests at http://localhost:${express_port}.` + ); +}); // Some old API routes are commented out for now due to breaking type changes. @@ -347,4 +401,3 @@ app.listen(express_port, () => { // res.json(false) // } // }) -