From 516445fea6d635a04477e129d4adaf06ce8539f8 Mon Sep 17 00:00:00 2001 From: enlineawork Date: Tue, 8 Sep 2026 17:16:03 -0300 Subject: [PATCH] refactor(android): share auth and clean uploaded finding photos --- .../dhinspeccion/data/FieldFindingsMobile.kt | 83 +++++++------------ 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldFindingsMobile.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldFindingsMobile.kt index 6690364..05dd653 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldFindingsMobile.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/FieldFindingsMobile.kt @@ -4,15 +4,12 @@ import android.content.Context import com.korexlabs.dhinspeccion.BuildConfig import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import okhttp3.MediaType.Companion.toMediaType import okhttp3.MultipartBody import okhttp3.OkHttpClient import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody -import retrofit2.HttpException import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.Body @@ -165,18 +162,14 @@ private interface FieldFindingsApi { @Part("accuracyM") accuracyM: RequestBody?, @Part("deviceLabel") deviceLabel: RequestBody, ): FieldFindingEvidence - - @POST("auth/mobile/refresh") - suspend fun refresh(@Body request: RefreshRequest): MobileSessionResponse } /** * 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) { - private val store = SecureSessionStore(context.applicationContext) - private val refreshMutex = Mutex() + private val sessions = MobileSessionCoordinator.get(context.applicationContext) private val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() private val api: FieldFindingsApi = Retrofit.Builder() .baseUrl(BuildConfig.API_BASE_URL) @@ -186,7 +179,7 @@ class FieldFindingsRepository(context: Context) { .create(FieldFindingsApi::class.java) suspend fun options(visitId: String, assetId: String, actId: String): FieldFindingOptionsResponse = - authorized { session -> + sessions.authorized { session -> api.options("Bearer ${session.accessToken}", visitId, assetId, actId) } @@ -194,11 +187,11 @@ class FieldFindingsRepository(context: Context) { visitId: String, assetId: String, request: CreateFieldFindingRequest, - ): FieldFindingCreateResponse = authorized { session -> + ): FieldFindingCreateResponse = sessions.authorized { session -> 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) } @@ -211,48 +204,30 @@ class FieldFindingsRepository(context: Context) { title: String? = null, description: String? = null, capturedAt: String = Instant.now().toString(), - ): FieldFindingEvidence = authorized { session -> - val text = "text/plain".toMediaType() - val part = MultipartBody.Part.createFormData( - "file", - file.name, - file.asRequestBody("image/jpeg".toMediaType()), - ) - api.uploadEvidence( - authorization = "Bearer ${session.accessToken}", - findingId = findingId, - file = part, - kind = "PHOTO".toRequestBody(text), - purpose = "OBSERVATION".toRequestBody(text), - title = title?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text), - description = description?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text), - capturedAt = capturedAt.toRequestBody(text), - latitude = latitude.toString().toRequestBody(text), - longitude = longitude.toString().toRequestBody(text), - accuracyM = accuracyM?.toString()?.toRequestBody(text), - deviceLabel = "DH Android".toRequestBody(text), - ) - } - - private suspend fun authorized(block: suspend (StoredSession) -> T): T { - 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 + ): FieldFindingEvidence { + val response = sessions.authorized { session -> + val text = "text/plain".toMediaType() + val part = MultipartBody.Part.createFormData( + "file", + file.name, + file.asRequestBody("image/jpeg".toMediaType()), + ) + api.uploadEvidence( + authorization = "Bearer ${session.accessToken}", + findingId = findingId, + file = part, + kind = "PHOTO".toRequestBody(text), + purpose = "OBSERVATION".toRequestBody(text), + title = title?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text), + description = description?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text), + capturedAt = capturedAt.toRequestBody(text), + latitude = latitude.toString().toRequestBody(text), + longitude = longitude.toString().toRequestBody(text), + accuracyM = accuracyM?.toString()?.toRequestBody(text), + deviceLabel = "DH Android".toRequestBody(text), + ) } + file.delete() + return response } }