feat(CON-223) : Ajouter l'auth

feat(CON-262) : Aligner les flux avec les nouveaux dev back
This commit is contained in:
Louis Legrand 2025-02-11 14:48:19 +01:00
parent 5303e594da
commit c57c9b7d05
25 changed files with 146 additions and 60 deletions

View File

@ -72,6 +72,10 @@ android {
dimension = "version"
}
create("dev") {
dimension = "version"
}
create("demo") {
dimension = "version"
}

View File

@ -14,7 +14,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import fr.openium.consentium.R
import fr.openium.consentium.api.Consentium
import fr.openium.consentium.api.state.FetchConsentiumState
@ -25,14 +27,17 @@ fun SplashScreen(
) {
// Property
val context = LocalContext.current
val consentium = remember { Consentium(context = context, applicationId = "DemoApplicationId") }
val consentiumKey = stringResource(R.string.consentium_api_key)
val consentium = remember { Consentium(context = context, applicationId = consentiumKey) }
// Effect
LaunchedEffect(Unit) {
consentium.fetchConsentState.collect { consentState ->
when (consentState) {
FetchConsentiumState.Idle,
FetchConsentiumState.Loading -> {}
FetchConsentiumState.Loading,
-> {
}
FetchConsentiumState.Error -> {
// Handle error

View File

@ -1,3 +1,4 @@
<resources>
<string name="app_name">Consentium</string>
<string translatable="false" name="consentium_api_key">01938ce4-331a-7592-9e90-f09201ff4f36</string>
</resources>

View File

@ -34,6 +34,10 @@ android {
dimension = "version"
}
create("dev") {
dimension = "version"
}
create("demo") {
dimension = "version"
}

View File

@ -34,6 +34,10 @@ android {
dimension = "version"
}
create("dev") {
dimension = "version"
}
create("demo") {
dimension = "version"
}

View File

@ -2,6 +2,7 @@ package fr.openium.consentium.api
import android.content.Context
import dagger.hilt.android.EntryPointAccessors
import fr.openium.consentium.api.model.ConsentState
import fr.openium.consentium.api.model.PurposeChoice
import fr.openium.consentium.api.state.FetchConsentiumState
import fr.openium.consentium.api.state.SetConsentiumState
@ -35,7 +36,7 @@ class Consentium(
ConsentiumRepositoryGetResponse.Error -> _fetchConsentState.value = FetchConsentiumState.Error
is ConsentiumRepositoryGetResponse.GetConsentsSuccess -> {
val areConsentsValid = consentResponse.isValid
val areConsentsValid = consentResponse.state == ConsentState.VALID
if (areConsentsValid) {
_fetchConsentState.value = FetchConsentiumState.Valid(purposes = consentResponse.purposes)
} else {

View File

@ -0,0 +1,11 @@
package fr.openium.consentium.api.adapter
import fr.openium.consentium.api.model.ConsentState
import fr.openium.consentium.data.remote.model.GetConsent
internal fun GetConsent.ConsentStateDTO.toConsentState(): ConsentState = when (this) {
GetConsent.ConsentStateDTO.UNSET -> ConsentState.UNSET
GetConsent.ConsentStateDTO.VALID -> ConsentState.VALID
GetConsent.ConsentStateDTO.EXPIRED -> ConsentState.EXPIRED
GetConsent.ConsentStateDTO.NEW_VERSION -> ConsentState.NEW_VERSION
}

View File

@ -5,7 +5,6 @@ import fr.openium.consentium.data.remote.model.GetConsent
internal fun GetConsent.PurposeDTO.toPurpose() = Purpose(
identifier = identifier,
isRequired = isRequired,
isAccepted = isAccepted.toPurposeStatus(),
choice = choice.toPurposeStatus(),
vendors = vendors?.map { it.toVendor() } ?: emptyList(),
)

View File

@ -4,7 +4,7 @@ import fr.openium.consentium.api.model.PurposeChoice
import fr.openium.consentium.data.remote.model.PatchConsent
internal fun PurposeChoice.toPatchConsentPurposeDTO() = PatchConsent.PurposeDTO(
identifier = purposeIdentifier.toString(),
isAccepted = isAccepted,
identifier = purposeIdentifier,
choice = choice.toPurposeStatusDTO(),
vendors = vendors.map { it.toPatchConsentVendorDTO() }
)

View File

@ -2,9 +2,16 @@ package fr.openium.consentium.api.adapter
import fr.openium.consentium.api.model.PurposeStatus
import fr.openium.consentium.data.remote.model.GetConsent
import fr.openium.consentium.data.remote.model.PatchConsent
internal fun GetConsent.PurposeStatusDTO.toPurposeStatus() = when (this) {
GetConsent.PurposeStatusDTO.ACCEPTED -> PurposeStatus.ACCEPTED
GetConsent.PurposeStatusDTO.REJECTED -> PurposeStatus.REJECTED
GetConsent.PurposeStatusDTO.NOT_DEFINED -> PurposeStatus.NOT_DEFINED
GetConsent.PurposeStatusDTO.PARTIAL -> PurposeStatus.PARTIAL
}
internal fun PurposeStatus.toPurposeStatusDTO() = when (this) {
PurposeStatus.ACCEPTED -> PatchConsent.PurposeStatusDTO.ACCEPTED
PurposeStatus.REJECTED -> PatchConsent.PurposeStatusDTO.REJECTED
PurposeStatus.PARTIAL -> PatchConsent.PurposeStatusDTO.PARTIAL
}

View File

@ -4,6 +4,6 @@ import fr.openium.consentium.api.model.VendorChoice
import fr.openium.consentium.data.remote.model.PatchConsent
internal fun VendorChoice.toPatchConsentVendorDTO() = PatchConsent.VendorDTO(
identifier = vendorIdentifier.toString(),
identifier = vendorIdentifier,
isAccepted = isAccepted,
)

View File

@ -0,0 +1,8 @@
package fr.openium.consentium.api.model
enum class ConsentState {
UNSET,
VALID,
EXPIRED,
NEW_VERSION,
}

View File

@ -2,7 +2,6 @@ package fr.openium.consentium.api.model
data class Purpose(
val identifier: String,
val isRequired: Boolean,
val isAccepted: PurposeStatus,
val choice: PurposeStatus,
val vendors: List<Vendor>,
)

View File

@ -2,6 +2,6 @@ package fr.openium.consentium.api.model
data class PurposeChoice(
val purposeIdentifier: String,
val isAccepted: Boolean,
val choice: PurposeStatus,
val vendors: List<VendorChoice>,
)

View File

@ -3,6 +3,5 @@ package fr.openium.consentium.api.model
enum class PurposeStatus {
ACCEPTED,
REJECTED,
NOT_DEFINED,
UNKNOWN,
PARTIAL,
}

View File

@ -5,20 +5,20 @@ import fr.openium.consentium.data.remote.model.PatchConsent
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.Header
import retrofit2.http.POST
internal interface ConsentiumApi {
@GET("/consents")
suspend fun getConsents(
applicationId: String,
installationId: String,
@Header("Authorization") token: String,
): Response<GetConsent.GetConsentPayloadDTO>
@PATCH("/consents")
@POST("/consents")
suspend fun setConsents(
applicationId: String,
@Header("Authorization") token: String,
@Body patchConsent: PatchConsent.PatchConsentPayloadDTO,
): Response<Any>

View File

@ -10,41 +10,29 @@ import java.util.UUID
internal object ConsentiumMockApi : ConsentiumApi {
private val consents = GetConsent.GetConsentPayloadDTO(
id = UUID.randomUUID().toString(),
installationId = UUID.randomUUID().toString(),
purposes = listOf(
GetConsent.PurposeDTO(
identifier = "purpose-audience",
isRequired = true,
isAccepted = GetConsent.PurposeStatusDTO.ACCEPTED,
vendors = listOf(
GetConsent.VendorDTO(
identifier = "vendor-clarity",
isAccepted = true,
isRequired = true,
),
GetConsent.VendorDTO(
identifier = "vendor-matomo",
isAccepted = true,
isRequired = false,
)
)
vendors = null,
choice = GetConsent.PurposeStatusDTO.ACCEPTED,
),
GetConsent.PurposeDTO(
identifier = "purpose-required",
isRequired = true,
isAccepted = GetConsent.PurposeStatusDTO.REJECTED,
vendors = null
vendors = null,
choice = GetConsent.PurposeStatusDTO.REJECTED,
),
),
isValid = false,
state = GetConsent.ConsentStateDTO.VALID,
)
override suspend fun getConsents(applicationId: String, installationId: String): Response<GetConsent.GetConsentPayloadDTO> {
override suspend fun getConsents(token: String): Response<GetConsent.GetConsentPayloadDTO> {
delay(2000)
return Response.success(consents)
}
override suspend fun setConsents(applicationId: String, patchConsent: PatchConsent.PatchConsentPayloadDTO): Response<Any> {
override suspend fun setConsents(token: String, patchConsent: PatchConsent.PatchConsentPayloadDTO): Response<Any> {
delay(2000)
return Response.success(Unit)
}

View File

@ -7,16 +7,17 @@ internal sealed interface GetConsent {
@Serializable
data class GetConsentPayloadDTO(
@SerialName("id") val id: String,
@SerialName("installationId") val installationId: String,
@SerialName("state") val state: ConsentStateDTO? = null,
@SerialName("acceptedDate") val acceptedData: Long? = null,
@SerialName("purposes") val purposes: List<PurposeDTO>? = null,
@SerialName("isValid") val isValid: Boolean,
)
@Serializable
data class PurposeDTO(
@SerialName("identifier") val identifier: String,
@SerialName("isRequired") val isRequired: Boolean,
@SerialName("isAccepted") val isAccepted: PurposeStatusDTO,
@SerialName("choice") val choice: PurposeStatusDTO,
@SerialName("vendors") val vendors: List<VendorDTO>? = null,
)
@ -29,16 +30,30 @@ internal sealed interface GetConsent {
@Serializable
enum class PurposeStatusDTO {
@SerialName("ACCEPTED")
@SerialName("accepted")
ACCEPTED,
@SerialName("REJECTED")
@SerialName("refused")
REJECTED,
@SerialName("NOT_DEFINED")
NOT_DEFINED,
@SerialName("partial")
PARTIAL,
}
@Serializable
enum class ConsentStateDTO {
@SerialName("unset")
UNSET,
@SerialName("valid")
VALID,
@SerialName("expired")
EXPIRED,
@SerialName("new_version")
NEW_VERSION,
}
}

View File

@ -7,14 +7,13 @@ internal sealed interface PatchConsent {
@Serializable
data class PatchConsentPayloadDTO(
@SerialName("installationId") val installationId: String,
@SerialName("purposes") val purposes: List<PurposeDTO>? = null,
@SerialName("consentPurposes") val purposes: List<PurposeDTO>? = null,
)
@Serializable
data class PurposeDTO(
@SerialName("identifier") val identifier: String,
@SerialName("isAccepted") val isAccepted: Boolean,
@SerialName("choice") val choice: PurposeStatusDTO,
@SerialName("vendors") val vendors: List<VendorDTO>? = null,
)
@ -24,4 +23,16 @@ internal sealed interface PatchConsent {
@SerialName("isAccepted") val isAccepted: Boolean,
)
@Serializable
enum class PurposeStatusDTO {
@SerialName("accepted")
ACCEPTED,
@SerialName("refused")
REJECTED,
@SerialName("partial")
PARTIAL,
}
}

View File

@ -1,22 +1,24 @@
package fr.openium.consentium.domain.repository
import fr.openium.consentium.api.adapter.toConsentState
import fr.openium.consentium.api.adapter.toPatchConsentPurposeDTO
import fr.openium.consentium.api.adapter.toPurpose
import fr.openium.consentium.api.model.ConsentState
import fr.openium.consentium.api.model.Purpose
import fr.openium.consentium.api.model.PurposeChoice
import fr.openium.consentium.data.local.ConsentiumDataStore
import fr.openium.consentium.data.remote.ConsentiumApi
import fr.openium.consentium.data.remote.model.PatchConsent
import fr.openium.consentium.domain.useCase.GetAuthTokenUseCase
import javax.inject.Inject
internal class ConsentiumRepository @Inject constructor(
private val consentiumApi: ConsentiumApi,
private val consentiumDataStore: ConsentiumDataStore,
private val getAuthTokenUseCase: GetAuthTokenUseCase,
) {
suspend fun getConsents(applicationId: String): ConsentiumRepositoryGetResponse {
val installationId = consentiumDataStore.getInstallationUniqueId()
val consentsResponse = consentiumApi.getConsents(applicationId, installationId)
val authToken = getAuthTokenUseCase(applicationId)
val consentsResponse = consentiumApi.getConsents(authToken)
return try {
val consentsBody = if (consentsResponse.isSuccessful) {
consentsResponse.body() ?: throw Exception()
@ -25,7 +27,7 @@ internal class ConsentiumRepository @Inject constructor(
}
return ConsentiumRepositoryGetResponse.GetConsentsSuccess(
isValid = consentsBody.isValid,
state = consentsBody.state?.toConsentState() ?: ConsentState.UNSET,
purposes = consentsBody.purposes?.map { purpose -> purpose.toPurpose() } ?: emptyList()
)
} catch (e: Exception) {
@ -37,12 +39,11 @@ internal class ConsentiumRepository @Inject constructor(
applicationId: String,
consents: List<PurposeChoice>,
): ConsentiumRepositorySetResponse {
val installationId = consentiumDataStore.getInstallationUniqueId()
val authToken = getAuthTokenUseCase(applicationId)
val setConsentResponse = consentiumApi.setConsents(
applicationId = applicationId,
token = authToken,
patchConsent = PatchConsent.PatchConsentPayloadDTO(
installationId = installationId,
purposes = consents.map { it.toPatchConsentPurposeDTO() },
)
)
@ -61,7 +62,7 @@ internal interface ConsentiumRepositoryGetResponse {
data object Error : ConsentiumRepositoryGetResponse
data class GetConsentsSuccess(
val isValid: Boolean,
val state: ConsentState,
val purposes: List<Purpose>,
) : ConsentiumRepositoryGetResponse

View File

@ -0,0 +1,23 @@
package fr.openium.consentium.domain.useCase
import javax.inject.Inject
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
interface GetAuthTokenUseCase {
suspend operator fun invoke(applicationId: String): String
}
class GetAuthTokenUseCaseImpl @Inject internal constructor(
private val getConsentiumUniqueInstallationIdUseCase: GetConsentiumUniqueInstallationIdUseCase,
) : GetAuthTokenUseCase {
@OptIn(ExperimentalEncodingApi::class)
override suspend operator fun invoke(applicationId: String): String {
val uniqueInstallationId = getConsentiumUniqueInstallationIdUseCase()
val clearToken = "$applicationId.$uniqueInstallationId"
val cipheredToken = Base64.Default.encode(clearToken.toByteArray())
return "Bearer $cipheredToken"
}
}

View File

@ -4,14 +4,14 @@ import fr.openium.consentium.data.local.ConsentiumDataStore
import javax.inject.Inject
interface GetConsentiumUniqueInstallationIdUseCase {
suspend fun invoke(): String
suspend operator fun invoke(): String
}
class GetConsentiumUniqueInstallationIdUseCaseImpl @Inject internal constructor(
private val consentiumDataStore: ConsentiumDataStore,
) : GetConsentiumUniqueInstallationIdUseCase {
override suspend fun invoke(): String {
override suspend operator fun invoke(): String {
return consentiumDataStore.getInstallationUniqueId()
}

View File

@ -4,6 +4,8 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import fr.openium.consentium.domain.useCase.GetAuthTokenUseCase
import fr.openium.consentium.domain.useCase.GetAuthTokenUseCaseImpl
import fr.openium.consentium.domain.useCase.GetConsentiumUniqueInstallationIdUseCase
import fr.openium.consentium.domain.useCase.GetConsentiumUniqueInstallationIdUseCaseImpl
@ -16,4 +18,8 @@ interface ConsentiumUseCaseModule {
getUniqueInstallationIdUseCaseImpl: GetConsentiumUniqueInstallationIdUseCaseImpl,
): GetConsentiumUniqueInstallationIdUseCase
@Binds
fun bindGetAuthTokenUseCase(
getAuthTokenUseCaseImpl: GetAuthTokenUseCaseImpl,
): GetAuthTokenUseCase
}