fix: crash SQLiteConstraintException lors de la sync Bonsai
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ android {
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.0.11"
|
||||
versionName = "0.0.12"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BonsaiIssueDto> =
|
||||
post("projects/$projectId/issues", req.toJson()) { o ->
|
||||
o.toIssueDto()
|
||||
o.toIssueDto(fallbackProjectId = projectId)
|
||||
}
|
||||
|
||||
suspend fun updateIssue(projectId: Long, issueId: Long, req: BonsaIssueRequest): ApiResult<BonsaiIssueDto> =
|
||||
put("projects/$projectId/issues/$issueId", req.toJson()) { o ->
|
||||
o.toIssueDto()
|
||||
o.toIssueDto(fallbackProjectId = projectId)
|
||||
}
|
||||
|
||||
suspend fun deleteIssue(projectId: Long, issueId: Long): ApiResult<Unit> = 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() },
|
||||
|
||||
@@ -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,12 +92,15 @@ class BonsaiSyncManager @Inject constructor(
|
||||
|
||||
// Sync tasks (issues of type Task)
|
||||
allTaskIssues.forEach { issue ->
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user