'boolean', 'last_used_at' => 'datetime', 'last_error_at' => 'datetime', ]; /** * 플랫폼 상수 */ public const PLATFORM_IOS = 'ios'; public const PLATFORM_ANDROID = 'android'; public const PLATFORM_WEB = 'web'; /** * 사용자 관계 */ public function user(): BelongsTo { return $this->belongsTo(User::class); } /** * 테넌트 관계 */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } /** * Scope: 활성 토큰만 */ public function scopeActive($query) { return $query->where('is_active', true); } /** * Scope: 플랫폼별 필터 */ public function scopePlatform($query, string $platform) { return $query->where('platform', $platform); } /** * Scope: 특정 사용자의 토큰 */ public function scopeForUser($query, int $userId) { return $query->where('user_id', $userId); } /** * Scope: 특정 테넌트의 토큰 (global scope 무시) */ public function scopeForTenant($query, int $tenantId) { return $query->where('tenant_id', $tenantId); } /** * Scope: 에러가 있는 토큰 */ public function scopeHasError($query) { return $query->whereNotNull('last_error'); } /** * 에러 정보 기록 */ public function recordError(string $errorCode): void { $this->update([ 'last_error' => $errorCode, 'last_error_at' => now(), ]); } /** * 토큰 비활성화 (에러와 함께) */ public function deactivate(?string $errorCode = null): void { $data = ['is_active' => false]; if ($errorCode) { $data['last_error'] = $errorCode; $data['last_error_at'] = now(); } $this->update($data); } }