lthn.io/app/Core/Activity/Models/Activity.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

204 lines
5.9 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Activity\Models;
use Core\Activity\Scopes\ActivityScopes;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity as SpatieActivity;
/**
* Extended Activity model with workspace-aware scopes.
*
* This model extends Spatie's Activity model to add workspace scoping
* and additional query scopes for the Core PHP framework.
*
* To use this model instead of Spatie's default, add to your
* config/activitylog.php:
*
* 'activity_model' => \Core\Activity\Models\Activity::class,
*
* @method static \Illuminate\Database\Eloquent\Builder forWorkspace(\Illuminate\Database\Eloquent\Model|int $workspace)
* @method static \Illuminate\Database\Eloquent\Builder forSubject(\Illuminate\Database\Eloquent\Model $subject)
* @method static \Illuminate\Database\Eloquent\Builder forSubjectType(string $subjectType)
* @method static \Illuminate\Database\Eloquent\Builder byCauser(\Illuminate\Contracts\Auth\Authenticatable|\Illuminate\Database\Eloquent\Model $user)
* @method static \Illuminate\Database\Eloquent\Builder byCauserId(int $causerId, string|null $causerType = null)
* @method static \Illuminate\Database\Eloquent\Builder ofType(string|array $event)
* @method static \Illuminate\Database\Eloquent\Builder createdEvents()
* @method static \Illuminate\Database\Eloquent\Builder updatedEvents()
* @method static \Illuminate\Database\Eloquent\Builder deletedEvents()
* @method static \Illuminate\Database\Eloquent\Builder betweenDates(\DateTimeInterface|string $from, \DateTimeInterface|string|null $to = null)
* @method static \Illuminate\Database\Eloquent\Builder today()
* @method static \Illuminate\Database\Eloquent\Builder lastDays(int $days)
* @method static \Illuminate\Database\Eloquent\Builder lastHours(int $hours)
* @method static \Illuminate\Database\Eloquent\Builder search(string $search)
* @method static \Illuminate\Database\Eloquent\Builder inLog(string $logName)
* @method static \Illuminate\Database\Eloquent\Builder withChanges()
* @method static \Illuminate\Database\Eloquent\Builder withExistingSubject()
* @method static \Illuminate\Database\Eloquent\Builder withDeletedSubject()
* @method static \Illuminate\Database\Eloquent\Builder newest()
* @method static \Illuminate\Database\Eloquent\Builder oldest()
*/
class Activity extends SpatieActivity
{
use ActivityScopes;
/**
* Get the workspace ID from properties.
*/
public function getWorkspaceIdAttribute(): ?int
{
return $this->properties->get('workspace_id');
}
/**
* Get the old values from properties.
*
* @return array<string, mixed>
*/
public function getOldValuesAttribute(): array
{
return $this->properties->get('old', []);
}
/**
* Get the new values from properties.
*
* @return array<string, mixed>
*/
public function getNewValuesAttribute(): array
{
return $this->properties->get('attributes', []);
}
/**
* Get the changed attributes.
*
* @return Collection<string, array{old: mixed, new: mixed}>
*/
public function getChangesAttribute(): Collection
{
$old = $this->old_values;
$new = $this->new_values;
$changes = [];
foreach ($new as $key => $newValue) {
$oldValue = $old[$key] ?? null;
if ($oldValue !== $newValue) {
$changes[$key] = [
'old' => $oldValue,
'new' => $newValue,
];
}
}
return collect($changes);
}
/**
* Check if this activity has any changes.
*/
public function hasChanges(): bool
{
return ! empty($this->new_values) || ! empty($this->old_values);
}
/**
* Get a human-readable summary of changes.
*/
public function getChangesSummary(): string
{
$changes = $this->changes;
if ($changes->isEmpty()) {
return 'No changes recorded';
}
$parts = [];
foreach ($changes as $field => $values) {
$parts[] = sprintf(
'%s: %s -> %s',
$field,
$this->formatValue($values['old']),
$this->formatValue($values['new'])
);
}
return implode(', ', $parts);
}
/**
* Format a value for display.
*/
protected function formatValue(mixed $value): string
{
if ($value === null) {
return 'null';
}
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if (is_array($value)) {
return json_encode($value);
}
if ($value instanceof \DateTimeInterface) {
return $value->format('Y-m-d H:i:s');
}
return (string) $value;
}
/**
* Get the display name for the causer.
*/
public function getCauserNameAttribute(): string
{
$causer = $this->causer;
if (! $causer) {
return 'System';
}
return $causer->name ?? $causer->email ?? 'User #'.$causer->getKey();
}
/**
* Get the display name for the subject.
*/
public function getSubjectNameAttribute(): ?string
{
$subject = $this->subject;
if (! $subject) {
return null;
}
// Try common name attributes
foreach (['name', 'title', 'label', 'email', 'slug'] as $attribute) {
if (isset($subject->{$attribute})) {
return (string) $subject->{$attribute};
}
}
return class_basename($subject).' #'.$subject->getKey();
}
/**
* Get the subject type as a readable name.
*/
public function getSubjectTypeNameAttribute(): ?string
{
return $this->subject_type ? class_basename($this->subject_type) : null;
}
}