import { UserCredential, createUserWithEmailAndPassword, getAuth, onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth"; import { initializeApp } from "firebase/app"; import { getFirestore, collection, getDocs, addDoc, getDoc, doc, serverTimestamp, updateDoc, setDoc, arrayUnion, arrayRemove, query, where } from 'firebase/firestore/lite'; import Axios from "axios"; const firebaseConfig = { apiKey: "AIzaSyDrIRYKwF0EYIORPaFgxznS3n0pM2xw93Q", authDomain: "cs4610-finalproject.firebaseapp.com", projectId: "cs4610-finalproject", storageBucket: "cs4610-finalproject.appspot.com", messagingSenderId: "379812627650", appId: "1:379812627650:web:d7baa2f6216da11352a75b", measurementId: "G-6BKNSDXJZS" }; const SPOTIFY_CLIENT_ID = "f9744db27a124eb6b48ec0aaf31b3a2a"; const SPOTIFY_CLIENT_SECRET = "fcf8e359dfa14881a0181fe7e5603f19"; const OPENCAGE_API = "bcc90ba307524405853f395fdc4b990d" // Initialize Firebase const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); type User = { email: string; uid: string; username: string; firstName: string; lastName: string; friends: string[]; mostRecentPost: string; } type Post = { uid: string; spotifyData: any; created: any; location: string; username: string; } type Friend = { username: string; } // auth.onAuthStateChanged((user) => { // if (user) { // console.log("User is signed in"); // } else { // console.log("User is signed out"); // }}); export class Api { user: User | null = null; token: string | null = null; refreshToken: string | null = null; selfPosts: Post[] = []; friendsPosts: Post[] = []; friends: Friend[] = []; async createUser(email: string, password: string, username: string, firstName: string, lastName: string) { try { let userCredential = await createUserWithEmailAndPassword(auth, email, password); const user = userCredential.user; if (user === null) return false let newUser: User = { email: email, uid: user.uid, username: username, firstName: firstName, lastName: lastName, friends: [], mostRecentPost: "" } try { await setDoc(doc(db, "users", newUser.uid), newUser); this.user = newUser; return true; } catch (error) { console.error("Error adding user: ", error); return false; } } catch (error: any) { const errorCode = error.code; const errorMessage = error.message; console.error(`Error code: ${errorCode}, Error message: ${errorMessage}`); return false; } } async loginUser(email: string, password: string) { try { let userCredential = await signInWithEmailAndPassword(auth, email, password); const user = userCredential.user; try { let docSnap = await getDoc(doc(db, "users", user.uid)); this.user = docSnap.data() as User; return true; } catch (e) { console.error("Error adding document: ", e); return false; } } catch (error: any) { const errorCode = error.code; const errorMessage = error.message; console.error(`Error code: ${errorCode}, Error message: ${errorMessage}`); return false; } } signOut() { auth.signOut(); } getSpotifyLoginLink() { // const REDIRECT_URI = "http://localhost:5173/test/"; const REDIRECT_URI = "https://cs4610-finalproject.web.app/"; const AUTH_ENDPOINT = "https://accounts.spotify.com/authorize"; const RESPONSE_TYPE = "code"; return `${AUTH_ENDPOINT}?client_id=${SPOTIFY_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=user-read-private%20user-read-currently-playing&response_type=${RESPONSE_TYPE}` } async getSpotifyToken(code: string | null) { // const REDIRECT_URI = "http://localhost:5173/test/"; const REDIRECT_URI = "https://cs4610-finalproject.web.app/"; if (code === null) return; if (this.token !== null) return; const { data } = await Axios.post("https://accounts.spotify.com/api/token", { grant_type: "authorization_code", code: code, redirect_uri: REDIRECT_URI, }, { headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": `Basic ${btoa(SPOTIFY_CLIENT_ID + ":" + SPOTIFY_CLIENT_SECRET)}` } } ); window.localStorage.setItem("token", data.access_token); this.token = data.access_token; this.refreshToken = data.refresh_token; } getCurrentSpotifyToken() { return window.localStorage.getItem("token"); } spotifyLogout() { window.localStorage.removeItem("token"); } async getCurrentSong() { const token = this.getCurrentSpotifyToken(); if (token === null) return null; const { data } = await Axios.get("https://api.spotify.com/v1/me/player/currently-playing?market=US", { headers: { Authorization: `Bearer ${token}` } }); if (data === null) return null; if (!data.hasOwnProperty("item")) return null; return { name: data.item.name, artist: data.item.artists[0].name, album: data.item.album.name, url: data.item.external_urls.spotify, image: data.item.album.images[0].url }; } getCurrentUser() { return this.user; } async createPost() { if (this.user === null) { try { if (auth.currentUser === null) return false; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); return false; } } let spotifyData = await this.getCurrentSong(); if (spotifyData === null) return false; // Use OpenCage to get the user's location let location = "Utah State University"; // Default location let geoLocation: GeolocationPosition | null = null; if (navigator.geolocation) { // Get the user's current position async function getSpecificLoc(position: GeolocationPosition) { let response = await fetch(`https://api.opencagedata.com/geocode/v1/json?key=bcc90ba307524405853f395fdc4b990d&q=${position.coords.latitude}+${position.coords.longitude}`); let data = await response.json(); if (data.results && data.results.length > 0) { location = data.results[0].formatted; } } async function getCoordinates() { const pos = await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject); }) return pos as GeolocationPosition; } geoLocation = await getCoordinates(); if (geoLocation !== null) { await (getSpecificLoc(geoLocation)); } // Create the post object let post = { created: serverTimestamp(), spotifyData: spotifyData, location: location, username: "" } if (this.user === null) return false; post.username = this.user.username; try { let docRef = await addDoc(collection(db, `users/${this.user.uid}/posts`), post); await updateDoc(doc(db, "users", this.user.uid), { mostRecentPost: docRef.id }); let newPostRef = await getDoc(doc(db, `users/${this.user.uid}/posts/${docRef.id}`)); let newPost = newPostRef.data() as Post; newPost.uid = docRef.id; this.selfPosts.unshift(newPost); return true; } catch (e) { console.error("Error adding document: ", e); return false; } } else { // Geolocation is not supported by the user's browser, so use the default location let post = { created: serverTimestamp(), spotifyData: spotifyData, location: location, username: "" } if (this.user === null) return false; post.username = this.user.username; try { let docRef = await addDoc(collection(db, `users/${this.user.uid}/posts`), post); await updateDoc(doc(db, "users", this.user.uid), { mostRecentPost: docRef.id }); let newPost: Post = { uid: docRef.id, created: post.created, spotifyData: post.spotifyData, location: post.location, username: post.username // include the username in the new post } this.selfPosts.unshift(newPost); return true; } catch (e) { console.error("Error adding document: ", e); return false; } } } refreshCurrentFeed() { if (this.user === null) return; if (this.selfPosts.length === 0) return this.friendsPosts; return [this.selfPosts[0], ...this.friendsPosts]; } async getOwnLatestPost() { if (this.user === null) { try { if (auth.currentUser === null) return null; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return null; if (this.user.mostRecentPost === "") return null; try { if (this.selfPosts.length > 0) return this.selfPosts[0]; let docSnap = await getDoc(doc(db, `users/${this.user.uid}/posts/${this.user.mostRecentPost}`)); let data = docSnap.data() as Post; data.uid = docSnap.id; return data; } catch (e) { console.error("Error adding document: ", e); return null; } } // get your own posts to display on your profile async getOwnPosts() { if (this.user === undefined || this.user === null) { try { if (auth.currentUser === null) return this.selfPosts; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return this.selfPosts; try { this.selfPosts.length = 0; const querySnapshot = await getDocs(collection(db, `users/${this.user.uid}/posts`)); querySnapshot.forEach((doc) => { let data = doc.data() as Post; data.uid = doc.id; this.selfPosts.push(data); }); } catch (e) { console.error("Error adding document: ", e); } return this.selfPosts; } // get all recent posts from friends and yours, with yours being first async getFeedPosts() { let selfPost = await this.getOwnLatestPost(); let friendsPosts = await this.getFriendsPosts(); if (selfPost === null) return friendsPosts; let allPosts = [selfPost, ...friendsPosts]; return allPosts; } // Untested but should work async getFriendsPosts() { if (this.user === null) { try { if (auth.currentUser === null) return this.friendsPosts; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return this.friendsPosts; try { this.friendsPosts.length = 0; for (let index in this.user.friends) { let friend = this.user.friends[index]; let q = query(collection(db, `users`), where("username", "==", friend)); const querySnapshot = await getDocs(q); if (querySnapshot.empty) continue; let friendDoc = querySnapshot.docs[0]; let friendData = friendDoc.data() as User; let friendPost = await getDoc(doc(db, `users/${friendData.uid}/posts/${friendData.mostRecentPost}`)); let friendPostData = friendPost.data() as Post; friendPostData.uid = friendPost.id; this.friendsPosts.push(friendPostData); } } catch (e) { console.error("Error adding document: ", e); } return this.friendsPosts; } async addFriend(username: string) { if (this.user === null) { try { if (auth.currentUser === null) return false; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return false; try { await updateDoc(doc(db, "users", this.user.uid), { friends: arrayUnion(username) }); return true; } catch (e) { console.error("Error adding document: ", e); return false; } } async removeFriend(username: string) { if (this.user === null) { try { if (auth.currentUser === null) return false; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return false; try { await updateDoc(doc(db, "users", this.user.uid), { friends: arrayRemove(username) }); return true; } catch (e) { console.error("Error adding document: ", e); return false; } } // this is to get the friends themselves, such as for adding, deleting, or displaying async getFriends() { if (this.user === null) { try { if (auth.currentUser === null) return this.friends; let docSnap = await getDoc(doc(db, "users", auth.currentUser.uid)); this.user = docSnap.data() as User; } catch (e) { console.error("Error adding document: ", e); } } if (this.user === null) return this.friends; try { let tempFriends = await getDoc(doc(db, "users", this.user.uid)); this.friends.length = 0; if (!tempFriends.exists()) return this.friends; tempFriends.data().friends.forEach((username: string) => { let friend: Friend = { username: username, } this.friends.push(friend); }) return this.friends; } catch (e) { console.error("Error adding document: ", e); } return this.friends; } }