Skip to content

Commit

Permalink
Refactored most of the actions (ufosc#156)
Browse files Browse the repository at this point in the history
* new branch + function fix

* small bug fixes

* fixes pt2

* JWT WIP

* new branch + function fix

* small bug fixes

* fixes pt2

* JWT WIP

* new branch + function fix

* small bug fixes

* fixes pt2

* JWT WIP

* new branch + function fix

* small bug fixes

* added passport

* started auth + admin sdk

* removed passport

* added private key to gitignore

* Delete server/private_keys/private.json

* initializing firestore in admin

* finished most of action refactoring

* Made error case for email in use display correctly in front end (ufosc#158)

* changed getConnectedUsers to admin

* migrated to firebase-admin

---------

Co-authored-by: AlexanderWangY <[email protected]>
Co-authored-by: Mohammed Ali <[email protected]>
  • Loading branch information
3 people committed Feb 25, 2024
1 parent 76aedc5 commit 202b5bd
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 567 deletions.
680 changes: 223 additions & 457 deletions server/package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"firebase": "^10.5.0",
"firebase-admin": "^12.0.0",
"geofire-common": "^6.0.0",
"socket.io": "^4.7.4",
Expand Down
9 changes: 3 additions & 6 deletions server/src/actions/createConnectedUser.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// Uploads a new document in the ConnectedUsers collection.
import { doc, setDoc } from '@firebase/firestore'
import { connectedUsers } from '../utilities/firebaseInit'
import { ConnectedUser } from '../types/User'
import { connectedUsersCollection } from '../utilities/adminInit';

export const createUser = async (connectedUser: ConnectedUser) => {
try {
const ref = doc(connectedUsers, connectedUser.socketId) // Use the socketid as the index
await setDoc(ref, connectedUser)
return true

await connectedUsersCollection.doc(connectedUser.socketId).set(connectedUser)
console.log('User added to the database')
} catch (error) {
console.error(error.message)
return false
Expand Down
6 changes: 2 additions & 4 deletions server/src/actions/createMessage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Uploads a new document in the Messages collection.
import { doc, setDoc } from '@firebase/firestore'
import { messages } from '../utilities/firebaseInit'
import { Message } from '../types/Message'
import { messagesCollection } from '../utilities/adminInit'

export const createMessage = async (msg : Message) => {
try {
const ref = doc(messages, msg.msgId)
const status = await setDoc(ref, msg)
await messagesCollection.doc(msg.msgId).set(msg)
return true

} catch (error) {
Expand Down
17 changes: 3 additions & 14 deletions server/src/actions/deleteConnectedUser.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
// Delete a ConnectedUser document given a document's index. This should typically be a socketId, but it can also be something else.
import { doc, getDoc, deleteDoc } from '@firebase/firestore'
import { connectedUsers } from '../utilities/firebaseInit'
import { connectedUsersCollection } from '../utilities/adminInit'

export const deleteConnectedUserByIndex = async (index: string) => {
export const deleteConnectedUserByUID = async (socketID: string) => {
try {
const userRef = doc(connectedUsers, index)

// The promise returned by deleteDoc will be fulfilled (aka return 'true') both if the document requested for deletion exists or doesn't exist. It is rejected if the program is unable to send this request to Firestore.
// Therefore, we need to check to see if the document exists first, to most accurately know if it will be deleted.
// However, technically, there could be some kind of failure by deleteDoc after this check is performed, where the status of the deletion would then be inaccurately returned.
// TODO: find a way to assuredly know if a document is deleted after deleteDoc is called.

const userDoc = await getDoc(userRef)
if (!userDoc.exists()) throw Error("[FIREBASE] User does not exist.")

await deleteDoc(userRef)
await connectedUsersCollection.doc(socketID).delete()
return true

} catch (error) {
Expand Down
31 changes: 13 additions & 18 deletions server/src/actions/getConnectedUsers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { doc, endAt, getDocs, orderBy, query, startAt } from 'firebase/firestore'
import { connectedUsers } from '../utilities/firebaseInit'
import { distanceBetween, geohashForLocation, geohashQueryBounds } from 'geofire-common'
import { connectedUsersCollection } from '../utilities/adminInit'

export const findNearbyUsers = async (centerLat: number, centerLon: number, radius: number) => {
// Return an array of nearby userIds (which are also socket ids) given a center latitude and longitude.
Expand All @@ -18,21 +17,21 @@ export const findNearbyUsers = async (centerLat: number, centerLon: number, radi
const promises = []

for (const b of bounds) {
const q = query(
connectedUsers,
orderBy('location.geohash'),
startAt(b[0]),
endAt(b[1])
)

promises.push(getDocs(q))
const q = connectedUsersCollection
.orderBy('location.geohash')
.startAt(b[0])
.endAt(b[1])

promises.push(q.get())
}

// Collect query results and append into a single array
const snapshots = await Promise.all(promises)

const matchingDocs = []

for (const snap of snapshots) {

for (const doc of snap.docs) {
const lat = doc.get('location.lat')
const lon = doc.get('location.lon')
Expand All @@ -42,18 +41,14 @@ export const findNearbyUsers = async (centerLat: number, centerLon: number, radi
const distanceInKm = distanceBetween([lat, lon], [centerLat, centerLon])
const distanceInM = distanceInKm * 1000
if (distanceInM <= radius) {
matchingDocs.push(doc)
matchingDocs.push(doc.get('socketId'))
}
}
}

// Extract userIds from matched documents
const userSocketIds = []
for (const doc of matchingDocs) {
userSocketIds.push(doc.data()['socketId'])
}
console.log(`getNearbyUsers(): ${userSocketIds.length} users found within ${radius} meters of ${centerLat}, ${centerLon}`)
return userSocketIds
console.log(`getNearbyUsers(): ${matchingDocs.length} users found within ${radius} meters of ${centerLat}, ${centerLon}`)
console.log(matchingDocs)
return matchingDocs
} catch (error) {
console.error("getNearbyUsers() failed.", error.message)
}
Expand Down
25 changes: 6 additions & 19 deletions server/src/actions/updateConnectedUser.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import { doc, getDoc, updateDoc } from '@firebase/firestore'
import { connectedUsers } from '../utilities/firebaseInit'
import { geohashForLocation} from 'geofire-common'
import { connectedUsersCollection } from '../utilities/adminInit'

export const toggleUserConnectionStatus = async (index: string) => {
export const toggleUserConnectionStatus = async (socketID: string) => {
try {
const userRef = doc(connectedUsers, index)
const userDoc = await getDoc(userRef)

if (!userDoc.exists()) throw Error("[FIREBASE] User does not exist.")

let status = userDoc.data()['isConnected']

let status = connectedUsersCollection.doc(socketID).isConnected
// Flip the connection status
status = !status

updateDoc(userRef, { isConnected: status })
await connectedUsersCollection.doc(socketID).update({ isConnected: status })
return true

} catch (error) {
console.error(error.message)
return false
}
}

export const updateUserLocation = async (userIndex: string, lat: number, lon: number) => {
export const updateUserLocation = async (socketID: string, lat: number, lon: number) => {
try {
const ref = doc(connectedUsers, userIndex)
const userDoc = await getDoc(ref)

if (!userDoc.exists()) throw Error("[FIREBASE] User does not exist.")

const newHash = geohashForLocation([lat, lon])

updateDoc(ref, { "location.lat": lat, "location.lon": lon, "location.geohash": newHash })
await connectedUsersCollection.doc(socketID).update({ "location.lat": lat, "location.lon": lon, "location.geohash": newHash })
return true
} catch (error) {
console.error(error.message)
Expand Down
28 changes: 11 additions & 17 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
import express from 'express'
import 'dotenv/config'
import 'geofire-common'

import { Message } from './types/Message';

import { createMessage } from './actions/createMessage'
// import { deleteMessageById } from './actions/deleteMessage'
// import { getUserById } from './actions/getUsers'
import { createUser } from './actions/createConnectedUser'
import { toggleUserConnectionStatus, updateUserLocation } from './actions/updateConnectedUser'
import { deleteConnectedUserByIndex } from './actions/deleteConnectedUser'
import { deleteConnectedUserByUID } from './actions/deleteConnectedUser'
import {geohashForLocation} from 'geofire-common';
import { findNearbyUsers } from './actions/getConnectedUsers'
import { ConnectedUser } from './types/User';
import { adminApp } from './utilities/adminInit';


const { createServer } = require('http')
const { Server } = require('socket.io')

adminApp.firestore()


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 }))

Expand All @@ -39,7 +33,7 @@ const io = new Server(socketServer, {
},
});

io.on('connection', (socket: any) => {
io.on('connection', async (socket: any) => {
console.log(`[WS] User <${socket.id}> connected.`);
const defaultConnectedUser: ConnectedUser = {
uid: "UID",
Expand All @@ -55,12 +49,12 @@ io.on('connection', (socket: any) => {
geohash: "F"
}
} // TODO: Send this info from client on connection
createUser(defaultConnectedUser)
toggleUserConnectionStatus(socket.id)
await createUser(defaultConnectedUser)
await toggleUserConnectionStatus(socket.id)

socket.on('disconnect', () => {
console.log(`[WS] User <${socket.id}> exited.`);
deleteConnectedUserByIndex(socket.id)
deleteConnectedUserByUID(socket.id)
})
socket.on('ping', (ack) => {
// The (ack) parameter stands for "acknowledgement." This function sends a message back to the originating socket.
Expand Down Expand Up @@ -103,11 +97,11 @@ io.on('connection', (socket: any) => {
console.error("[WS] Error sending message:", error.message)
}
})
socket.on('updateLocation', async (message, ack) => {
socket.on('updateLocation', async (location, ack) => {
console.log(`[WS] Recieved new location from user <${socket.id}>.`)
try {
const lat = Number(message.lat)
const lon = Number(message.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.")
Expand Down Expand Up @@ -217,7 +211,7 @@ app.delete('/users', async (req, res) => {
const userId = req.query.userId
if (typeof userId != "string") throw Error(" [userId] is not a string.")

const success = await deleteConnectedUserByIndex(userId)
const success = await deleteConnectedUserByUID(userId)
if (!success) throw Error(" deleteUserById() failed.")

console.log(`[EXP] Request <DELETE /users${query}> returned successfully.`)
Expand Down
13 changes: 13 additions & 0 deletions server/src/private_key/private.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "fir-auth-89462",
"private_key_id": "770edc7187f5ef3d35359b9ad2304570d4f170ff",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCS6oM6wYwCp7a\nCXauPpBDjLzqq5PrMZCWOoi2THjqTzLJQc/Xwg5XlOyRX1DB4Uzl1iutfLsQXNuv\nMRze3i2jIOLysnrkvSuLW9n19k/1gIYYm7oxZQPaJ5iNJ33GFiNJqULlmMTvv6vp\nhXOOUZxl9dNY3HRpICcaGNPXjAv3tG7W+iHsidphp88Q8OMMmkZcl3+h2Hc1yDEf\nmXPR0YynfakDi45tMf5F9x3jRT6y6O30PSpZUIVL2cfwWs+zojnK+vy+cGCjBIJE\n/uI+A7XJJ4BWNLH/jhy6Jta/jDkCj/JbUsxRTX2ZuUeVi8uSMkzdy/Pb+o5f4SaJ\nl3mTa7S5AgMBAAECggEAEIChdwMrxX8TbrC81TAga/JbVeHAmKp/LblXRQeSAhG7\nvisSMpDLi84M+UxzlSIUGFXkZBH65/kBGjxVR8subGN1zzgQVtcH6KhipwDWmgMi\ncJriecFLDleMXhnXdZCKCv6+vTZIbNYWbYlNjy4YlvLmEm84DnlCBPYMoStGlXFm\nRKEfLl4FHHmCRcRkuGWRyKk9LtKNykrlaFVZ3BL11/TC38BlK3us+1cyUO3dA1EW\njAYSRG3tAVGVS7VUoxDDcA1V+Wnccpt+ws/rzIVYcTXawXrpHXFx977FwarGHReL\nmRmjHYGo/4Ry+q8Nk1y7GkdPYxq5GVAtXuRqSx7EgQKBgQD86ru/bBxJZxNAuHTE\nx9j/n0MrH9ulAFEzAXzq12Yf2xSytrUbD31zPg+rrFTGDNYISUPKSa9sxpC/jiwB\nXmuNhFVvt1521naZOtyezo3RYztcwOIxscvULHMUt/ikaFVsytlxrqDn0MJXuKM/\n5OZbbolc5pR3Oi82FOz09xB+OQKBgQDEqf5ox6V8ZfrMdFvhKC0EklyfDjClrJa2\nCkE0HK9ePxCpnEqBYC8wj6pXNOnLONYNZIjJ7Nz2CiFd0gIt7Ep9KtDNve99dWHR\nObPbt9fC8vMuzotBN6P345hzdqXR7OUUfAPFBCZTysWrfHzqg1tFYkt5t+YVfWuu\nMRq/xpzqgQKBgQC00Af7eQnb/EHKYlSwngNn9G8rtHHty4VBhs3MgsOzAIgSoAZn\n2zIfon3HiMNud5zIfcBmLTmp9WdkWvrg26Tenn4KCTkSko5lS6yQKDFBQcUdsZPE\nXUzQWhrH9CJhP2nbBkZgPK0yLY/S8OBc/IMnWKYBcaMwfbtk2Z7yHnN/GQKBgClH\ndTsRDM87qJTZp59vC2P2RLKuC8/6lffH1z/U9YpWumyffZQCWGVdAmgjlx8s4uEU\nxRF9QjPylGZY+lQhUNFM9174CxjOVqXP8syfng4xaJHekKQzxZr2jr1NniieDMdr\n8G6eHF1iJnOEQcQHplS9+RGnZAgGt19styyhx7YBAoGBAN++OfEq6S8zW/+Q9E8x\nAxvdNrFOkHnJf+URwTZQNFebvRUDouYjHE1fOCSM7npkNFV/DyINTsP6cNGKwGXM\nox1ODGb2uLk3hEQLDbqocIoWnuzY2QU3lubrSdAnEUDBovjd4of47BwYo2HfA+Pu\nTuLU4vtbFvSH7e3OJ9A//cKD\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "102183968562626917121",
"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/firebase-adminsdk-f0nzi%40fir-auth-89462.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
9 changes: 6 additions & 3 deletions server/src/utilities/adminInit.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const { initializeApp } = require('firebase-admin/app');
const admin = require('firebase-admin');
const serviceAccount = require("../../src/private_key/<YOUR KEY>.json");
const serviceAccount = require("../../src/private_key/private.json");

export const adminApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
credential: admin.credential.cert(serviceAccount),
});

export const db = admin.firestore();
export const connectedUsersCollection = db.collection('users');
export const messagesCollection = db.collection('messages');

console.log("[FIREBASE-ADMIN] Firebase admin SDK synced.")
28 changes: 0 additions & 28 deletions server/src/utilities/firebaseInit.ts

This file was deleted.

0 comments on commit 202b5bd

Please sign in to comment.