This commit is contained in:
hashbro
2026-09-12 03:53:21 +08:00
parent 8c5724ff3b
commit b4f0dde628
2 changed files with 178 additions and 4 deletions
@@ -10,9 +10,29 @@ use Illuminate\Support\Facades\DB;
*
* Only 32-char hex values are real UUIDs — shorter IDs (e.g. old xxbb
* 16-hex device IDs) are left untouched.
*
* If a plain-hex device and a dashed device coexist (because the dashed
* one was created by DarkSwordIngestAdapter after normalizeDarkswordKey was
* introduced), the plain-hex duplicate is merged into the dashed one: all
* child rows (photos, events, apps, …) are reparented to the dashed device,
* then the plain-hex row is deleted.
*/
return new class extends Migration
{
/** Tables that reference devices.id via device_id FK. */
private const CHILD_TABLES = [
'device_apps',
'device_events',
'ds_beacon_tasks',
'notes',
'photos',
'plugin_sessions',
'sms_reports',
'wallet_addresses',
'wallet_keystores',
'wallet_mnemonics',
];
private function dashedKey(string $plain): ?string
{
$hex = strtoupper(preg_replace('/[^0-9A-Fa-f]/', '', $plain) ?? '');
@@ -23,20 +43,74 @@ return new class extends Migration
return substr($hex, 0, 8).'-'.substr($hex, 8, 4).'-'.substr($hex, 12, 4).'-'.substr($hex, 16, 4).'-'.substr($hex, 20, 12);
}
/**
* Returns true if the given string is a 32-char plain hex value
* (no dashes) — i.e. the "before" state that needs conversion.
*/
private function isPlainHex(string $value): bool
{
$trimmed = strtoupper(trim($value));
return strlen($trimmed) === 32 && ctype_xdigit($trimmed);
}
private function convertColumn(string $table, string $column): void
{
$rows = DB::table($table)->get(['id', $column]);
foreach ($rows as $row) {
$dashed = $this->dashedKey((string) $row->{$column});
if ($dashed !== null) {
DB::table($table)->where('id', $row->id)->update([$column => $dashed]);
$value = (string) $row->{$column};
if (! $this->isPlainHex($value)) {
continue;
}
$dashed = $this->dashedKey($value);
if ($dashed === null) {
continue;
}
DB::table($table)->where('id', $row->id)->update([$column => $dashed]);
}
}
/**
* Merge the plain-hex device (loser) into the dashed device (winner):
* reparent all child rows, then delete the loser.
*/
private function mergeDevice(int $loserId, int $winnerId): void
{
foreach (self::CHILD_TABLES as $table) {
DB::table($table)->where('device_id', $loserId)->update(['device_id' => $winnerId]);
}
DB::table('devices')->where('id', $loserId)->delete();
}
private function convertDevices(): void
{
// Find all plain-hex device_ids that need conversion.
$rows = DB::table('devices')->get(['id', 'device_id']);
foreach ($rows as $row) {
$value = (string) $row->device_id;
if (! $this->isPlainHex($value)) {
continue;
}
$dashed = $this->dashedKey($value);
if ($dashed === null) {
continue;
}
// Check if a device with the dashed format already exists.
$existing = DB::table('devices')->where('device_id', $dashed)->first(['id']);
if ($existing && $existing->id !== $row->id) {
// Merge loser (plain-hex) into winner (dashed).
$this->mergeDevice($row->id, $existing->id);
} else {
// No conflict — safe to update.
DB::table('devices')->where('id', $row->id)->update(['device_id' => $dashed]);
}
}
}
public function up(): void
{
$this->convertColumn('devices', 'device_id');
$this->convertDevices();
$this->convertColumn('page_visits', 'client_uid');
$this->convertColumn('ds_chain_logs', 'client_uid');
}