Update Spatie versi 4
This commit is contained in:
@@ -4,10 +4,12 @@ namespace App\Http\Controllers\Web\Konfigurasi;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Konfigurasi\FilterLogAktivitasRequest;
|
use App\Http\Requests\Konfigurasi\FilterLogAktivitasRequest;
|
||||||
|
use App\Models\Konfigurasi\LogAktivitas;
|
||||||
use App\Models\Pertanian\Petani;
|
use App\Models\Pertanian\Petani;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class LogAktivitasController extends Controller
|
class LogAktivitasController extends Controller
|
||||||
{
|
{
|
||||||
@@ -38,21 +40,24 @@ class LogAktivitasController extends Controller
|
|||||||
{
|
{
|
||||||
$filter = $request->validated();
|
$filter = $request->validated();
|
||||||
|
|
||||||
// Ambil log sesuai filter
|
// Ambil log sesuai filter (tanpa COUNT total)
|
||||||
$aktivitas = Activity::query()
|
$aktivitas = LogAktivitas::query()
|
||||||
->with(['causer', 'subject'])
|
->with('causer:id,name,role')
|
||||||
->when($filter['jenis'] ?? null, fn ($q, $jenis) => $q->where('log_name', $jenis))
|
->when($filter['jenis'] ?? null, fn ($q, $jenis) => $q->where('log_name', $jenis))
|
||||||
->when($filter['dari'] ?? null, fn ($q, $tgl) => $q->where('created_at', '>=', $tgl))
|
->when($filter['dari'] ?? null, fn ($q, $tgl) => $q->where('created_at', '>=', $tgl))
|
||||||
->when($filter['sampai'] ?? null, fn ($q, $tgl) => $q->where('created_at', '<', Carbon::parse($tgl)->addDay()))
|
->when($filter['sampai'] ?? null, fn ($q, $tgl) => $q->where('created_at', '<', Carbon::parse($tgl)->addDay()))
|
||||||
->when($filter['search'] ?? null, fn ($q, $cari) => $this->cari($q, '%' . mb_strtolower($cari) . '%'))
|
->when($filter['search'] ?? null, fn ($q, $cari) => $this->cari($q, '%' . mb_strtolower($cari) . '%'))
|
||||||
->latest('id')
|
->latest('id')
|
||||||
->paginate((int) ($filter['per_page'] ?? self::PER_PAGE[0]))
|
->simplePaginate((int) ($filter['per_page'] ?? self::PER_PAGE[0]))
|
||||||
->withQueryString();
|
->withQueryString();
|
||||||
|
|
||||||
// Nama petani untuk log tab petani (satu query)
|
// Nama petani untuk log yang belum menyimpan nama (satu query)
|
||||||
$namaPetani = Petani::withoutGlobalScopes()->withTrashed()
|
$idPetani = $aktivitas->getCollection()
|
||||||
->whereIn('id', $aktivitas->getCollection()->pluck('properties.petani_id')->filter()->unique())
|
->map(fn ($a) => $a->properties['petani_id'] ?? ($a->subject_type === Petani::class ? $a->subject_id : null))
|
||||||
->pluck('nama', 'id');
|
->filter()
|
||||||
|
->unique();
|
||||||
|
|
||||||
|
$namaPetani = $idPetani->isEmpty() ? collect() : Petani::withoutGlobalScopes()->whereIn('id', $idPetani)->pluck('nama', 'id');
|
||||||
|
|
||||||
return view('konfigurasi.log-aktivitas.index', [
|
return view('konfigurasi.log-aktivitas.index', [
|
||||||
'aktivitas' => $aktivitas,
|
'aktivitas' => $aktivitas,
|
||||||
@@ -62,15 +67,16 @@ class LogAktivitasController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cari di aktivitas, nama pengguna, dan nama petani
|
// Cari di aktivitas, nama pengguna, dan nama petani (memakai index)
|
||||||
private function cari($query, string $cari): void
|
private function cari(Builder $query, string $cari): void
|
||||||
{
|
{
|
||||||
|
$petani = Petani::withoutGlobalScopes()->select('id')->whereRaw('lower(nama) like ?', [$cari]);
|
||||||
|
$pengguna = User::withoutGlobalScopes()->select('id')->whereRaw('lower(name) like ?', [$cari]);
|
||||||
|
|
||||||
$query->where(fn ($q) => $q
|
$query->where(fn ($q) => $q
|
||||||
->whereRaw('lower(description) like ?', [$cari])
|
->whereRaw('lower(description) like ?', [$cari])
|
||||||
->orWhereHasMorph('causer', [User::class], fn ($q) => $q->whereRaw('lower(name) like ?', [$cari]))
|
->orWhere(fn ($q) => $q->where('causer_type', User::class)->whereIn('causer_id', $pengguna))
|
||||||
->orWhereRaw(
|
->orWhere(fn ($q) => $q->where('subject_type', Petani::class)->whereIn('subject_id', $petani))
|
||||||
"coalesce((properties->>'petani_id')::bigint, case when subject_type = ? then subject_id end) in (select id from petani where lower(nama) like ?)",
|
->orWhereIn(DB::raw("(properties->>'petani_id')::bigint"), $petani));
|
||||||
[Petani::class, $cari]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Services\Konfigurasi\BufferLogAktivitas;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class SimpanLogAktivitas
|
||||||
|
{
|
||||||
|
public function __construct(private BufferLogAktivitas $buffer) {}
|
||||||
|
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$this->buffer->mulai();
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simpan log setelah respons terkirim; request gagal (5xx) tidak dicatat
|
||||||
|
public function terminate(Request $request, Response $response): void
|
||||||
|
{
|
||||||
|
$response->getStatusCode() >= 500 ? $this->buffer->buang() : $this->buffer->simpan();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Models\Concerns;
|
namespace App\Models\Concerns;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
use Spatie\Activitylog\Contracts\Activity;
|
||||||
use Spatie\Activitylog\Support\LogOptions;
|
use Spatie\Activitylog\LogOptions;
|
||||||
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
|
||||||
trait CatatAktivitas
|
trait CatatAktivitas
|
||||||
{
|
{
|
||||||
@@ -36,15 +36,19 @@ trait CatatAktivitas
|
|||||||
->logExcept([...self::$kolomTidakDicatat, ...$this->getHidden()])
|
->logExcept([...self::$kolomTidakDicatat, ...$this->getHidden()])
|
||||||
->dontLogIfAttributesChangedOnly(self::$kolomTidakDicatat)
|
->dontLogIfAttributesChangedOnly(self::$kolomTidakDicatat)
|
||||||
->logOnlyDirty()
|
->logOnlyDirty()
|
||||||
->dontLogEmptyChanges()
|
->dontSubmitEmptyLogs()
|
||||||
->setDescriptionForEvent(fn (string $event) => $label . ' ' . (self::$kataEvent[$event] ?? $event));
|
->setDescriptionForEvent(fn (string $event) => $label . ' ' . (self::$kataEvent[$event] ?? $event));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tandai petani terkait
|
// Simpan nama data & petani terkait di log (halaman log tak perlu memuat data asli)
|
||||||
public function beforeActivityLogged(Model $activity, string $event): void
|
public function tapActivity(Activity $activity, string $eventName): void
|
||||||
{
|
{
|
||||||
if ($petaniId = $this->getAttribute('petani_id')) {
|
$label = $this->getAttribute('nama') ?? $this->getAttribute('name');
|
||||||
$activity->properties = $activity->properties->put('petani_id', (int) $petaniId);
|
$petaniId = $this->getAttribute('petani_id');
|
||||||
}
|
|
||||||
|
$activity->properties = $activity->properties->merge(array_filter([
|
||||||
|
'label' => is_string($label) ? Str::limit($label, 100) : null,
|
||||||
|
'petani_id' => $petaniId ? (int) $petaniId : null,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Konfigurasi;
|
||||||
|
|
||||||
|
use App\Services\Konfigurasi\BufferLogAktivitas;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
class LogAktivitas extends Activity
|
||||||
|
{
|
||||||
|
// Saat request web/API, log ditampung lalu disimpan sekaligus di akhir request
|
||||||
|
public function save(array $options = []): bool
|
||||||
|
{
|
||||||
|
$buffer = app(BufferLogAktivitas::class);
|
||||||
|
|
||||||
|
if ($this->exists || ! $buffer->aktif()) {
|
||||||
|
return parent::save($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateTimestamps();
|
||||||
|
$buffer->tambah($this->getAttributes());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,8 @@ use Dedoc\Scramble\Support\Generator\OpenApi;
|
|||||||
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
||||||
|
|
||||||
use App\Models\Ekosistem\TabelBasisData;
|
use App\Models\Ekosistem\TabelBasisData;
|
||||||
use Spatie\Activitylog\Support\ActivityLogStatus;
|
use App\Services\Konfigurasi\BufferLogAktivitas;
|
||||||
|
use Spatie\Activitylog\ActivityLogStatus;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -24,7 +25,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
$this->app->scoped(BufferLogAktivitas::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Konfigurasi;
|
||||||
|
|
||||||
|
use App\Models\Konfigurasi\LogAktivitas;
|
||||||
|
|
||||||
|
class BufferLogAktivitas
|
||||||
|
{
|
||||||
|
private bool $aktif = false;
|
||||||
|
|
||||||
|
private array $antrean = [];
|
||||||
|
|
||||||
|
public function mulai(): void
|
||||||
|
{
|
||||||
|
$this->aktif = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function aktif(): bool
|
||||||
|
{
|
||||||
|
return $this->aktif;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const KOLOM = [
|
||||||
|
'log_name', 'description', 'subject_type', 'subject_id', 'event',
|
||||||
|
'causer_type', 'causer_id', 'properties', 'batch_uuid', 'created_at', 'updated_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Samakan kolom tiap baris supaya bisa di-insert sekaligus
|
||||||
|
public function tambah(array $baris): void
|
||||||
|
{
|
||||||
|
$this->antrean[] = array_merge(array_fill_keys(self::KOLOM, null), array_intersect_key($baris, array_flip(self::KOLOM)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simpan semua log dalam satu insert
|
||||||
|
public function simpan(): void
|
||||||
|
{
|
||||||
|
foreach (array_chunk($this->antrean, 500) as $potongan) {
|
||||||
|
LogAktivitas::query()->insert($potongan);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buang();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buang(): void
|
||||||
|
{
|
||||||
|
$this->antrean = [];
|
||||||
|
$this->aktif = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->append(\App\Http\Middleware\ResolveProgram::class);
|
$middleware->append(\App\Http\Middleware\ResolveProgram::class);
|
||||||
|
$middleware->append(\App\Http\Middleware\SimpanLogAktivitas::class);
|
||||||
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'akses.petani' => \App\Http\Middleware\BatasiAksesPetani::class,
|
'akses.petani' => \App\Http\Middleware\BatasiAksesPetani::class,
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/telescope": "^5.18",
|
"laravel/telescope": "^5.18",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"spatie/laravel-activitylog": "^5.1"
|
"spatie/laravel-activitylog": "^4.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
|||||||
+13
-34
@@ -1,21 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Spatie\Activitylog\Actions\CleanActivityLogAction;
|
|
||||||
use Spatie\Activitylog\Actions\LogActivityAction;
|
|
||||||
use Spatie\Activitylog\Models\Activity;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If set to false, no activities will be saved to the database.
|
* If set to false, no activities will be saved to the database.
|
||||||
*/
|
*/
|
||||||
'enabled' => env('ACTIVITYLOG_ENABLED', true),
|
'enabled' => env('ACTIVITY_LOGGER_ENABLED', true),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When the clean command is executed, all recording activities older than
|
* When the clean-command is executed, all recording activities older than
|
||||||
* the number of days specified here will be deleted.
|
* the number of days specified here will be deleted.
|
||||||
*/
|
*/
|
||||||
'clean_after_days' => 365,
|
'delete_records_older_than_days' => 365,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If no log name is passed to the activity() helper
|
* If no log name is passed to the activity() helper
|
||||||
@@ -30,44 +26,27 @@ return [
|
|||||||
'default_auth_driver' => null,
|
'default_auth_driver' => null,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If set to true, the subject relationship on activities
|
* If set to true, the subject returns soft deleted models.
|
||||||
* will include soft deleted models.
|
|
||||||
*/
|
*/
|
||||||
'include_soft_deleted_subjects' => true,
|
'subject_returns_soft_deleted_models' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This model will be used to log activity.
|
* This model will be used to log activity.
|
||||||
* It should implement the Spatie\Activitylog\Contracts\Activity interface
|
* It should implement the Spatie\Activitylog\Contracts\Activity interface
|
||||||
* and extend Illuminate\Database\Eloquent\Model.
|
* and extend Illuminate\Database\Eloquent\Model.
|
||||||
*/
|
*/
|
||||||
'activity_model' => Activity::class,
|
'activity_model' => \App\Models\Konfigurasi\LogAktivitas::class,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These attributes will be excluded from logging for all models.
|
* This is the name of the table that will be created by the migration and
|
||||||
* Model-specific exclusions via logExcept() are merged with these.
|
* used by the Activity model shipped with this package.
|
||||||
*/
|
*/
|
||||||
'default_except_attributes' => [],
|
'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When enabled, activities are buffered in memory and inserted in a
|
* This is the database connection that will be used by the migration and
|
||||||
* single bulk query after the response has been sent to the client.
|
* the Activity model shipped with this package. In case it's not set
|
||||||
* This can significantly reduce the number of database queries when
|
* Laravel's database.default will be used instead.
|
||||||
* many activities are logged during a single request.
|
|
||||||
*
|
|
||||||
* Only enable this if your application logs a high volume of activities
|
|
||||||
* per request. Buffered activities will not have an ID until the
|
|
||||||
* buffer is flushed.
|
|
||||||
*/
|
*/
|
||||||
'buffer' => [
|
'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
|
||||||
'enabled' => env('ACTIVITYLOG_BUFFER_ENABLED', true),
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* These action classes can be overridden to customize how activities
|
|
||||||
* are logged and cleaned. Your custom classes must extend the originals.
|
|
||||||
*/
|
|
||||||
'actions' => [
|
|
||||||
'log_activity' => LogActivityAction::class,
|
|
||||||
'clean_log' => CleanActivityLogAction::class,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
@@ -15,11 +16,19 @@ return new class extends Migration
|
|||||||
$table->nullableMorphs('subject', 'subject');
|
$table->nullableMorphs('subject', 'subject');
|
||||||
$table->string('event')->nullable();
|
$table->string('event')->nullable();
|
||||||
$table->nullableMorphs('causer', 'causer');
|
$table->nullableMorphs('causer', 'causer');
|
||||||
$table->json('attribute_changes')->nullable();
|
|
||||||
$table->json('properties')->nullable();
|
$table->json('properties')->nullable();
|
||||||
|
$table->uuid('batch_uuid')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
$table->index('created_at');
|
$table->index('created_at');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Index pencarian log per petani
|
||||||
|
DB::statement("create index activity_log_petani_id_index on activity_log (((properties->>'petani_id')::bigint))");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('activity_log');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
@use('App\Services\Konfigurasi\FormatLogAktivitas', 'Format')
|
@use('App\Services\Konfigurasi\FormatLogAktivitas', 'Format')
|
||||||
@use('App\Models\Pertanian\Petani')
|
@use('App\Models\Pertanian\Petani')
|
||||||
@use('Illuminate\Support\Str')
|
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="container-xxl flex-grow-1 container-p-y">
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
@@ -75,10 +74,10 @@
|
|||||||
@php
|
@php
|
||||||
$j = $jenis[$item->log_name] ?? ['label' => $item->log_name ?? '-', 'warna' => 'secondary'];
|
$j = $jenis[$item->log_name] ?? ['label' => $item->log_name ?? '-', 'warna' => 'secondary'];
|
||||||
$prop = $item->properties ?? collect();
|
$prop = $item->properties ?? collect();
|
||||||
$baru = $item->attribute_changes['attributes'] ?? [];
|
$baru = $prop['attributes'] ?? [];
|
||||||
$lama = $item->attribute_changes['old'] ?? null;
|
$lama = $prop['old'] ?? null;
|
||||||
$petaniId = $item->subject_type === Petani::class ? $item->subject_id : ($prop['petani_id'] ?? null);
|
$petaniId = $item->subject_type === Petani::class ? $item->subject_id : ($prop['petani_id'] ?? null);
|
||||||
$namaData = $item->subject?->nama ?? $item->subject?->name;
|
$namaData = $prop['label'] ?? null;
|
||||||
@endphp
|
@endphp
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
@@ -97,7 +96,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<span class="d-block">{{ $item->description }}</span>
|
<span class="d-block">{{ $item->description }}</span>
|
||||||
@if ($item->subject_type && $item->subject_type !== Petani::class)
|
@if ($item->subject_type && $item->subject_type !== Petani::class)
|
||||||
<small class="text-body-secondary d-block">#{{ $item->subject_id }}{{ $namaData ? ' · ' . Str::limit($namaData, 40) : '' }}</small>
|
<small class="text-body-secondary d-block">#{{ $item->subject_id }}{{ $namaData ? ' · ' . $namaData : '' }}</small>
|
||||||
@endif
|
@endif
|
||||||
@if ($petaniId)
|
@if ($petaniId)
|
||||||
<small class="text-body-secondary d-block">
|
<small class="text-body-secondary d-block">
|
||||||
@@ -154,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-center justify-content-md-end border-top p-4">
|
<div class="d-flex justify-content-center justify-content-md-end border-top p-4">
|
||||||
{{ $aktivitas->links('pagination::bootstrap-5') }}
|
{{ $aktivitas->links('pagination::simple-bootstrap-5') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user