diff --git a/app/Models/Activity.php b/app/Models/Activity.php new file mode 100644 index 00000000..e42b8f0a --- /dev/null +++ b/app/Models/Activity.php @@ -0,0 +1,13 @@ + + */ + protected function getDontLogIfAttributesChangedOnly(): array + { + return ['created_at', 'updated_at', 'deleted_at']; + } + + /** + * @return string[] + */ + protected function logExceptAttributes(): array + { + return ['created_at', 'updated_at']; + } + + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->dontSubmitEmptyLogs() + ->logAll() + ->logExcept($this->logExceptAttributes()) + ->dontLogIfAttributesChangedOnly($this->getDontLogIfAttributesChangedOnly()) + ->logOnlyDirty() + ->useLogName($this->getLogName()); + } + + protected function getLogName(): string + { + return $this->formatLogName($this::class); + } + + protected function formatLogName(string $class): string + { + $className = Str::kebab(class_basename($class)); + + return Str::plural(explode('-', $className)[0]); + } + + /** + * Get the event names that should be recorded. + * + * @return Collection + */ + protected static function eventsToBeRecorded(): Collection + { + $class = static::class; + + /** @var list $doNotRecordEvents */ + $doNotRecordEvents = property_exists($class, 'doNotRecordEvents') ? $class::$doNotRecordEvents : []; + $reject = collect($doNotRecordEvents); + + if (property_exists($class, 'recordEvents')) { + /** @var list $recordEvents */ + $recordEvents = $class::$recordEvents; + + return collect($recordEvents)->reject(fn (string $eventName): bool => $reject->contains($eventName)); + } + + $events = collect([ + 'created', + 'updated', + 'deleted', + ]); + + if (collect(class_uses_recursive(static::class))->contains(SoftDeletes::class)) { + $events->push('restored'); + } + + return $events->reject(fn (string $eventName): bool => $reject->contains($eventName)); + } +} diff --git a/app/Models/Concerns/LogsModificationsTrait.php b/app/Models/Concerns/LogsModificationsTrait.php new file mode 100644 index 00000000..01329037 --- /dev/null +++ b/app/Models/Concerns/LogsModificationsTrait.php @@ -0,0 +1,17 @@ + + */ + protected static array $doNotRecordEvents = [ + 'created', + ]; +} diff --git a/app/Models/Cuisine.php b/app/Models/Cuisine.php index 726631f6..eeb6eeab 100644 --- a/app/Models/Cuisine.php +++ b/app/Models/Cuisine.php @@ -4,6 +4,7 @@ use App\Models\Concerns\ActivatableTrait; use App\Models\Concerns\HasHelloFreshIdsTrait; +use App\Models\Concerns\LogsModificationsTrait; use Database\Factories\CuisineFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -24,6 +25,7 @@ class Cuisine extends Model use HasHelloFreshIdsTrait; use HasTranslations; + use LogsModificationsTrait; /** * The attributes that are translatable. diff --git a/app/Models/Ingredient.php b/app/Models/Ingredient.php index 7980ebd6..71764979 100644 --- a/app/Models/Ingredient.php +++ b/app/Models/Ingredient.php @@ -4,6 +4,7 @@ use App\Models\Concerns\ActivatableTrait; use App\Models\Concerns\HasHelloFreshIdsTrait; +use App\Models\Concerns\LogsModificationsTrait; use Database\Factories\IngredientFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -26,6 +27,7 @@ class Ingredient extends Model use HasHelloFreshIdsTrait; use HasTranslations; + use LogsModificationsTrait; /** * The attributes that are translatable. diff --git a/app/Models/Label.php b/app/Models/Label.php index c768d18f..72cf02c0 100644 --- a/app/Models/Label.php +++ b/app/Models/Label.php @@ -4,6 +4,7 @@ use App\Models\Concerns\ActivatableTrait; use App\Models\Concerns\HasHandlesTrait; +use App\Models\Concerns\LogsModificationsTrait; use Database\Factories\LabelFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -24,6 +25,7 @@ class Label extends Model use HasHandlesTrait; use HasTranslations; + use LogsModificationsTrait; /** * The attributes that are translatable. diff --git a/app/Models/Menu.php b/app/Models/Menu.php index 0d31bccb..00f3f61b 100644 --- a/app/Models/Menu.php +++ b/app/Models/Menu.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Models\Concerns\ActivatableTrait; +use App\Models\Concerns\LogsModificationsTrait; use Carbon\Carbon; use Database\Factories\MenuFactory; use Illuminate\Database\Eloquent\Attributes\Scope; @@ -25,6 +26,8 @@ class Menu extends Model /** @use HasFactory */ use HasFactory; + use LogsModificationsTrait; + /** * The attributes that are mass assignable. * diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index e4e79073..46610cfb 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Models\Concerns\ActivatableTrait; +use App\Models\Concerns\LogsModificationsTrait; use App\Observers\RecipeObserver; use App\Support\HelloFresh\HelloFreshAsset; use Database\Factories\RecipeFactory; @@ -30,6 +31,7 @@ class Recipe extends Model use HasFactory; use HasTranslations; + use LogsModificationsTrait; use SoftDeletes; /** diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 7bcdf9c7..7c9506a4 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -4,6 +4,7 @@ use App\Models\Concerns\ActivatableTrait; use App\Models\Concerns\HasHelloFreshIdsTrait; +use App\Models\Concerns\LogsModificationsTrait; use Database\Factories\TagFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -24,6 +25,7 @@ class Tag extends Model use HasHelloFreshIdsTrait; use HasTranslations; + use LogsModificationsTrait; /** * The attributes that are translatable. diff --git a/app/Models/Utensil.php b/app/Models/Utensil.php index c9809752..21eb830e 100644 --- a/app/Models/Utensil.php +++ b/app/Models/Utensil.php @@ -4,6 +4,7 @@ use App\Models\Concerns\ActivatableTrait; use App\Models\Concerns\HasHelloFreshIdsTrait; +use App\Models\Concerns\LogsModificationsTrait; use Database\Factories\UtensilFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -24,6 +25,7 @@ class Utensil extends Model use HasHelloFreshIdsTrait; use HasTranslations; + use LogsModificationsTrait; /** * The attributes that are translatable. diff --git a/composer.json b/composer.json index e2ae0d6f..bccd2348 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "livewire/flux-pro": "^2.11", "norman-huth/gd-text": "^1.0", "sentry/sentry-laravel": "^4.20", + "spatie/laravel-activitylog": "^4.10", "spatie/laravel-medialibrary": "^11.17", "spatie/laravel-translatable": "^6.12" }, diff --git a/composer.lock b/composer.lock index e9c4c89b..b955d8d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "63debd7f48145b408396da44c71bf1e4", + "content-hash": "8fce6fec93fb9da5233d358175c82a82", "packages": [ { "name": "brick/math", @@ -5463,6 +5463,97 @@ }, "time": "2025-11-26T10:57:19+00:00" }, + { + "name": "spatie/laravel-activitylog", + "version": "4.10.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-activitylog.git", + "reference": "bb879775d487438ed9a99e64f09086b608990c10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/bb879775d487438ed9a99e64f09086b608990c10", + "reference": "bb879775d487438ed9a99e64f09086b608990c10", + "shasum": "" + }, + "require": { + "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.6.3" + }, + "require-dev": { + "ext-json": "*", + "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0", + "pestphp/pest": "^1.20 || ^2.0 || ^3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Activitylog\\ActivitylogServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Activitylog\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev.gummibeer@gmail.com", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "A very simple activity logger to monitor the users of your website or application", + "homepage": "https://github.com/spatie/activitylog", + "keywords": [ + "activity", + "laravel", + "log", + "spatie", + "user" + ], + "support": { + "issues": "https://github.com/spatie/laravel-activitylog/issues", + "source": "https://github.com/spatie/laravel-activitylog/tree/4.10.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-06-15T06:59:49+00:00" + }, { "name": "spatie/laravel-medialibrary", "version": "11.17.10", diff --git a/config/activitylog.php b/config/activitylog.php new file mode 100644 index 00000000..ea34da55 --- /dev/null +++ b/config/activitylog.php @@ -0,0 +1,54 @@ + env('ACTIVITY_LOGGER_ENABLED', true), + + /* + * When the clean-command is executed, all recording activities older than + * the number of days specified here will be deleted. + */ + 'delete_records_older_than_days' => 365, + + /* + * If no log name is passed to the activity() helper + * we use this default log name. + */ + 'default_log_name' => 'default', + + /* + * You can specify an auth driver here that gets user models. + * If this is null we'll use the current Laravel auth driver. + */ + 'default_auth_driver' => null, + + /* + * If set to true, the subject returns soft deleted models. + */ + 'subject_returns_soft_deleted_models' => false, + + /* + * This model will be used to log activity. + * It should implement the Spatie\Activitylog\Contracts\Activity interface + * and extend Illuminate\Database\Eloquent\Model. + */ + 'activity_model' => Activity::class, + + /* + * This is the name of the table that will be created by the migration and + * used by the Activity model shipped with this package. + */ + 'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'), + + /* + * This is the database connection that will be used by the migration and + * the Activity model shipped with this package. In case it's not set + * Laravel's database.default will be used instead. + */ + 'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'), +]; diff --git a/database/migrations/2026_01_26_000020_create_activity_log_table.php b/database/migrations/2026_01_26_000020_create_activity_log_table.php new file mode 100644 index 00000000..612b8e0b --- /dev/null +++ b/database/migrations/2026_01_26_000020_create_activity_log_table.php @@ -0,0 +1,41 @@ +create(config('activitylog.table_name'), function (Blueprint $table): void { + $table->id(); + $table->string('log_name')->nullable(); + $table->text('description'); + // $table->nullableUuidMorphs('subject', 'subject'); + $table->nullableMorphs('subject', 'subject'); + $table->string('event')->nullable(); + // $table->nullableUuidMorphs('causer', 'causer'); + $table->nullableMorphs('causer', 'causer'); + $table->jsonb('properties')->nullable(); + $table->uuid('batch_uuid')->nullable(); + $table->index('log_name'); + $table->timestamp('created_at', precision: 3)->nullable(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name')); + } +};