From 4d59f371ac83c3ab7a139fcc3235007476f2e4c7 Mon Sep 17 00:00:00 2001 From: Gato Date: Sat, 6 Jun 2026 09:54:10 +0200 Subject: [PATCH] fix: crash SQLiteConstraintException lors de la sync Bonsai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BonsaiApiClient : optLong/optString avec fallback pour éviter JSONException si l'API omet projectId, type, status ou priority dans la réponse - BonsaiSyncManager : enveloppe sync() dans runCatching pour ne jamais propager d'exception non gérée ; chaque insert de tâche est aussi isolé pour que les erreurs individuelles ne bloquent pas toute la sync Co-Authored-By: Claude Sonnet 4.6 --- app/build.gradle.kts | 2 +- .../mobile/data/bonsai/BonsaiApiClient.kt | 16 ++++++++-------- .../mobile/data/bonsai/BonsaiSyncManager.kt | 18 ++++++++++++------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1d67e5b..a125908 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,7 +16,7 @@ android { minSdk = 26 targetSdk = 35 versionCode = 1 - versionName = "0.0.11" + versionName = "0.0.12" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiApiClient.kt b/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiApiClient.kt index 2c3247f..37b7d97 100644 --- a/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiApiClient.kt +++ b/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiApiClient.kt @@ -40,7 +40,7 @@ class BonsaiApiClient @Inject constructor( val o = arr.getJSONObject(i) BonsaiIssueDto( id = o.getLong("id"), - projectId = o.getLong("projectId"), + projectId = o.optLong("projectId", projectId), // fallback = the project we queried type = o.getString("type"), name = o.getString("name"), status = o.getString("status"), @@ -71,12 +71,12 @@ class BonsaiApiClient @Inject constructor( suspend fun createIssue(projectId: Long, req: BonsaIssueRequest): ApiResult = post("projects/$projectId/issues", req.toJson()) { o -> - o.toIssueDto() + o.toIssueDto(fallbackProjectId = projectId) } suspend fun updateIssue(projectId: Long, issueId: Long, req: BonsaIssueRequest): ApiResult = put("projects/$projectId/issues/$issueId", req.toJson()) { o -> - o.toIssueDto() + o.toIssueDto(fallbackProjectId = projectId) } suspend fun deleteIssue(projectId: Long, issueId: Long): ApiResult = withContext(Dispatchers.IO) { @@ -146,13 +146,13 @@ class BonsaiApiClient @Inject constructor( description?.let { put("description", it) } }.toString() - private fun JSONObject.toIssueDto() = BonsaiIssueDto( + private fun JSONObject.toIssueDto(fallbackProjectId: Long = 0L) = BonsaiIssueDto( id = getLong("id"), - projectId = getLong("projectId"), - type = getString("type"), + projectId = optLong("projectId", fallbackProjectId), + type = optString("type", "Task"), name = getString("name"), - status = getString("status"), - priority = getString("priority"), + status = optString("status", "todo"), + priority = optString("priority", "MOYENNE"), assignee = optString("assignee").takeIf { it.isNotEmpty() }, dueDate = optString("dueDate").takeIf { it.isNotEmpty() }, description = optString("description").takeIf { it.isNotEmpty() }, diff --git a/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiSyncManager.kt b/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiSyncManager.kt index 1f72304..9a3f460 100644 --- a/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiSyncManager.kt +++ b/app/src/main/java/com/planify/mobile/data/bonsai/BonsaiSyncManager.kt @@ -30,7 +30,10 @@ class BonsaiSyncManager @Inject constructor( private val taskRepository: TaskRepository, private val labelRepository: LabelRepository, ) { - suspend fun sync(): SyncResult { + suspend fun sync(): SyncResult = runCatching { doSync() } + .getOrElse { SyncResult.Failure(it.message ?: "Erreur interne") } + + private suspend fun doSync(): SyncResult { if (!authManager.isLoggedIn) return SyncResult.NotLoggedIn val projectsResult = apiClient.getProjects() @@ -89,11 +92,14 @@ class BonsaiSyncManager @Inject constructor( // Sync tasks (issues of type Task) allTaskIssues.forEach { issue -> - val labels = milestonesByIssueId[issue.id] ?: emptyList() - val task = issue.toTask(labels, now) - val existing = taskRepository.getTaskById(task.id) - if (existing == null) taskRepository.insertTask(task) - else taskRepository.updateTask(task) + runCatching { + val labels = milestonesByIssueId[issue.id] ?: emptyList() + val task = issue.toTask(labels, now) + val existing = taskRepository.getTaskById(task.id) + if (existing == null) taskRepository.insertTask(task) + else taskRepository.updateTask(task) + } + // Individual task failures are skipped; the next sync will retry. } return SyncResult.Success