Add WorkspaceActivity model, migration, and LogsWorkspaceActivity trait to provide a central audit trail for significant workspace actions including security events, membership changes, and entitlement changes. Fixes #37 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
136 lines
3.9 KiB
PHP
136 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Core\Tenant\Concerns;
|
|
|
|
use Core\Tenant\Models\User;
|
|
use Core\Tenant\Models\Workspace;
|
|
use Core\Tenant\Models\WorkspaceActivity;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Trait for models that should log workspace activity on changes.
|
|
*
|
|
* Usage:
|
|
* class WorkspaceMember extends Model {
|
|
* use LogsWorkspaceActivity;
|
|
*
|
|
* protected static string $activityActionPrefix = 'member';
|
|
* }
|
|
*
|
|
* For auto-logging create/update/delete:
|
|
* class WorkspaceMember extends Model {
|
|
* use LogsWorkspaceActivity;
|
|
*
|
|
* protected static bool $autoLogActivity = true;
|
|
* protected static string $activityActionPrefix = 'member';
|
|
* }
|
|
*
|
|
* For manual logging:
|
|
* $model->logActivity('member.role_changed', ['old_role' => 'member', 'new_role' => 'admin']);
|
|
*/
|
|
trait LogsWorkspaceActivity
|
|
{
|
|
/**
|
|
* Boot the trait — optionally auto-log on create/update/delete.
|
|
*/
|
|
protected static function bootLogsWorkspaceActivity(): void
|
|
{
|
|
if (static::shouldAutoLog()) {
|
|
static::created(function (Model $model) {
|
|
$model->logActivityIfWorkspaced('created');
|
|
});
|
|
|
|
static::updated(function (Model $model) {
|
|
$model->logActivityIfWorkspaced('updated', [
|
|
'changed' => $model->getChanges(),
|
|
]);
|
|
});
|
|
|
|
static::deleted(function (Model $model) {
|
|
$model->logActivityIfWorkspaced('deleted');
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether this model should auto-log create/update/delete events.
|
|
*
|
|
* Override in model to enable: protected static bool $autoLogActivity = true;
|
|
*/
|
|
protected static function shouldAutoLog(): bool
|
|
{
|
|
return property_exists(static::class, 'autoLogActivity')
|
|
&& static::$autoLogActivity === true;
|
|
}
|
|
|
|
/**
|
|
* Log an activity for this model within its workspace context.
|
|
*/
|
|
public function logActivity(string $action, ?array $metadata = null, ?User $user = null): WorkspaceActivity
|
|
{
|
|
$workspaceId = $this->getActivityWorkspaceId();
|
|
|
|
return WorkspaceActivity::record(
|
|
workspace: $workspaceId,
|
|
action: $action,
|
|
subject: $this,
|
|
user: $user,
|
|
metadata: $metadata,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Log activity only if the model has a workspace context.
|
|
*/
|
|
protected function logActivityIfWorkspaced(string $suffix, ?array $metadata = null): void
|
|
{
|
|
$workspaceId = $this->getActivityWorkspaceId();
|
|
|
|
if ($workspaceId === null) {
|
|
return;
|
|
}
|
|
|
|
$action = static::getActivityActionPrefix() . '.' . $suffix;
|
|
|
|
WorkspaceActivity::record(
|
|
workspace: $workspaceId,
|
|
action: $action,
|
|
subject: $this,
|
|
metadata: $metadata,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the workspace ID for this model's activity log.
|
|
*
|
|
* Override in model if the workspace relationship differs.
|
|
*/
|
|
protected function getActivityWorkspaceId(): ?int
|
|
{
|
|
if (property_exists($this, 'workspace_id') || $this->getAttribute('workspace_id')) {
|
|
return $this->getAttribute('workspace_id');
|
|
}
|
|
|
|
// Try to resolve from the current workspace context
|
|
$workspace = Workspace::current();
|
|
|
|
return $workspace?->id;
|
|
}
|
|
|
|
/**
|
|
* Get the action prefix for auto-logged events.
|
|
*
|
|
* Override via: protected static string $activityActionPrefix = 'custom_prefix';
|
|
*/
|
|
protected static function getActivityActionPrefix(): string
|
|
{
|
|
if (property_exists(static::class, 'activityActionPrefix')) {
|
|
return static::$activityActionPrefix;
|
|
}
|
|
|
|
// Derive from class name: WorkspaceMember -> workspace_member
|
|
return strtolower((string) preg_replace('/(?<!^)[A-Z]/', '_$0', class_basename(static::class)));
|
|
}
|
|
}
|