CS4610-FinalProject / FinalProject / src / lib / Api.ts
Api.ts
Raw
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;
  }
}