diff --git a/app/Http/Controllers/Api/V1/SyncController.php b/app/Http/Controllers/Api/V1/SyncController.php index cf1b7a3..ea4a011 100644 --- a/app/Http/Controllers/Api/V1/SyncController.php +++ b/app/Http/Controllers/Api/V1/SyncController.php @@ -52,6 +52,20 @@ class SyncController extends Controller { $uuid = $op['uuid']; + // The client sends `client_updated_at` as ISO-8601 with milliseconds and a + // trailing Z (e.g. 2026-07-21T10:10:25.006Z). MySQL's DATETIME/TIMESTAMP + // columns reject that literal, so normalise it to `Y-m-d H:i:s` (UTC) once + // here, before it reaches any handler. Invalid/empty values become null. + if (! empty($op['client_updated_at'])) { + try { + $op['client_updated_at'] = Carbon::parse($op['client_updated_at']) + ->timezone(config('app.timezone')) + ->format('Y-m-d H:i:s'); + } catch (\Throwable) { + $op['client_updated_at'] = null; + } + } + // Op-level idempotency: if this operation was already applied, replay its result. $prior = SyncLog::where('op_uuid', $uuid) ->where('entity', $op['entity'])->where('op', $op['op'])->first();