<# Joue la migration Mongo -> Postgres. Sans -Apply : dry run global (n'ecrit rien). Avec -Apply : joue pour de vrai, instance par instance, de la plus petite a la plus grande — c'est ce qui fait qu'un defaut casse sur le plus petit perimetre possible. Chaque appel est une transaction unique : soit tout, soit rien. ⚠️ Un dry run ne peut detecter AUCUNE violation de contrainte : il n'ecrit rien, donc SaveChanges n'est jamais appele. Un NOT NULL viole ne sort qu'avec -Apply. C'est arrive le 07/09/2026 (LastName null sur test@email.be). ⚠️ Compter 2 a 5 min : la sonde HEAD interroge chaque blob Firebase, 30 a la fois. Elle tourne AUSSI en dry run. #> param( [switch]$Apply, [string]$InstanceId, [string]$BaseUrl = "http://localhost:5000" ) $ErrorActionPreference = "Stop" . "$PSScriptRoot\_common.ps1" $jwt = Get-Jwt $PSScriptRoot $headers = @{ Authorization = "Bearer $jwt" } function Invoke-Migration($dryRun, $instance) { $uri = "$BaseUrl/api/migration/run?dryRun=$($dryRun.ToString().ToLower())" if ($instance) { $uri += "&instanceId=$instance" } $label = if ($dryRun) { "DRY RUN" } else { "REEL" } if ($instance) { $label += " / $instance" } Write-Host "=== $label ===" -ForegroundColor Cyan $t0 = Get-Date try { $r = Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -ContentType "application/json" -Body "{}" } catch { Write-Host " ECHEC HTTP : $(Get-HttpErrorBody $_)" -ForegroundColor Red Write-Host " Un 500 signifie que la transaction a ete ANNULEE : la base est intacte." -ForegroundColor Yellow return $false } $sec = [int]((Get-Date) - $t0).TotalSeconds Write-Host " duree : $sec s" -ForegroundColor Gray $m = $r.migrated ($m.PSObject.Properties | Where-Object { $_.Value -is [int] -and $_.Value -gt 0 } | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ", " | ForEach-Object { Write-Host " migre : $_" } Write-Host " erreurs : $($r.errors.Count) | signalees : $($r.skipped.Count)" $r.errors | Select-Object -First 5 | ForEach-Object { Write-Host " ! $_" -ForegroundColor Yellow } if ($r.fatalError) { Write-Host " FATAL : $($r.fatalError)" -ForegroundColor Red; return $false } Write-Host "" return $true } if (-not $Apply) { Write-Host "Mode simulation. Ajouter -Apply pour ecrire." -ForegroundColor Yellow Invoke-Migration $true $InstanceId | Out-Null exit 0 } if ($InstanceId) { if (Invoke-Migration $false $InstanceId) { exit 0 } else { exit 1 } } # ⚠️ L'API ne donne pas le volume par instance : cet ordre est ALPHABETIQUE, # pas croissant. Or jouer la plus petite d'abord est ce qui fait qu'un defaut # casse sur le plus petit perimetre — c'est ce qui a permis de trouver le # LastName null sur 1 utilisateur au lieu de 10, le 07/09/2026. # Pour maitriser l'ordre, enchainer les appels avec -InstanceId a la main. Write-Host "Ordre ALPHABETIQUE (l'API ne donne pas les volumes)." -ForegroundColor Yellow Write-Host "Pour commencer par la plus petite, appeler -InstanceId une par une." -ForegroundColor Yellow Write-Host "" $ids = (Invoke-RestMethod -Uri "$BaseUrl/api/Instance" -Headers $headers) | Sort-Object { $_.name } | Select-Object -ExpandProperty id foreach ($id in $ids) { if (-not (Invoke-Migration $false $id)) { exit 1 } } Write-Host "Toutes les instances sont passees." -ForegroundColor Green