*/ protected $fillable = [ ActionLog::ATTRIBUTE_BATCH_ID, ActionLog::ATTRIBUTE_NAME, ActionLog::ATTRIBUTE_USER, ActionLog::ATTRIBUTE_ACTIONABLE_TYPE, ActionLog::ATTRIBUTE_ACTIONABLE_ID, ActionLog::ATTRIBUTE_TARGET_TYPE, ActionLog::ATTRIBUTE_TARGET_ID, ActionLog::ATTRIBUTE_MODEL_TYPE, ActionLog::ATTRIBUTE_MODEL_ID, ActionLog::ATTRIBUTE_FIELDS, ActionLog::ATTRIBUTE_STATUS, ActionLog::ATTRIBUTE_EXCEPTION, ActionLog::ATTRIBUTE_FINISHED_AT, ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ ActionLog::ATTRIBUTE_FIELDS => 'array', ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::class, ]; } /** * When an exception is thrown, the current action logs should be handled. */ public static function updateCurrentActionLogToFailed(Throwable $e): void { if ($actionLog = Session::get('currentActionLog')) { ActionLog::query() ->where(ActionLog::ATTRIBUTE_BATCH_ID, $actionLog) ->where(ActionLog::ATTRIBUTE_STATUS, ActionLogStatus::RUNNING->value) ->update([ ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FAILED->value, ActionLog::ATTRIBUTE_EXCEPTION => $e->__toString(), ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } } public function getName(): string { return Str::of($this->name) ->append(' - ') ->append($this->target()->getName()) ->__toString(); } public function actionable(): MorphTo { return $this->morphTo(); } /** * Get the target of the action for user interface linking. */ public function target(): MorphTo|BaseModel { return $this->morphTo() ->withTrashed(); } /** * @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class, ActionLog::ATTRIBUTE_USER); } /** * Get the user id for the action log. */ public static function getUserId(): int { return Filament::auth()->id(); } /** * Register an action log for a model created. */ public static function modelCreated(Model $model): ActionLog { return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(), ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => 'Create', ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(), ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getAttributes(), $model), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Register an action log for a model updated. */ public static function modelUpdated(Model $model): ActionLog { return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(), ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => 'Update', ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(), ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getChanges(), $model), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Register an action log for a model deleted. */ public static function modelDeleted(Model $model): ActionLog { return ActionLog::modelSoftDeleted('Delete', $model); } /** * Register an action log for a model restored. */ public static function modelRestored(Model $model): ActionLog { return ActionLog::modelSoftDeleted('Restore', $model); } /** * Register an action log for a model that is soft-deleted. */ public static function modelSoftDeleted(string $actionName, Model $model): ActionLog { return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(), ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => $actionName, ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(), ActionLog::ATTRIBUTE_FIELDS => static::getFields($model->getAttributes(), $model), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Register an action log for a model attached. */ public static function modelPivot(string $actionName, Model $related, Model $parent, Model $pivot, ?Action $action = null): ActionLog { $data = $action?->getData(); return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(), ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => $actionName, ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($related->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $related->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($parent->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $parent->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($pivot->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $pivot instanceof Pivot ? null : $pivot->getKey(), ActionLog::ATTRIBUTE_FIELDS => $data ? static::getFields($data) : static::getFields($pivot->getAttributes(), $pivot), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Register an action log for a model associated (HasMany). */ public static function modelAssociated(string $actionName, Model $related, Model $parent, Action $action): ActionLog { return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(), ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => $actionName, ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($related->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $related->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($parent->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $parent->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($related->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $related->getKey(), ActionLog::ATTRIBUTE_FIELDS => static::getFields($action->getData()), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Register an action log for when a model has an action executed. */ public static function modelActioned(string $batchId, Action $action, Model $model): ActionLog { return ActionLog::query()->create([ ActionLog::ATTRIBUTE_BATCH_ID => $batchId, ActionLog::ATTRIBUTE_USER => ActionLog::getUserId(), ActionLog::ATTRIBUTE_NAME => $action->getLabel(), ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(), ActionLog::ATTRIBUTE_TARGET_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(), ActionLog::ATTRIBUTE_MODEL_TYPE => Relation::getMorphAlias($model->getMorphClass()), ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(), ActionLog::ATTRIBUTE_FIELDS => static::getFields($action->getData()), ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::RUNNING->value, ]); } /** * Update the all the models status of a batch to running. */ public function batchRunning(): void { $this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id) ->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value]) ->update([ ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::RUNNING->value, ]); } /** * Update the all the models status of a batch to finished. */ public function batchFinished(): void { $this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id) ->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value]) ->update([ ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Update the all the models status of a batch to failed. */ public function batchFailed(Throwable|string|null $exception = null): void { $this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id) ->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value]) ->update([ ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FAILED->value, ActionLog::ATTRIBUTE_EXCEPTION => $exception ? $exception->__toString() : null, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Update the model status to finished. */ public function finished(): void { $this->updateStatus(ActionLogStatus::FINISHED); } /** * Update the model status to failed. */ public function failed(Throwable|string|null $exception = null): void { $this->updateStatus(ActionLogStatus::FAILED, $exception); } /** * Check if the model status is failed. */ public function hasFailed(): bool { return $this->status === ActionLogStatus::FAILED; } /** * Update the status of a given action event. */ public function updateStatus(ActionLogStatus $status, Throwable|string|null $exception = null): void { if ($exception instanceof Throwable) { $exception = $exception->__toString(); } $this->update([ ActionLog::ATTRIBUTE_STATUS => $status->value, ActionLog::ATTRIBUTE_EXCEPTION => $exception, ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(), ]); } /** * Format the fields to store. */ protected static function getFields(array $fields, ?Model $model = null): array { return collect($fields) ->forget(Model::CREATED_AT) ->forget(Model::UPDATED_AT) ->forget(ModelConstants::ATTRIBUTE_DELETED_AT) ->map(function ($value, $key) use ($model): string { if ($model instanceof Model) { if (in_array($key, $model->getHidden())) { return DiscordEmbedField::DEFAULT_FIELD_VALUE; } return DiscordEmbedField::formatEmbedFieldValue($model->getAttribute($key)); } return DiscordEmbedField::formatEmbedFieldValue($value); }) ->toArray(); } }