diff --git a/app/Enums/LogAction.php b/app/Enums/LogAction.php new file mode 100644 index 0000000..73d0968 --- /dev/null +++ b/app/Enums/LogAction.php @@ -0,0 +1,22 @@ + auth()?->user()?->id, - 'content' => "Quote sent. {$quote}", + 'user_id' => auth()?->user()?->id ?? 1, + 'loggable_type' => Quote::class, + 'loggable_id' => null, + 'action' => LogAction::SEND, + 'content' => $quote, + 'ip' => request()->ip(), ]); $this->webHookSend($quote); @@ -39,9 +43,12 @@ class WebHookController extends Controller $quote = Quote::inRandomOrder()->first(); Log::create([ - 'user_id' => auth()?->user()?->id, - 'quote_id' => $quote->id, - 'content' => 'Quote sent.', + 'user_id' => auth()?->user()?->id ?? 1, + 'loggable_type' => Quote::class, + 'loggable_id' => $quote->id, + 'action' => LogAction::REQUEST, + 'content' => 'Random quote requested', + 'ip' => request()->ip(), ]); $this->webHookSend($quote->quote); diff --git a/app/Models/Log.php b/app/Models/Log.php index f359360..3e1be21 100644 --- a/app/Models/Log.php +++ b/app/Models/Log.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\MorphTo; /** * @mixin IdeHelperLog @@ -12,9 +13,11 @@ class Log extends Model { protected $fillable = [ 'user_id', - 'quote_id', - 'requested_quote_id', + 'loggable_type', + 'loggable_id', + 'action', 'content', + 'ip', ]; public function user(): BelongsTo @@ -22,13 +25,11 @@ class Log extends Model return $this->belongsTo(User::class); } - public function quote(): BelongsTo + /** + * Get the parent loggable model (user, quote or requested quote). + */ + public function loggable(): MorphTo { - return $this->belongsTo(Quote::class); - } - - public function requestedQuote(): BelongsTo - { - return $this->belongsTo(RequestedQuote::class); + return $this->morphTo(); } } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 57649fb..e904578 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -2,8 +2,9 @@ namespace App\Models; +use App\Enums\LogAction; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -20,37 +21,47 @@ class Quote extends Model Log::create([ 'user_id' => auth()?->user()?->id, - 'quote_id' => $this->id, - 'content' => 'Quote requested.', + 'loggable_type' => self::class, + 'loggable_id' => $this->id, + 'action' => LogAction::REQUEST, + 'content' => $this->quote, + 'ip' => request()->ip(), ]); } protected $fillable = [ + 'user_id', 'quote', ]; - public function logs(): hasMany + public function logs(): MorphOne { - return $this->hasMany(Log::class); + return $this->morphOne(Log::class, 'loggable'); } public static function boot(): void { parent::boot(); - self::created(function ($model) { + self::created(function (Quote $model) { Log::create([ 'user_id' => auth()?->user()?->id, - 'quote_id' => $model->id, - 'content' => 'Quote created.', + 'loggable_type' => self::class, + 'loggable_id' => $model->id, + 'action' => LogAction::CREATE, + 'content' => $model->quote, + 'ip' => request()->ip(), ]); }); - self::deleted(function ($model) { + self::deleted(function (Quote $model) { Log::create([ 'user_id' => auth()?->user()?->id, - 'quote_id' => $model->id, - 'content' => 'Quote deleted.', + 'loggable_type' => self::class, + 'loggable_id' => $model->id, + 'action' => LogAction::DELETE, + 'content' => $model->quote, + 'ip' => request()->ip(), ]); }); } diff --git a/app/Models/RequestedQuote.php b/app/Models/RequestedQuote.php index 8ab0190..e9fa709 100644 --- a/app/Models/RequestedQuote.php +++ b/app/Models/RequestedQuote.php @@ -2,8 +2,9 @@ namespace App\Models; +use App\Enums\LogAction; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\SoftDeletes; /** @@ -15,20 +16,22 @@ class RequestedQuote extends Model protected $fillable = [ 'quote', - 'user_id', ]; - public function logs(): hasMany + public function logs(): MorphOne { - return $this->hasMany(Log::class); + return $this->morphOne(Log::class, 'loggable'); } public function approve(): void { Log::create([ 'user_id' => auth()?->user()?->id, - 'requested_quote_id' => $this->id, - 'content' => 'Quote approved.', + 'loggable_type' => self::class, + 'loggable_id' => $this->id, + 'action' => LogAction::APPROVE, + 'content' => $this->quote, + 'ip' => request()->ip(), ]); Quote::create([ @@ -42,8 +45,11 @@ class RequestedQuote extends Model { Log::create([ 'user_id' => auth()?->user()?->id, - 'requested_quote_id' => $this->id, - 'content' => 'Quote rejected.', + 'loggable_type' => self::class, + 'loggable_id' => $this->id, + 'action' => LogAction::REJECT, + 'content' => $this->quote, + 'ip' => request()->ip(), ]); $this->delete(); diff --git a/app/Models/User.php b/app/Models/User.php index 7d2f868..49ba7d0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,18 +2,20 @@ namespace App\Models; +use App\Enums\LogAction; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; /** * @mixin IdeHelperUser */ class User extends Authenticatable { - use HasApiTokens, HasFactory, Notifiable; + use HasFactory, Notifiable; /** * The model's default values for attributes. @@ -62,27 +64,33 @@ class User extends Authenticatable 'password' => 'hashed', ]; - public function logs(): hasMany + public function logAction(): HasMany { return $this->hasMany(Log::class); } - public function fullName(): string + public function logs(): MorphOne { - return "{$this->firstname} {$this->lastname}"; + return $this->morphOne(Log::class, 'loggable'); + } + + public function getFullNameAttribute(): string + { + return "$this->firstname $this->lastname"; } public static function boot(): void { parent::boot(); - // These will only ever get created by the api/bot. - // If we want to add something like by whom later on, - // we can just append it to the content e.g auth()?->user()?->id ?? 'API'. self::created(function ($model) { Log::create([ - 'user_id' => $model->id, - 'content' => 'User created.', + 'user_id' => auth()?->user()?->id ?? 1, + 'loggable_type' => self::class, + 'loggable_id' => $model->id, + 'action' => LogAction::CREATE, + 'content' => $model->full_name, + 'ip' => request()->ip(), ]); }); } diff --git a/database/migrations/2023_10_10_200426_create_logs_table.php b/database/migrations/2023_10_10_200426_create_logs_table.php index d5292b6..a804593 100644 --- a/database/migrations/2023_10_10_200426_create_logs_table.php +++ b/database/migrations/2023_10_10_200426_create_logs_table.php @@ -14,9 +14,11 @@ return new class extends Migration Schema::create('logs', function (Blueprint $table) { $table->id(); $table->foreignIdFor(User::class)->nullable(); - $table->foreignIdFor(Quote::class)->nullable(); - $table->foreignIdFor(RequestedQuote::class)->nullable(); - $table->string('content'); + $table->string('loggable_type'); + $table->integer('loggable_id'); + $table->string('action'); + $table->text('content'); + $table->string('ip'); $table->timestamps(); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index bee7ef0..8090ebe 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -12,7 +12,7 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - $this->call(BaseQuotesSeeder::class); $this->call(PriceyBotSeeder::class); + $this->call(BaseQuotesSeeder::class); } } diff --git a/resources/views/admin/logs.blade.php b/resources/views/admin/logs.blade.php index 181274e..dca138b 100644 --- a/resources/views/admin/logs.blade.php +++ b/resources/views/admin/logs.blade.php @@ -1,9 +1,5 @@ -
-
-
- -
-
+
+
diff --git a/resources/views/livewire/pages/admin/logs.blade.php b/resources/views/livewire/pages/admin/logs.blade.php index 557d4d2..26a8c81 100644 --- a/resources/views/livewire/pages/admin/logs.blade.php +++ b/resources/views/livewire/pages/admin/logs.blade.php @@ -1,23 +1,23 @@ getLogs(); - } + $logs = Log::with(['user', 'loggable']) + ->orderByDesc('created_at') + ->paginate(15); - public function getLogs(): void - { - // TODO: look into pagination for this. - $this->logs = Log::with(['user', 'quote', 'requestedQuote'])->get()->sortByDesc('created_at'); + return [ + 'logs' => $logs, + ]; } }; ?> @@ -30,38 +30,56 @@ new #[Layout('layouts.app')] class extends Component class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-nexi-black dark:text-gray-200 sm:pl-6"> User + + Type + + + Entity ID + + + Action + Logs - Type + IP @foreach ($logs as $log) - - {{ $log?->user->fullName() ?? 'N/A' }} + + {{ $log?->user?->full_name ?? 'Anonymous' }} - + + {{ str_replace('App\Models\\', '', $log->loggable_type) }} + + + {{ $log->loggable_id }} + + + {{ $log->action }} + + {{ $log->content }} - - @if (!empty($log->quote_id)) - Quote - {{ $log->quote_id }} - @elseif (!empty($log->requested_quote_id)) - Requsted Quote - ID: {{ $log->requested_quote_id }} - @else - N/A - @endif + + {{ $log->ip }} @endforeach
+
+ {{ $logs->links() }} +
diff --git a/resources/views/livewire/pages/admin/send.blade.php b/resources/views/livewire/pages/admin/send.blade.php index 7b8f526..67603a0 100644 --- a/resources/views/livewire/pages/admin/send.blade.php +++ b/resources/views/livewire/pages/admin/send.blade.php @@ -1,5 +1,6 @@ $validated['quote']]); Log::create([ - 'user_id' => auth()?->user()?->id, - 'content' => "Quote sent. {$validated['quote']}", + 'user_id' => auth()->user()->id, + 'loggable_type' => Log::class, + 'loggable_id' => null, + 'action' => LogAction::SEND, + 'content' => $validated['quote'], + 'ip' => request()->ip(), ]); $this->quote = ''; diff --git a/resources/views/livewire/pages/auth/login.blade.php b/resources/views/livewire/pages/auth/login.blade.php index b9851f8..9e788c2 100644 --- a/resources/views/livewire/pages/auth/login.blade.php +++ b/resources/views/livewire/pages/auth/login.blade.php @@ -1,6 +1,8 @@ regenerate(); Log::create([ - 'user_id' => auth()?->user()?->id, - 'content' => "Quote sent. {$quote}", + 'user_id' => auth()->user()->id, + 'loggable_type' => User::class, + 'loggable_id' => auth()->user()->id, + 'action' => LogAction::ACCESS, + 'content' => 'User logged in', + 'ip' => request()->ip(), ]); $this->redirect( diff --git a/resources/views/livewire/pages/auth/reset-password.blade.php b/resources/views/livewire/pages/auth/reset-password.blade.php index 0d2b932..4603b51 100644 --- a/resources/views/livewire/pages/auth/reset-password.blade.php +++ b/resources/views/livewire/pages/auth/reset-password.blade.php @@ -1,5 +1,7 @@ flash('status', __($status)); Log::create([ - 'user_id' => auth()?->user()?->id, - 'content' => "Quote sent. {$quote}", + 'user_id' => auth()->user()->id, + 'loggable_type' => User::class, + 'loggable_id' => auth()?->user()?->id, + 'action' => LogAction::UPDATE, + 'content' => 'User reset password via reset', + 'ip' => request()->ip(), ]); $this->redirectRoute('login', navigate: true); diff --git a/resources/views/livewire/profile/update-password-form.blade.php b/resources/views/livewire/profile/update-password-form.blade.php index 90b9a68..c9c03bb 100644 --- a/resources/views/livewire/profile/update-password-form.blade.php +++ b/resources/views/livewire/profile/update-password-form.blade.php @@ -1,6 +1,8 @@ auth()?->user()?->id, - 'content' => "Quote sent. {$quote}", + 'user_id' => auth()->user()->id, + 'loggable_type' => User::class, + 'loggable_id' => auth()?->user()?->id, + 'action' => LogAction::UPDATE, + 'content' => 'User updated password', + 'ip' => request()->ip(), ]); $this->reset('current_password', 'password', 'password_confirmation'); @@ -53,25 +59,41 @@ new class extends Component
- - - + + +
- - - + + +
- - - + + +
- {{ __('Save') }} + {{ __('Save') }} {{ __('Saved.') }} diff --git a/vite.config.js b/vite.config.js index 89f26f5..3344198 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,8 +7,7 @@ export default defineConfig({ input: [ 'resources/css/app.css', 'resources/js/app.js', - ], - refresh: true, + ] }), ], });