refactor(android): share auth and clean uploaded finding photos
This commit is contained in:
+9
-34
@@ -4,15 +4,12 @@ import android.content.Context
|
|||||||
import com.korexlabs.dhinspeccion.BuildConfig
|
import com.korexlabs.dhinspeccion.BuildConfig
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import retrofit2.HttpException
|
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
@@ -165,18 +162,14 @@ private interface FieldFindingsApi {
|
|||||||
@Part("accuracyM") accuracyM: RequestBody?,
|
@Part("accuracyM") accuracyM: RequestBody?,
|
||||||
@Part("deviceLabel") deviceLabel: RequestBody,
|
@Part("deviceLabel") deviceLabel: RequestBody,
|
||||||
): FieldFindingEvidence
|
): FieldFindingEvidence
|
||||||
|
|
||||||
@POST("auth/mobile/refresh")
|
|
||||||
suspend fun refresh(@Body request: RefreshRequest): MobileSessionResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cliente de campo para Hallazgos y sus evidencias append-only.
|
* Cliente de campo para Hallazgos y sus evidencias append-only.
|
||||||
* F3.2 exige que la APK identifique explícitamente el Acta activa.
|
* F5 comparte autenticación con Inventario y Actas para serializar refresh tokens rotativos.
|
||||||
*/
|
*/
|
||||||
class FieldFindingsRepository(context: Context) {
|
class FieldFindingsRepository(context: Context) {
|
||||||
private val store = SecureSessionStore(context.applicationContext)
|
private val sessions = MobileSessionCoordinator.get(context.applicationContext)
|
||||||
private val refreshMutex = Mutex()
|
|
||||||
private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
|
private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
|
||||||
private val api: FieldFindingsApi = Retrofit.Builder()
|
private val api: FieldFindingsApi = Retrofit.Builder()
|
||||||
.baseUrl(BuildConfig.API_BASE_URL)
|
.baseUrl(BuildConfig.API_BASE_URL)
|
||||||
@@ -186,7 +179,7 @@ class FieldFindingsRepository(context: Context) {
|
|||||||
.create(FieldFindingsApi::class.java)
|
.create(FieldFindingsApi::class.java)
|
||||||
|
|
||||||
suspend fun options(visitId: String, assetId: String, actId: String): FieldFindingOptionsResponse =
|
suspend fun options(visitId: String, assetId: String, actId: String): FieldFindingOptionsResponse =
|
||||||
authorized { session ->
|
sessions.authorized { session ->
|
||||||
api.options("Bearer ${session.accessToken}", visitId, assetId, actId)
|
api.options("Bearer ${session.accessToken}", visitId, assetId, actId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,11 +187,11 @@ class FieldFindingsRepository(context: Context) {
|
|||||||
visitId: String,
|
visitId: String,
|
||||||
assetId: String,
|
assetId: String,
|
||||||
request: CreateFieldFindingRequest,
|
request: CreateFieldFindingRequest,
|
||||||
): FieldFindingCreateResponse = authorized { session ->
|
): FieldFindingCreateResponse = sessions.authorized { session ->
|
||||||
api.create("Bearer ${session.accessToken}", visitId, assetId, request)
|
api.create("Bearer ${session.accessToken}", visitId, assetId, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun evidence(findingId: String): FieldFindingEvidenceListResponse = authorized { session ->
|
suspend fun evidence(findingId: String): FieldFindingEvidenceListResponse = sessions.authorized { session ->
|
||||||
api.evidence("Bearer ${session.accessToken}", findingId)
|
api.evidence("Bearer ${session.accessToken}", findingId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +204,8 @@ class FieldFindingsRepository(context: Context) {
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
capturedAt: String = Instant.now().toString(),
|
capturedAt: String = Instant.now().toString(),
|
||||||
): FieldFindingEvidence = authorized { session ->
|
): FieldFindingEvidence {
|
||||||
|
val response = sessions.authorized { session ->
|
||||||
val text = "text/plain".toMediaType()
|
val text = "text/plain".toMediaType()
|
||||||
val part = MultipartBody.Part.createFormData(
|
val part = MultipartBody.Part.createFormData(
|
||||||
"file",
|
"file",
|
||||||
@@ -233,26 +227,7 @@ class FieldFindingsRepository(context: Context) {
|
|||||||
deviceLabel = "DH Android".toRequestBody(text),
|
deviceLabel = "DH Android".toRequestBody(text),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
file.delete()
|
||||||
private suspend fun <T> authorized(block: suspend (StoredSession) -> T): T {
|
return response
|
||||||
var session = store.load() ?: throw IllegalStateException("Sesión no iniciada")
|
|
||||||
try {
|
|
||||||
return block(session)
|
|
||||||
} catch (error: HttpException) {
|
|
||||||
if (error.code() != 401) throw error
|
|
||||||
}
|
|
||||||
session = refresh(session.refreshToken)
|
|
||||||
return block(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun refresh(previousRefreshToken: String): StoredSession = refreshMutex.withLock {
|
|
||||||
val latest = store.load() ?: throw IllegalStateException("Sesión no iniciada")
|
|
||||||
if (latest.refreshToken != previousRefreshToken) return@withLock latest
|
|
||||||
try {
|
|
||||||
store.save(api.refresh(RefreshRequest(previousRefreshToken)))
|
|
||||||
} catch (error: Throwable) {
|
|
||||||
store.clear()
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user