php-tenant/Models/WorkspaceActivity.php

257 lines
9.2 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Core\Tenant\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* Workspace Activity - audit log for significant workspace actions.
*
* Tracks security events, membership changes, entitlement changes, and
* other significant actions within a workspace.
*
* @property int $id
* @property int $workspace_id
* @property int|null $user_id
* @property string $action
* @property string|null $subject_type
* @property int|null $subject_id
* @property array|null $metadata
* @property \Carbon\Carbon|null $created_at
*/
class WorkspaceActivity extends Model
{
/**
* Disable updated_at since this is an append-only audit log.
*/
public const UPDATED_AT = null;
protected $table = 'workspace_activities';
protected $fillable = [
'workspace_id',
'user_id',
'action',
'subject_type',
'subject_id',
'metadata',
];
protected $casts = [
'metadata' => 'array',
];
// ─────────────────────────────────────────────────────────────────────────
// Action Constants — Workspace
// ─────────────────────────────────────────────────────────────────────────
public const ACTION_WORKSPACE_CREATED = 'workspace.created';
public const ACTION_WORKSPACE_UPDATED = 'workspace.updated';
public const ACTION_WORKSPACE_DELETED = 'workspace.deleted';
public const ACTION_WORKSPACE_SETTINGS_CHANGED = 'workspace.settings_changed';
// ─────────────────────────────────────────────────────────────────────────
// Action Constants — Membership
// ─────────────────────────────────────────────────────────────────────────
public const ACTION_MEMBER_INVITED = 'member.invited';
public const ACTION_MEMBER_JOINED = 'member.joined';
public const ACTION_MEMBER_REMOVED = 'member.removed';
public const ACTION_MEMBER_ROLE_CHANGED = 'member.role_changed';
public const ACTION_MEMBER_TEAM_CHANGED = 'member.team_changed';
// ─────────────────────────────────────────────────────────────────────────
// Action Constants — Entitlements
// ─────────────────────────────────────────────────────────────────────────
public const ACTION_PACKAGE_ASSIGNED = 'package.assigned';
public const ACTION_PACKAGE_REMOVED = 'package.removed';
public const ACTION_BOOST_APPLIED = 'boost.applied';
public const ACTION_BOOST_REMOVED = 'boost.removed';
// ─────────────────────────────────────────────────────────────────────────
// Action Constants — Security Events
// ─────────────────────────────────────────────────────────────────────────
public const ACTION_API_KEY_CREATED = 'api_key.created';
public const ACTION_API_KEY_REVOKED = 'api_key.revoked';
public const ACTION_WEBHOOK_CREATED = 'webhook.created';
public const ACTION_WEBHOOK_DELETED = 'webhook.deleted';
// ─────────────────────────────────────────────────────────────────────────
// Relationships
// ─────────────────────────────────────────────────────────────────────────
/**
* The workspace this activity belongs to.
*/
public function workspace(): BelongsTo
{
return $this->belongsTo(Workspace::class);
}
/**
* The user who performed this action.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* The subject (polymorphic) of this activity.
*/
public function subject(): MorphTo
{
return $this->morphTo();
}
// ─────────────────────────────────────────────────────────────────────────
// Scopes
// ─────────────────────────────────────────────────────────────────────────
/**
* Scope to a specific workspace.
*/
public function scopeForWorkspace($query, Workspace|int $workspace)
{
$workspaceId = $workspace instanceof Workspace ? $workspace->id : $workspace;
return $query->where('workspace_id', $workspaceId);
}
/**
* Scope to a specific action.
*/
public function scopeForAction($query, string $action)
{
return $query->where('action', $action);
}
/**
* Scope to a specific action prefix (e.g. 'member.' matches all member actions).
*/
public function scopeForActionGroup($query, string $prefix)
{
return $query->where('action', 'like', $prefix . '%');
}
/**
* Scope to a specific subject.
*/
public function scopeForSubject($query, Model $subject)
{
return $query
->where('subject_type', $subject->getMorphClass())
->where('subject_id', $subject->getKey());
}
/**
* Scope to a specific user.
*/
public function scopeByUser($query, User|int $user)
{
$userId = $user instanceof User ? $user->id : $user;
return $query->where('user_id', $userId);
}
/**
* Scope to activities within a date range.
*/
public function scopeBetween($query, $from, $to)
{
return $query->whereBetween('created_at', [$from, $to]);
}
/**
* Order by most recent first.
*/
public function scopeLatestFirst($query)
{
return $query->orderByDesc('created_at');
}
// ─────────────────────────────────────────────────────────────────────────
// Factory Methods
// ─────────────────────────────────────────────────────────────────────────
/**
* Record a workspace activity.
*/
public static function record(
Workspace|int $workspace,
string $action,
?Model $subject = null,
?User $user = null,
?array $metadata = null,
): self {
$workspaceId = $workspace instanceof Workspace ? $workspace->id : $workspace;
return self::create([
'workspace_id' => $workspaceId,
'user_id' => $user?->id ?? auth()->id(),
'action' => $action,
'subject_type' => $subject?->getMorphClass(),
'subject_id' => $subject?->getKey(),
'metadata' => $metadata,
]);
}
/**
* Record a membership change.
*/
public static function recordMembershipChange(
Workspace|int $workspace,
string $action,
WorkspaceMember|User $subject,
?User $performedBy = null,
?array $metadata = null,
): self {
return self::record($workspace, $action, $subject, $performedBy, $metadata);
}
/**
* Record an entitlement change.
*/
public static function recordEntitlementChange(
Workspace|int $workspace,
string $action,
Model $subject,
?User $performedBy = null,
?array $metadata = null,
): self {
return self::record($workspace, $action, $subject, $performedBy, $metadata);
}
/**
* Record a security event.
*/
public static function recordSecurityEvent(
Workspace|int $workspace,
string $action,
?Model $subject = null,
?User $performedBy = null,
?array $metadata = null,
): self {
return self::record($workspace, $action, $subject, $performedBy, $metadata);
}
}