File size: 3,699 Bytes
f23825d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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<FirestoreSong> = {
  fromFirestore(snapshot, options) {
    const data = snapshot.data(options)
    return data as FirestoreSong
  },
  toFirestore(song) {
    return song
  },
}

export const songDataConverter: FirestoreDataConverter<FirestoreSongData> = {
  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<FirestoreSong>,
) => {
  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<FirestoreSong>,
) => {
  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)),
  )
}