app/src/main/java/com/sdp13epfl2021/projmag/database/impl/firebase/FirebaseUserdataDatabase.kt
package com.sdp13epfl2021.projmag.database.impl.firebase
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.SetOptions
import com.sdp13epfl2021.projmag.curriculumvitae.CurriculumVitae
import com.sdp13epfl2021.projmag.database.interfaces.ProjectId
import com.sdp13epfl2021.projmag.database.interfaces.UserdataDatabase
import com.sdp13epfl2021.projmag.model.*
import javax.inject.Inject
/**
* An implementation of a user-data database
* using Google Firebase/FireStore
*/
class FirebaseUserdataDatabase @Inject constructor(
private val firestore: FirebaseFirestore,
private val auth: FirebaseAuth
) : UserdataDatabase {
companion object {
/**
* Root collection for user-data
*/
const val ROOT = "user-data"
/**
* The field containing favorites
*/
const val FAVORITES_FIELD = "favorites"
/**
* The field containing applied to
*/
const val APPLIED_TO_FIELD = "applied to"
/**
* The field containing cv
*/
const val CV_FIELD = "cv"
/**
* The field containing cv
*/
const val PREF_FIELD = "preferences"
const val USER_PROFILE = "user-profile"
private val AUTH_EXCEPTION: Exception = SecurityException("User needs to be authenticated")
}
/**
* Return the current logged user or null if none
*
* @return current logged user or null
*/
private fun getUser(): FirebaseUser? = auth.currentUser
/**
* Return the document associated to the user
*
* @return document associated to the user
*/
private fun getUserDoc(): DocumentReference? =
getUser()?.let { user ->
firestore
.collection(ROOT)
.document(user.uid)
}
override fun pushFavoriteProject(
projectID: ProjectId,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
pushListOfFavoriteProjects(
listOf(projectID),
onSuccess,
onFailure
)
}
override fun pushListOfFavoriteProjects(
projectIDs: List<ProjectId>,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.let { doc ->
getListOfFavoriteProjects(
{ ls ->
val newList: List<ProjectId> = (ls.toSet().union(projectIDs.toSet())).toList()
doc.set(
hashMapOf(
"favorites" to newList
), SetOptions.merge()
).addOnSuccessListener { onSuccess() }.addOnFailureListener(onFailure)
},
onFailure
)
} ?: onFailure(AUTH_EXCEPTION)
}
@Suppress("UNCHECKED_CAST")
override fun getListOfFavoriteProjects(
onSuccess: (List<ProjectId>) -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.run {
get()
.addOnSuccessListener { doc ->
onSuccess((doc[FAVORITES_FIELD] as? List<ProjectId>) ?: listOf())
}.addOnFailureListener(onFailure)
} ?: onFailure(AUTH_EXCEPTION)
}
override fun removeFromFavorite(
projectID: ProjectId,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.let { doc ->
getListOfFavoriteProjects(
{
val newList = it.filter { pid -> pid != projectID }
doc.update(FAVORITES_FIELD, newList)
.addOnSuccessListener { onSuccess() }
.addOnFailureListener(onFailure)
},
onFailure
)
} ?: onFailure(AUTH_EXCEPTION)
}
override fun pushCv(
cv: CurriculumVitae,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.set(
hashMapOf(CV_FIELD to cv),
SetOptions.merge()
)?.addOnSuccessListener { onSuccess() }?.addOnFailureListener(onFailure)
?: onFailure(AUTH_EXCEPTION)
}
@Suppress("UNCHECKED_CAST")
override fun getCv(
userID: String,
onSuccess: (CurriculumVitae?) -> Unit,
onFailure: (Exception) -> Unit
) {
firestore
.collection(ROOT)
.document(userID)
.get()
.addOnSuccessListener {
try {
val cvMap = it[CV_FIELD] as Map<String, Any>
val summary = cvMap["summary"] as String
val education = convertToPeriodList(cvMap, "education")
val jobExperience = convertToPeriodList(cvMap, "jobExperience")
val languages = convertToLanguageList(cvMap, "languages")
val skills = convertToSkillList(cvMap, "skills")
onSuccess(CurriculumVitae(summary, education, jobExperience, languages, skills))
} catch (e: Exception) {
onFailure(e)
}
}
.addOnFailureListener(onFailure)
}
@Suppress("UNCHECKED_CAST")
private fun getListOfMap(cvMap: Map<String, Any>, fieldName: String): List<Map<String, Any>> {
return cvMap[fieldName] as List<Map<String, Any>>
}
private fun convertToPeriodList(
cvMap: Map<String, Any>,
fieldName: String
): List<CurriculumVitae.PeriodDescription> {
return getListOfMap(cvMap, fieldName).map { map ->
CurriculumVitae.PeriodDescription(
name = map["name"] as String,
location = map["location"] as String,
description = map["description"] as String,
from = (map["from"] as Number).toInt(),
to = (map["to"] as Number).toInt()
)
}
}
private fun convertToLanguageList(
cvMap: Map<String, Any>,
fieldName: String
): List<CurriculumVitae.Language> {
return getListOfMap(cvMap, fieldName).map { map ->
CurriculumVitae.Language(
language = map["language"] as String,
level = CurriculumVitae.Language.Level.valueOf(map["level"] as String)
)
}
}
private fun convertToSkillList(
cvMap: Map<String, Any>,
fieldName: String
): List<CurriculumVitae.SkillDescription> {
return getListOfMap(cvMap, fieldName).map { map ->
CurriculumVitae.SkillDescription(
name = map["name"] as String,
skillLevel = CurriculumVitae.SkillDescription.SkillLevel.valueOf(map["skillLevel"] as String)
)
}
}
override fun applyUnapply(
apply: Boolean,
projectId: ProjectId,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.let { doc ->
val fieldValue = if (apply) {
FieldValue.arrayUnion(projectId)
} else {
FieldValue.arrayRemove(projectId)
}
doc.update(APPLIED_TO_FIELD, fieldValue)
.addOnSuccessListener { onSuccess() }
.addOnFailureListener(onFailure)
} ?: onFailure(AUTH_EXCEPTION)
}
@Suppress("UNCHECKED_CAST")
override fun getListOfAppliedToProjects(
onSuccess: (List<ProjectId>) -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()?.run {
get()
.addOnSuccessListener { doc ->
onSuccess((doc[APPLIED_TO_FIELD] as? List<ProjectId>) ?: listOf())
}.addOnFailureListener(onFailure)
} ?: onFailure(AUTH_EXCEPTION)
}
@Suppress("UNCHECKED_CAST")
override fun getPreferences(
onSuccess: (ProjectFilter?) -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()
?.get()
?.addOnSuccessListener { doc ->
onSuccess(doc?.get(PREF_FIELD)?.let { ProjectFilter(it as Map<String, Any>) })
}?.addOnFailureListener(onFailure)
?: onFailure(AUTH_EXCEPTION)
}
override fun pushPreferences(
pf: ProjectFilter,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
getUserDoc()
?.set(hashMapOf(PREF_FIELD to pf))
?.addOnSuccessListener { onSuccess() }
?.addOnFailureListener(onFailure)
?: onFailure(AUTH_EXCEPTION)
}
override fun uploadProfile(
profile: ImmutableProfile,
onSuccess: () -> Unit,
onFailure: (Exception) -> Unit
) {
val id = getUser()?.uid
if (id != null) {
firestore
.collection(USER_PROFILE)
.document(id)
.set(profile)
.addOnSuccessListener { onSuccess() }
.addOnFailureListener(onFailure)
} else {
onFailure(AUTH_EXCEPTION)
}
}
override fun getProfile(
userID: String,
onSuccess: (profile: ImmutableProfile?) -> Unit,
onFailure: (Exception) -> Unit
) {
firestore
.collection(USER_PROFILE)
.document(userID).get()
.addOnSuccessListener { document ->
if (document != null) {
val firstName = (document["firstName"] as? String)
val lastName = (document["lastName"] as? String)
val age = (document["age"] as? Long)?.toInt()
val sciper = (document["sciper"] as? Long)?.toInt()
val phoneNumber = (document["phoneNumber"] as? String)
val gender = Gender.enumOf(document["gender"] as? String)
val role = when (document["role"] as? String) {
Role.TEACHER.name -> Role.TEACHER
Role.STUDENT.name -> Role.STUDENT
else -> Role.OTHER
}
if (firstName != null && lastName != null && age != null && gender != null && phoneNumber != null) {
when (val resProfile = ImmutableProfile.build(
lastName,
firstName,
age,
gender,
sciper,
phoneNumber,
role
)) {
is Success -> {
onSuccess(resProfile.value)
}
is Failure -> {
onFailure(Exception("Failure reason : ${resProfile.reason}"))
}
}
} else {
onFailure(Exception("At least one of the following fields is null: firstName = $firstName, lastName = $lastName, age = $age, gender = $gender, sciper = $sciper, phoneNumber = $phoneNumber."))
}
} else {
onSuccess(null)
}
}
.addOnFailureListener(onFailure)
}
}