diff --git a/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSource.kt b/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSource.kt new file mode 100644 index 00000000..329c3a3a --- /dev/null +++ b/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSource.kt @@ -0,0 +1,54 @@ +package com.intelligentbackpack.desktopdata.datasource + +import com.intelligentbackpack.accessdomain.entities.Email +import com.intelligentbackpack.accessdomain.entities.User +import com.intelligentbackpack.desktopdomain.entities.Book +import com.intelligentbackpack.desktopdomain.entities.Desktop +import com.intelligentbackpack.desktopdomain.entities.SchoolSupply +import kotlinx.coroutines.flow.Flow + +/** + * Interface for the desktop remote data source. + */ +interface DesktopRemoteDataSource { + /** + * Gets a book by its ISBN. + * + * @param isbn The ISBN of the book. + * @return The book if it exists. + */ + fun getBook(isbn: String): Book? + + /** + * Gets the desktop. + * + * @param user The user. + * @return The desktop. + */ + fun getDesktop(user: User): Desktop + + /** + * Adds a school supply to the desktop. + * + * @param user The user. + * @param schoolSupply The school supply to add. + */ + fun addSchoolSupply(user: User, schoolSupply: SchoolSupply) + + /** + * Subscribes to the backpack. + * + * @param email the email that subscribe to the backpack + * @param backpack the backpack to subscribe + * @return a [Flow] with the set of rfid of school supplies in the backpack + */ + fun subscribeToBackpackChanges(email: Email, backpack: String): Flow>> + + /** + * Associates a backpack to a user. + * + * @param user the user to associate the backpack + * @param hash the hash of the backpack to associate + */ + fun associateBackpack(user: User, hash: String) +} \ No newline at end of file diff --git a/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSourceImpl.kt b/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSourceImpl.kt new file mode 100644 index 00000000..ca3fdeec --- /dev/null +++ b/desktopData/src/main/kotlin/com/intelligentbackpack/desktopdata/datasource/DesktopRemoteDataSourceImpl.kt @@ -0,0 +1,139 @@ +package com.intelligentbackpack.desktopdata.datasource + +import book.communication.BuyBook +import com.google.firebase.database.DataSnapshot +import com.google.firebase.database.DatabaseError +import com.google.firebase.database.ValueEventListener +import com.google.firebase.database.ktx.database +import com.google.firebase.ktx.Firebase +import com.intelligentbackpack.accessdomain.entities.Email +import com.intelligentbackpack.accessdomain.entities.User +import com.intelligentbackpack.desktopdata.adapter.BookAdapter.fromRemoteToDomain +import com.intelligentbackpack.desktopdata.adapter.SchoolSupplyAdapter.fromRemoteToDomain +import com.intelligentbackpack.desktopdata.api.BackpackApi +import com.intelligentbackpack.desktopdata.api.DesktopApi +import com.intelligentbackpack.desktopdomain.entities.Book +import com.intelligentbackpack.desktopdomain.entities.BookCopy +import com.intelligentbackpack.desktopdomain.entities.Desktop +import com.intelligentbackpack.desktopdomain.entities.SchoolSupply +import com.intelligentbackpack.desktopdomain.policies.RFIDPolicy +import com.intelligentbackpack.networkutility.DownloadException +import com.intelligentbackpack.networkutility.ErrorHandler.getError +import com.intelligentbackpack.networkutility.RetrofitHelper +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.runBlocking + +class DesktopRemoteDataSourceImpl( + baseUrl: String, + backpackUrl: String, + realtimeUrl: String, +) : DesktopRemoteDataSource { + + private val desktopApi = RetrofitHelper.getInstance(baseUrl).create(DesktopApi::class.java) + private val backpackApi = RetrofitHelper.getInstance(backpackUrl).create(BackpackApi::class.java) + private val database = Firebase.database(realtimeUrl) + override fun getBook(isbn: String): Book? { + val response = desktopApi.getBook(isbn).execute() + if (response.isSuccessful) { + return response.body()?.fromRemoteToDomain() + } else { + throw DownloadException(getError(response)) + } + } + + override fun getDesktop(user: User): Desktop { + val response = desktopApi.getLibrary(user.email).execute() + if (response.isSuccessful) { + val books = response.body() + ?.copiesList + ?.mapNotNull { getBook(it.isbn) } + ?.toSet() + ?: emptySet() + val copies = response.body() + ?.copiesList + ?.map { it.fromRemoteToDomain(books) } + ?.toSet() + ?: emptySet() + val backpacks = database.reference + .child(user.email) + .get().result + val hasBackpack = backpacks.key != null + val supplyInBackpack = backpacks.children + .asSequence() + .mapNotNull { it.getValue(String::class.java) } + .map { it.uppercase() } + .filter { RFIDPolicy.isValid(it) } + .toSet() + return Desktop.create( + schoolSupplies = copies, + schoolSuppliesInBackpack = copies.filter { it.rfidCode in supplyInBackpack }.toSet(), + backpackAssociated = hasBackpack, + ) + } else { + throw DownloadException(getError(response)) + } + } + + override fun addSchoolSupply(user: User, schoolSupply: SchoolSupply) { + if (schoolSupply is BookCopy) { + val copy = BuyBook.newBuilder().apply { + isbn = schoolSupply.book.isbn + rfid = schoolSupply.rfidCode + emailCompratore = user.email + }.build() + val response = desktopApi.addBookCopy(copy).execute() + if (!response.isSuccessful) { + throw DownloadException(getError(response)) + } + } else { + throw DownloadException("School supply not supported") + } + } + + @ExperimentalCoroutinesApi + override fun subscribeToBackpackChanges(email: Email, backpack: String) = + callbackFlow>> { + val postListener = object : ValueEventListener { + override fun onCancelled(error: DatabaseError) { + runBlocking { + this@callbackFlow.send(Result.failure(error.toException())) + } + } + + override fun onDataChange(dataSnapshot: DataSnapshot) { + val items = dataSnapshot.children.map { it.key } + runBlocking { + this@callbackFlow.send( + Result.success( + items + .filterNotNull() + .map { it.uppercase() } + .filter { RFIDPolicy.isValid(it) } + .toSet() + ) + ) + } + } + } + database.reference + .child(email) + .child(backpack) + .addValueEventListener(postListener) + + awaitClose { + database.reference + .child(email) + .child(backpack) + .removeEventListener(postListener) + } + } + + override fun associateBackpack(user: User, hash: String) { + val response = backpackApi.associateBackpack(user.email, hash).execute() + if (!response.isSuccessful) { + throw DownloadException(getError(response)) + } + } +}