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