diff --git a/.env.example b/.env.example index 473fa05a6..519ce7d0f 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,7 @@ APP_ENV=local APP_DEBUG=true DEBUGBAR_ENABLED=true DEBUGBAR_EDITOR= +AUDITING_ENABLED=true APP_URL=http://localhost ASSET_URL=null APP_KEY= diff --git a/.env.example-sail b/.env.example-sail index d39d889fc..5c389fa0e 100644 --- a/.env.example-sail +++ b/.env.example-sail @@ -8,6 +8,7 @@ APP_ENV=local APP_DEBUG=true DEBUGBAR_ENABLED=true DEBUGBAR_EDITOR= +AUDITING_ENABLED=true APP_URL=http://localhost ASSET_URL=null APP_KEY= diff --git a/app/Models/Admin/Dump.php b/app/Models/Admin/Dump.php index c2088d94c..97cd6dcbe 100644 --- a/app/Models/Admin/Dump.php +++ b/app/Models/Admin/Dump.php @@ -35,6 +35,13 @@ class Dump extends BaseModel final public const ATTRIBUTE_PATH = 'path'; final public const ATTRIBUTE_LINK = 'link'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/Admin/Feature.php b/app/Models/Admin/Feature.php index 56e15d611..21da32d5c 100644 --- a/app/Models/Admin/Feature.php +++ b/app/Models/Admin/Feature.php @@ -36,6 +36,13 @@ class Feature extends BaseModel final public const ATTRIBUTE_SCOPE = 'scope'; final public const ATTRIBUTE_VALUE = 'value'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 1189888af..0282ae544 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -11,6 +11,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Config; use Illuminate\Support\Str; +use OwenIt\Auditing\Auditable as HasAudits; +use OwenIt\Auditing\Contracts\Auditable; /** * Class BaseModel. @@ -18,8 +20,9 @@ use Illuminate\Support\Str; * @property Carbon $created_at * @property Carbon $updated_at */ -abstract class BaseModel extends Model implements HasSubtitle, Nameable +abstract class BaseModel extends Model implements Auditable, HasSubtitle, Nameable { + use HasAudits; use ModelHasActionLogs; /** diff --git a/app/Models/Discord/DiscordThread.php b/app/Models/Discord/DiscordThread.php index 1a5b2ced5..bbd36dd98 100644 --- a/app/Models/Discord/DiscordThread.php +++ b/app/Models/Discord/DiscordThread.php @@ -34,6 +34,13 @@ class DiscordThread extends BaseModel final public const RELATION_ANIME = 'anime'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/List/External/ExternalEntry.php b/app/Models/List/External/ExternalEntry.php index faf903484..a2bf3d4d1 100644 --- a/app/Models/List/External/ExternalEntry.php +++ b/app/Models/List/External/ExternalEntry.php @@ -43,6 +43,13 @@ class ExternalEntry extends BaseModel final public const RELATION_PROFILE = 'externalprofile'; final public const RELATION_USER = 'externalprofile.user'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/List/External/ExternalToken.php b/app/Models/List/External/ExternalToken.php index 7a56112f6..2d8ecae81 100644 --- a/app/Models/List/External/ExternalToken.php +++ b/app/Models/List/External/ExternalToken.php @@ -42,6 +42,13 @@ class ExternalToken extends BaseModel final public const RELATION_USER = 'externalprofile.user'; final public const RELATION_USER_SHALLOW = 'user'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/List/ExternalProfile.php b/app/Models/List/ExternalProfile.php index bdd6cb461..4658d2f16 100644 --- a/app/Models/List/ExternalProfile.php +++ b/app/Models/List/ExternalProfile.php @@ -64,6 +64,13 @@ class ExternalProfile extends BaseModel final public const RELATION_EXTERNAL_TOKEN = 'externaltoken'; final public const RELATION_USER = 'user'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/List/Playlist.php b/app/Models/List/Playlist.php index f7736f691..eeabad171 100644 --- a/app/Models/List/Playlist.php +++ b/app/Models/List/Playlist.php @@ -69,6 +69,13 @@ class Playlist extends BaseModel implements HasAggregateLikes, HasHashids, HasIm final public const RELATION_USER = 'user'; final public const RELATION_VIEWS = 'views'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/List/Playlist/PlaylistTrack.php b/app/Models/List/Playlist/PlaylistTrack.php index 68af84ac9..18647be04 100644 --- a/app/Models/List/Playlist/PlaylistTrack.php +++ b/app/Models/List/Playlist/PlaylistTrack.php @@ -62,6 +62,13 @@ class PlaylistTrack extends BaseModel implements HasHashids, InteractsWithSchema final public const RELATION_THEME_GROUP = 'animethemeentry.animetheme.group'; final public const RELATION_VIDEO = 'video'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/User/Encode.php b/app/Models/User/Encode.php index cb9d5aa62..bcbbce383 100644 --- a/app/Models/User/Encode.php +++ b/app/Models/User/Encode.php @@ -34,6 +34,13 @@ class Encode extends BaseModel final public const RELATION_USER = 'user'; final public const RELATION_VIDEO = 'video'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The table associated with the model. * diff --git a/app/Models/User/Like.php b/app/Models/User/Like.php index c0ff45f41..172fa22b2 100644 --- a/app/Models/User/Like.php +++ b/app/Models/User/Like.php @@ -33,6 +33,13 @@ class Like extends BaseModel final public const RELATION_USER = 'user'; final public const RELATION_LIKEABLE = 'likeable'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The table associated with the model. * diff --git a/app/Models/User/Report.php b/app/Models/User/Report.php index 28c1d3a6b..7095c814a 100644 --- a/app/Models/User/Report.php +++ b/app/Models/User/Report.php @@ -51,6 +51,13 @@ class Report extends BaseModel final public const RELATION_STEPS = 'steps'; final public const RELATION_USER = 'user'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/User/Report/ReportStep.php b/app/Models/User/Report/ReportStep.php index b418eb5dd..a2065602f 100644 --- a/app/Models/User/Report/ReportStep.php +++ b/app/Models/User/Report/ReportStep.php @@ -58,6 +58,13 @@ class ReportStep extends BaseModel final public const RELATION_TARGET = 'target'; final public const RELATION_REPORT = 'report'; + /** + * Is auditing disabled? + * + * @var bool + */ + public static $auditingDisabled = true; + /** * The attributes that are mass assignable. * diff --git a/app/Models/Wiki/Song/Performance.php b/app/Models/Wiki/Song/Performance.php index cb8fb22fa..693d86fac 100644 --- a/app/Models/Wiki/Song/Performance.php +++ b/app/Models/Wiki/Song/Performance.php @@ -19,6 +19,7 @@ use Database\Factories\Wiki\Song\PerformanceFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Relations\Relation; /** * Class Performance. @@ -132,7 +133,7 @@ class Performance extends BaseModel implements SoftDeletable */ public function isMembership(): bool { - return $this->artist_type === Membership::class; + return $this->artist_type === Relation::getMorphAlias(Membership::class); } /** diff --git a/app/Models/Wiki/Video/VideoScript.php b/app/Models/Wiki/Video/VideoScript.php index 0b28d24f4..9e77afbe5 100644 --- a/app/Models/Wiki/Video/VideoScript.php +++ b/app/Models/Wiki/Video/VideoScript.php @@ -12,7 +12,6 @@ use App\Events\Wiki\Video\Script\VideoScriptDeleted; use App\Events\Wiki\Video\Script\VideoScriptForceDeleting; use App\Events\Wiki\Video\Script\VideoScriptRestored; use App\Events\Wiki\Video\Script\VideoScriptUpdated; -use App\Http\Api\Schema\Schema; use App\Http\Api\Schema\Wiki\Video\ScriptSchema; use App\Models\BaseModel; use App\Models\Wiki\Video; diff --git a/composer.json b/composer.json index 0776cbcbe..0889b332a 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "leandrocfe/filament-apex-charts": "4.0-beta1", "mll-lab/graphql-php-scalars": "^6.4.1", "mll-lab/laravel-graphiql": "^3.3.4", + "owen-it/laravel-auditing": "^14.0", "propaganistas/laravel-disposable-email": "^2.4.17", "rebing/graphql-laravel": "^9.10", "spatie/db-dumper": "^3.8.0", diff --git a/composer.lock b/composer.lock index bf6ba68ad..2473b54e8 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": "d5988605c29dfd384eb615972e5fc3fd", + "content-hash": "cdde89d37d3b79f71315942ac1dccf16", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -6487,6 +6487,90 @@ ], "time": "2025-07-07T06:15:55+00:00" }, + { + "name": "owen-it/laravel-auditing", + "version": "v14.0.0", + "source": { + "type": "git", + "url": "https://github.com/owen-it/laravel-auditing.git", + "reference": "f92602d1b3f53df29ddd577290e9d735ea707c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/owen-it/laravel-auditing/zipball/f92602d1b3f53df29ddd577290e9d735ea707c53", + "reference": "f92602d1b3f53df29ddd577290e9d735ea707c53", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/filesystem": "^11.0|^12.0", + "php": "^8.2" + }, + "require-dev": { + "mockery/mockery": "^1.5.1", + "orchestra/testbench": "^9.0|^10.0", + "phpunit/phpunit": "^11.0" + }, + "type": "package", + "extra": { + "laravel": { + "providers": [ + "OwenIt\\Auditing\\AuditingServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "v14-dev" + } + }, + "autoload": { + "psr-4": { + "OwenIt\\Auditing\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antério Vieira", + "email": "anteriovieira@gmail.com" + }, + { + "name": "Raphael França", + "email": "raphaelfrancabsb@gmail.com" + }, + { + "name": "Morten D. Hansen", + "email": "morten@visia.dk" + } + ], + "description": "Audit changes of your Eloquent models in Laravel", + "homepage": "https://laravel-auditing.com", + "keywords": [ + "Accountability", + "Audit", + "auditing", + "changes", + "eloquent", + "history", + "laravel", + "log", + "logging", + "lumen", + "observer", + "record", + "revision", + "tracking" + ], + "support": { + "issues": "https://github.com/owen-it/laravel-auditing/issues", + "source": "https://github.com/owen-it/laravel-auditing" + }, + "time": "2025-02-26T16:40:54+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.0.0", diff --git a/config/app.php b/config/app.php index 35b620b1d..de991d5c0 100644 --- a/config/app.php +++ b/config/app.php @@ -214,6 +214,7 @@ return [ /* * Package Service Providers... */ + OwenIt\Auditing\AuditingServiceProvider::class, /* * Application Service Providers... diff --git a/config/audit.php b/config/audit.php new file mode 100644 index 000000000..a00e8c36f --- /dev/null +++ b/config/audit.php @@ -0,0 +1,196 @@ + env('AUDITING_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Audit Implementation + |-------------------------------------------------------------------------- + | + | Define which Audit model implementation should be used. + | + */ + + 'implementation' => OwenIt\Auditing\Models\Audit::class, + + /* + |-------------------------------------------------------------------------- + | User Morph prefix & Guards + |-------------------------------------------------------------------------- + | + | Define the morph prefix and authentication guards for the User resolver. + | + */ + + 'user' => [ + 'morph_prefix' => 'user', + 'guards' => [ + 'web', + 'api', + ], + 'resolver' => OwenIt\Auditing\Resolvers\UserResolver::class, + ], + + /* + |-------------------------------------------------------------------------- + | Audit Resolvers + |-------------------------------------------------------------------------- + | + | Define the IP Address, User Agent and URL resolver implementations. + | + */ + 'resolvers' => [], + + /* + |-------------------------------------------------------------------------- + | Audit Events + |-------------------------------------------------------------------------- + | + | The Eloquent events that trigger an Audit. + | + */ + + 'events' => [ + 'created', + 'updated', + 'deleted', + 'restored', + ], + + /* + |-------------------------------------------------------------------------- + | Strict Mode + |-------------------------------------------------------------------------- + | + | Enable the strict mode when auditing? + | + */ + + 'strict' => false, + + /* + |-------------------------------------------------------------------------- + | Global exclude + |-------------------------------------------------------------------------- + | + | Have something you always want to exclude by default? - add it here. + | Note that this is overwritten (not merged) with local exclude + | + */ + + 'exclude' => [], + + /* + |-------------------------------------------------------------------------- + | Empty Values + |-------------------------------------------------------------------------- + | + | Should Audit records be stored when the recorded old_values & new_values + | are both empty? + | + | Some events may be empty on purpose. Use allowed_empty_values to exclude + | those from the empty values check. For example when auditing + | model retrieved events which will never have new and old values. + | + | + */ + + 'empty_values' => true, + 'allowed_empty_values' => [ + 'retrieved', + ], + + /* + |-------------------------------------------------------------------------- + | Allowed Array Values + |-------------------------------------------------------------------------- + | + | Should the array values be audited? + | + | By default, array values are not allowed. This is to prevent performance + | issues when storing large amounts of data. You can override this by + | setting allow_array_values to true. + */ + 'allowed_array_values' => false, + + /* + |-------------------------------------------------------------------------- + | Audit Timestamps + |-------------------------------------------------------------------------- + | + | Should the created_at, updated_at and deleted_at timestamps be audited? + | + */ + + 'timestamps' => false, + + /* + |-------------------------------------------------------------------------- + | Audit Threshold + |-------------------------------------------------------------------------- + | + | Specify a threshold for the amount of Audit records a model can have. + | Zero means no limit. + | + */ + + 'threshold' => 0, + + /* + |-------------------------------------------------------------------------- + | Audit Driver + |-------------------------------------------------------------------------- + | + | The default audit driver used to keep track of changes. + | + */ + + 'driver' => 'database', + + /* + |-------------------------------------------------------------------------- + | Audit Driver Configurations + |-------------------------------------------------------------------------- + | + | Available audit drivers and respective configurations. + | + */ + + 'drivers' => [ + 'database' => [ + 'table' => 'audits', + 'connection' => null, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Audit Queue Configurations + |-------------------------------------------------------------------------- + | + | Available audit queue configurations. + | + */ + + 'queue' => [ + 'enable' => false, + 'connection' => 'sync', + 'queue' => 'default', + 'delay' => 0, + ], + + /* + |-------------------------------------------------------------------------- + | Audit Console + |-------------------------------------------------------------------------- + | + | Whether console events should be audited (eg. php artisan db:seed). + | + */ + + 'console' => false, +]; diff --git a/database/migrations/2025_08_24_041058_create_audits_table.php b/database/migrations/2025_08_24_041058_create_audits_table.php new file mode 100644 index 000000000..7e159820f --- /dev/null +++ b/database/migrations/2025_08_24_041058_create_audits_table.php @@ -0,0 +1,46 @@ +create($table, function (Blueprint $table) { + $morphPrefix = config('audit.user.morph_prefix', 'user'); + + $table->bigIncrements('id'); + $table->string($morphPrefix.'_type')->nullable(); + $table->unsignedBigInteger($morphPrefix.'_id')->nullable(); + $table->string('event'); + $table->morphs('auditable'); + $table->text('old_values')->nullable(); + $table->text('new_values')->nullable(); + $table->string('tags')->nullable(); + $table->timestamps(); + + $table->index([$morphPrefix.'_id', $morphPrefix.'_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $connection = config('audit.drivers.database.connection', config('database.default')); + $table = config('audit.drivers.database.table', 'audits'); + + Schema::connection($connection)->dropIfExists($table); + } +};