import { addDoc, Bytes, collection, deleteDoc, DocumentReference, FirestoreDataConverter, getDoc, getDocs, query, QueryDocumentSnapshot, serverTimestamp, Timestamp, updateDoc, where, } from "firebase/firestore" import { songFromMidi, songToMidi } from "../common/midi/midiConversion" import Song from "../common/song" import { auth, firestore } from "./firebase" export interface FirestoreSongData { createdAt: Timestamp updatedAt: Timestamp data?: Bytes userId: string } export interface FirestoreSong { name: string createdAt: Timestamp updatedAt: Timestamp dataRef: DocumentReference userId: string } export const songConverter: FirestoreDataConverter = { fromFirestore(snapshot, options) { const data = snapshot.data(options) return data as FirestoreSong }, toFirestore(song) { return song }, } export const songDataConverter: FirestoreDataConverter = { fromFirestore(snapshot, options) { const data = snapshot.data(options) return data as FirestoreSongData }, toFirestore(song) { return song }, } export const songCollection = collection(firestore, "songs").withConverter( songConverter, ) export const songDataCollection = collection( firestore, "songData", ).withConverter(songDataConverter) export const loadSong = async ( songSnapshot: QueryDocumentSnapshot, ) => { const snapshot = await getDoc( songSnapshot.data().dataRef.withConverter(songDataConverter), ) const data = snapshot.data()?.data if (data === undefined) { throw new Error("Song data does not exist") } const song = songFromMidi(data.toUint8Array()) song.name = songSnapshot.data().name song.firestoreReference = songSnapshot.ref song.firestoreDataReference = snapshot.ref song.isSaved = true return song } export const createSong = async (song: Song) => { if (auth.currentUser === null) { throw new Error("You must be logged in to save songs to the cloud") } const bytes = songToMidi(song) const dataDoc = await addDoc(songDataCollection, { createdAt: serverTimestamp(), updatedAt: serverTimestamp(), data: Bytes.fromUint8Array(bytes), userId: auth.currentUser.uid, }) const doc = await addDoc(songCollection, { name: song.name, createdAt: serverTimestamp(), updatedAt: serverTimestamp(), dataRef: dataDoc, userId: auth.currentUser.uid, }) song.firestoreDataReference = dataDoc song.firestoreReference = doc song.isSaved = true } export const updateSong = async (song: Song) => { if (auth.currentUser === null) { throw new Error("You must be logged in to save songs to the cloud") } if ( song.firestoreReference === null || song.firestoreDataReference === null ) { throw new Error("This song is not loaded from the cloud") } const bytes = songToMidi(song) await updateDoc(song.firestoreReference, { updatedAt: serverTimestamp(), name: song.name, }) await updateDoc(song.firestoreDataReference, { updatedAt: serverTimestamp(), data: Bytes.fromUint8Array(bytes), }) song.isSaved = true } export const deleteSong = async ( song: QueryDocumentSnapshot, ) => { if (auth.currentUser === null) { throw new Error("You must be logged in to save songs to the cloud") } await deleteDoc(song.data().dataRef) await deleteDoc(song.ref) } export const getCurrentUserSongs = async () => { if (auth.currentUser === null) { throw new Error("You must be logged in to get songs from the cloud") } return await getDocs( query(songCollection, where("userId", "==", auth.currentUser.uid)), ) }