Implement superadmin audit log for mutations
This commit is contained in:
@@ -59,7 +59,7 @@
|
||||
{"id":"fotospiel-app-gsv","title":"Localized SEO: validate hreflang via Search Console/Lighthouse","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:02:36.4821072+01:00","created_by":"soeren","updated_at":"2026-01-01T16:02:36.4821072+01:00"}
|
||||
{"id":"fotospiel-app-hbt","title":"Moderation queue for guest content","description":"Queue for flagged guest content (photos, feedback). Bulk actions to hide/delete/resolve with audit.","notes":"Land the plane: tests run (FilamentPanelNavigationTest, PhotoModerationQueueTest, TenantFeedbackModerationQueueTest, TenantLifecycle*), migrations added for photo + feedback moderation, no follow-up blockers.","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T14:18:37.777772819+01:00","updated_at":"2026-01-01T18:50:57.274743566+01:00","closed_at":"2026-01-01T18:46:09.677538603+01:00"}
|
||||
{"id":"fotospiel-app-ihd","title":"Superadmin control surface spec and access matrix","description":"Define the minimal superadmin control surface, permissions, and mapping to tenant/guest responsibilities. Document scope and non-goals.","notes":"Added superadmin control surface + access matrix to docs/ops/operations-manual.md (Section 1.1), including non-goals and role scope.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T14:18:10.789147344+01:00","updated_at":"2026-01-01T19:52:54.391624328+01:00","closed_at":"2026-01-01T19:52:54.391628452+01:00"}
|
||||
{"id":"fotospiel-app-iyc","title":"Superadmin audit log for admin actions","description":"Audit trail for superadmin actions without PII payloads.","status":"open","priority":2,"issue_type":"feature","created_at":"2026-01-01T14:20:19.043695952+01:00","updated_at":"2026-01-01T14:20:19.043695952+01:00"}
|
||||
{"id":"fotospiel-app-iyc","title":"Superadmin audit log for admin actions","description":"Audit trail for superadmin actions without PII payloads.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-01T14:20:19.043695952+01:00","updated_at":"2026-01-02T11:57:23.328889123+01:00","closed_at":"2026-01-02T11:57:23.328889123+01:00","close_reason":"Closed"}
|
||||
{"id":"fotospiel-app-iyh","title":"Security review follow-ups: signed URL TTLs, guest asset throttles, CORS allowlist, logging hygiene","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:05:42.642109576+01:00","created_by":"soeren","updated_at":"2026-01-01T16:05:42.642109576+01:00"}
|
||||
{"id":"fotospiel-app-jk4","title":"Checkout refactor: CheckoutController + marketing route alignment","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:06:21.088319132+01:00","created_by":"soeren","updated_at":"2026-01-01T16:06:26.663419594+01:00","closed_at":"2026-01-01T16:06:26.663419594+01:00","close_reason":"Completed in codebase (verified)"}
|
||||
{"id":"fotospiel-app-jqy","title":"Tenant admin onboarding: Playwright skeleton for welcome flow","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T16:08:11.226297707+01:00","created_by":"soeren","updated_at":"2026-01-01T16:08:16.827679424+01:00","closed_at":"2026-01-01T16:08:16.827679424+01:00","close_reason":"Completed in codebase (verified)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
fotospiel-app-z2k
|
||||
fotospiel-app-iyc
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Filament\Blog\Resources\CategoryResource\Pages;
|
||||
use App\Filament\Blog\Traits\HasContentEditor;
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Models\BlogCategory;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
@@ -24,6 +25,7 @@ use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -116,38 +118,25 @@ class CategoryResource extends Resource
|
||||
$data['description_de'] = $descArray['de'] ?? '';
|
||||
$data['description_en'] = $descArray['en'] ?? '';
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('BeforeFill Description Extraction:', [
|
||||
'descJson' => $descJson,
|
||||
'descArray' => $descArray,
|
||||
'description_de' => $data['description_de'],
|
||||
'description_en' => $data['description_en'],
|
||||
]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
\Illuminate\Support\Facades\Log::info('mutateFormDataBeforeCreate Input Data:', ['data' => $data]);
|
||||
|
||||
$nameData = [
|
||||
'de' => $data['name_de'] ?? '',
|
||||
'en' => $data['name_en'] ?? '',
|
||||
];
|
||||
$data['name'] = json_encode($nameData);
|
||||
\Illuminate\Support\Facades\Log::info('mutateFormDataBeforeCreate Name JSON:', ['name' => $nameData]);
|
||||
|
||||
$descData = [
|
||||
'de' => $data['description_de'] ?? '',
|
||||
'en' => $data['description_en'] ?? '',
|
||||
];
|
||||
$data['description'] = json_encode($descData);
|
||||
\Illuminate\Support\Facades\Log::info('mutateFormDataBeforeCreate Description JSON:', ['description' => $descData]);
|
||||
|
||||
unset($data['name_de'], $data['name_en'], $data['description_de'], $data['description_en']);
|
||||
|
||||
\Illuminate\Support\Facades\Log::info('mutateFormDataBeforeCreate Final Data:', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -185,11 +174,28 @@ class CategoryResource extends Resource
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, BlogCategory $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Blog\Resources\CategoryResource\Pages;
|
||||
|
||||
use App\Filament\Blog\Resources\CategoryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreateCategory extends CreateRecord
|
||||
class CreateCategory extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
|
||||
@@ -3,17 +3,23 @@
|
||||
namespace App\Filament\Blog\Resources\CategoryResource\Pages;
|
||||
|
||||
use App\Filament\Blog\Resources\CategoryResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCategory extends EditRecord
|
||||
class EditCategory extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -40,12 +46,13 @@ class EditCategory extends EditRecord
|
||||
public function save(bool $shouldRedirect = true, bool $shouldSendSavedNotification = true): void
|
||||
{
|
||||
$state = $this->form->getState();
|
||||
\Illuminate\Support\Facades\Log::info('EditCategory Save - Full State:', $state);
|
||||
|
||||
$data = $state['data'] ?? $state;
|
||||
|
||||
$data = \App\Filament\Blog\Resources\CategoryResource::mutateFormDataBeforeSave($data);
|
||||
|
||||
$this->record->update($data);
|
||||
|
||||
parent::afterSave();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Filament\Blog\Traits\HasContentEditor;
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Models\BlogCategory;
|
||||
use App\Models\BlogPost;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -29,6 +30,7 @@ use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -243,11 +245,27 @@ class PostResource extends Resource
|
||||
->actions([
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-o-trash')
|
||||
->label(''),
|
||||
->label('')
|
||||
->after(fn (BlogPost $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Blog\Resources\PostResource\Pages;
|
||||
|
||||
use App\Filament\Blog\Resources\PostResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreatePost extends CreateRecord
|
||||
class CreatePost extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
}
|
||||
@@ -3,10 +3,11 @@
|
||||
namespace App\Filament\Blog\Resources\PostResource\Pages;
|
||||
|
||||
use App\Filament\Blog\Resources\PostResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPost extends EditRecord
|
||||
class EditPost extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
@@ -14,7 +15,12 @@ class EditPost extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
namespace App\Filament\Clusters\DailyOps\Resources\Photos\Pages;
|
||||
|
||||
use App\Filament\Clusters\DailyOps\Resources\Photos\PhotoResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPhoto extends EditRecord
|
||||
class EditPhoto extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PhotoResource::class;
|
||||
|
||||
@@ -15,7 +16,12 @@ class EditPhoto extends EditRecord
|
||||
{
|
||||
return [
|
||||
ViewAction::make(),
|
||||
DeleteAction::make(),
|
||||
DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Clusters\DailyOps\Resources\Photos\Tables;
|
||||
use App\Models\Event;
|
||||
use App\Models\Photo;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
@@ -208,6 +209,18 @@ class PhotosTable
|
||||
'moderated_at' => now(),
|
||||
'moderated_by' => Filament::auth()->id(),
|
||||
]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'photo.'.$status,
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'status',
|
||||
'moderation_notes',
|
||||
'moderated_at',
|
||||
'moderated_by',
|
||||
]),
|
||||
source: self::class
|
||||
);
|
||||
}
|
||||
|
||||
private static function applyModerationToRecords(Collection $records, string $status, ?string $notes): int
|
||||
@@ -215,7 +228,7 @@ class PhotosTable
|
||||
$moderatedAt = now();
|
||||
$moderatedBy = Filament::auth()->id();
|
||||
|
||||
return Photo::query()
|
||||
$updated = Photo::query()
|
||||
->whereIn('id', $records->pluck('id'))
|
||||
->where('status', 'pending')
|
||||
->update([
|
||||
@@ -224,6 +237,24 @@ class PhotosTable
|
||||
'moderated_at' => $moderatedAt,
|
||||
'moderated_by' => $moderatedBy,
|
||||
]);
|
||||
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->record(
|
||||
'photo.'.$status,
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'status',
|
||||
'moderation_notes',
|
||||
'moderated_at',
|
||||
'moderated_by',
|
||||
]),
|
||||
source: self::class
|
||||
);
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
private static function statusLabels(): array
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Clusters\RareAdmin\Resources\SuperAdminActionLogs\Pages;
|
||||
|
||||
use App\Filament\Clusters\RareAdmin\Resources\SuperAdminActionLogs\SuperAdminActionLogResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageSuperAdminActionLogs extends ManageRecords
|
||||
{
|
||||
protected static string $resource = SuperAdminActionLogResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Clusters\RareAdmin\Resources\SuperAdminActionLogs;
|
||||
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Filament\Clusters\RareAdmin\Resources\SuperAdminActionLogs\Pages\ManageSuperAdminActionLogs;
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class SuperAdminActionLogResource extends Resource
|
||||
{
|
||||
protected static ?string $model = SuperAdminActionLog::class;
|
||||
|
||||
protected static ?string $cluster = RareAdminCluster::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedClipboardDocumentList;
|
||||
|
||||
protected static ?int $navigationSort = 95;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'action';
|
||||
|
||||
public static function getNavigationGroup(): UnitEnum|string|null
|
||||
{
|
||||
return __('admin.nav.infrastructure');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return 'Audit log';
|
||||
}
|
||||
|
||||
public static function canCreate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canEdit($record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDelete($record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDeleteAny(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('action')
|
||||
->columns([
|
||||
TextColumn::make('action')
|
||||
->badge()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('actor.fullName')
|
||||
->label('Actor')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('subject_type')
|
||||
->label('Subject')
|
||||
->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—')
|
||||
->searchable()
|
||||
->toggleable(),
|
||||
TextColumn::make('subject_id')
|
||||
->label('Subject ID')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
TextColumn::make('metadata')
|
||||
->label('Fields')
|
||||
->formatStateUsing(function ($state): string {
|
||||
if (! is_array($state)) {
|
||||
return '—';
|
||||
}
|
||||
|
||||
$fields = $state['fields'] ?? [];
|
||||
|
||||
return $fields ? implode(', ', $fields) : '—';
|
||||
})
|
||||
->toggleable(isToggledHiddenByDefault: true)
|
||||
->wrap(),
|
||||
TextColumn::make('source')
|
||||
->label('Source')
|
||||
->limit(40)
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('occurred_at')
|
||||
->label('Timestamp')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageSuperAdminActionLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,17 @@
|
||||
namespace App\Filament\Resources\Coupons\Pages;
|
||||
|
||||
use App\Filament\Resources\Coupons\CouponResource;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
use App\Jobs\SyncCouponToPaddle;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCoupon extends CreateRecord
|
||||
class CreateCoupon extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = CouponResource::class;
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
parent::afterCreate();
|
||||
|
||||
SyncCouponToPaddle::dispatch($this->record);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
namespace App\Filament\Resources\Coupons\Pages;
|
||||
|
||||
use App\Filament\Resources\Coupons\CouponResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Jobs\SyncCouponToPaddle;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCoupon extends EditRecord
|
||||
class EditCoupon extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = CouponResource::class;
|
||||
|
||||
@@ -19,14 +20,34 @@ class EditCoupon extends EditRecord
|
||||
return [
|
||||
ViewAction::make(),
|
||||
DeleteAction::make()
|
||||
->after(fn ($record) => SyncCouponToPaddle::dispatch($record, true)),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
->after(function ($record): void {
|
||||
app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
|
||||
SyncCouponToPaddle::dispatch($record, true);
|
||||
}),
|
||||
ForceDeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'force_deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
RestoreAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'restored',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
parent::afterSave();
|
||||
|
||||
SyncCouponToPaddle::dispatch($this->record);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\Coupons\Pages;
|
||||
|
||||
use App\Filament\Resources\Coupons\CouponResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -13,7 +14,13 @@ class ViewCoupon extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources\Coupons\Tables;
|
||||
use App\Enums\CouponStatus;
|
||||
use App\Enums\CouponType;
|
||||
use App\Jobs\SyncCouponToPaddle;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -18,6 +19,7 @@ use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CouponsTable
|
||||
@@ -95,7 +97,13 @@ class CouponsTable
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Action::make('sync')
|
||||
->label(__('Sync to Paddle'))
|
||||
->icon('heroicon-m-arrow-path')
|
||||
@@ -104,9 +112,42 @@ class CouponsTable
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
ForceDeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'force_deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
RestoreBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'restored',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\EmotionResource\Pages;
|
||||
use App\Models\Emotion;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
@@ -17,6 +18,7 @@ use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class EmotionResource extends Resource
|
||||
@@ -116,10 +118,27 @@ class EmotionResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, Emotion $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\EmotionResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EmotionResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
@@ -13,7 +14,13 @@ class ManageEmotions extends ManageRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\Action::make('import')
|
||||
->label(__('admin.common.import_csv'))
|
||||
->icon('heroicon-o-arrow-up-tray')
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Exports\EventPurchaseExporter;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\EventPurchaseResource\Pages;
|
||||
use App\Models\EventPurchase;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -23,6 +24,7 @@ use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class EventPurchaseResource extends Resource
|
||||
@@ -174,11 +176,29 @@ class EventPurchaseResource extends Resource
|
||||
->action(function (EventPurchase $record) {
|
||||
$record->update(['refunded_at' => now()]);
|
||||
Log::info('Refund processed for purchase ID: '.$record->id);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'event_purchase.refunded',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['refunded_at']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
ExportBulkAction::make()
|
||||
->label('Export CSV')
|
||||
->exporter(EventPurchaseExporter::class),
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\EventPurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventPurchaseResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreateEventPurchase extends CreateRecord
|
||||
class CreateEventPurchase extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = EventPurchaseResource::class;
|
||||
}
|
||||
@@ -3,10 +3,11 @@
|
||||
namespace App\Filament\Resources\EventPurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventPurchaseResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditEventPurchase extends EditRecord
|
||||
class EditEventPurchase extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = EventPurchaseResource::class;
|
||||
|
||||
@@ -14,7 +15,12 @@ class EditEventPurchase extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\EventPurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventPurchaseResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -13,7 +14,13 @@ class ViewEventPurchase extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Models\Event;
|
||||
use App\Models\EventJoinTokenEvent;
|
||||
use App\Models\EventType;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use App\Support\JoinTokenLayoutRegistry;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
@@ -22,6 +23,7 @@ use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class EventResource extends Resource
|
||||
@@ -133,11 +135,26 @@ class EventResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, Event $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\Action::make('toggle')
|
||||
->label(__('admin.events.actions.toggle_active'))
|
||||
->icon('heroicon-o-power')
|
||||
->action(fn ($record) => $record->update(['is_active' => ! $record->is_active])),
|
||||
->action(function (Event $record): void {
|
||||
$record->update(['is_active' => ! $record->is_active]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'event.toggled',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_active']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Actions\Action::make('download_photos')
|
||||
->label(__('admin.events.actions.download_photos'))
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
@@ -243,7 +260,18 @@ class EventResource extends Resource
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\EventResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreateEvent extends CreateRecord
|
||||
class CreateEvent extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = EventResource::class;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\EventResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
|
||||
class EditEvent extends EditRecord
|
||||
class EditEvent extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = EventResource::class;
|
||||
}
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
namespace App\Filament\Resources\EventResource\RelationManagers;
|
||||
|
||||
use App\Models\EventPackage;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
@@ -93,15 +93,43 @@ class EventPackagesRelationManager extends RelationManager
|
||||
])
|
||||
->filters([])
|
||||
->headerActions([
|
||||
CreateAction::make(),
|
||||
CreateAction::make()
|
||||
->after(fn (array $data, EventPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, EventPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
DeleteAction::make()
|
||||
->after(fn (EventPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\EventTypeResource\Pages;
|
||||
use App\Models\EventType;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
@@ -16,6 +17,7 @@ use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class EventTypeResource extends Resource
|
||||
@@ -104,10 +106,27 @@ class EventTypeResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, EventType $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\EventTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EventTypeResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
@@ -13,7 +14,13 @@ class ManageEventTypes extends ManageRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\GiftVoucherResource\Pages;
|
||||
use App\Models\GiftVoucher;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use App\Services\GiftVouchers\GiftVoucherService;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
@@ -97,6 +98,13 @@ class GiftVoucherResource extends Resource
|
||||
->visible(fn (GiftVoucher $record): bool => $record->canBeRefunded())
|
||||
->action(function (GiftVoucher $record, GiftVoucherService $service): void {
|
||||
$service->refund($record, 'customer_request');
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'gift_voucher.refunded',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['status', 'refunded_at']),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->successNotificationTitle('Gutschein erstattet'),
|
||||
Action::make('resend')
|
||||
@@ -118,6 +126,13 @@ class GiftVoucherResource extends Resource
|
||||
$record,
|
||||
Carbon::parse($data['recipient_delivery_scheduled_at'])
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'gift_voucher.delivery_scheduled',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->visible(fn (GiftVoucher $record): bool => ! empty($record->recipient_email)),
|
||||
Action::make('mark_redeemed')
|
||||
@@ -136,6 +151,13 @@ class GiftVoucherResource extends Resource
|
||||
'manual_marked' => true,
|
||||
]),
|
||||
])->save();
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'gift_voucher.marked_redeemed',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['status', 'redeemed_at', 'metadata']),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->successNotificationTitle('Als eingelöst markiert'),
|
||||
]);
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
namespace App\Filament\Resources\GiftVoucherResource\Pages;
|
||||
|
||||
use App\Filament\Resources\GiftVoucherResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use App\Services\GiftVouchers\GiftVoucherService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ListGiftVouchers extends ListRecords
|
||||
@@ -62,7 +63,20 @@ class ListGiftVouchers extends ListRecords
|
||||
],
|
||||
];
|
||||
|
||||
$service->issueFromPaddle($payload);
|
||||
$voucher = $service->issueFromPaddle($payload);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'issued',
|
||||
$voucher,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'amount',
|
||||
'currency',
|
||||
'status',
|
||||
'expires_at',
|
||||
'coupon_id',
|
||||
]),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->modalHeading('Geschenkgutschein ausstellen'),
|
||||
];
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Filament\Resources\LegalPageResource\Pages;
|
||||
use App\Models\LegalPage;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
@@ -18,6 +19,7 @@ use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class LegalPageResource extends Resource
|
||||
@@ -99,10 +101,27 @@ class LegalPageResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, LegalPage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\LegalPageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\LegalPageResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
|
||||
class EditLegalPage extends EditRecord
|
||||
class EditLegalPage extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = LegalPageResource::class;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Filament\Resources\MediaStorageTargetResource\Pages;
|
||||
use App\Models\MediaStorageTarget;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
@@ -15,6 +16,7 @@ use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class MediaStorageTargetResource extends Resource
|
||||
@@ -115,10 +117,27 @@ class MediaStorageTargetResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, MediaStorageTarget $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace App\Filament\Resources\MediaStorageTargetResource\Pages;
|
||||
|
||||
use App\Filament\Resources\MediaStorageTargetResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreateMediaStorageTarget extends CreateRecord
|
||||
class CreateMediaStorageTarget extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = MediaStorageTargetResource::class;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
namespace App\Filament\Resources\MediaStorageTargetResource\Pages;
|
||||
|
||||
use App\Filament\Resources\MediaStorageTargetResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditMediaStorageTarget extends EditRecord
|
||||
class EditMediaStorageTarget extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = MediaStorageTargetResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\PackageAddonResource\Pages;
|
||||
use App\Jobs\SyncPackageAddonToPaddle;
|
||||
use App\Models\PackageAddon;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
@@ -17,6 +18,7 @@ use Filament\Tables;
|
||||
use Filament\Tables\Columns\BadgeColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PackageAddonResource extends Resource
|
||||
{
|
||||
@@ -130,11 +132,28 @@ class PackageAddonResource extends Resource
|
||||
->body('Das Add-on wird im Hintergrund mit Paddle abgeglichen.')
|
||||
->send();
|
||||
}),
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, PackageAddon $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\BulkActionGroup::make([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\PackageAddonResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackageAddonResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreatePackageAddon extends CreateRecord
|
||||
class CreatePackageAddon extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = PackageAddonResource::class;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\PackageAddonResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackageAddonResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
|
||||
class EditPackageAddon extends EditRecord
|
||||
class EditPackageAddon extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PackageAddonResource::class;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Filament\Resources\PackageResource\Pages;
|
||||
use App\Jobs\PullPackageFromPaddle;
|
||||
use App\Jobs\SyncPackageToPaddle;
|
||||
use App\Models\Package;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
@@ -37,6 +38,7 @@ use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
@@ -319,20 +321,75 @@ class PackageResource extends Resource
|
||||
->send();
|
||||
}),
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, Package $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
DeleteAction::make()
|
||||
->visible(fn (Package $record) => ! $record->trashed()),
|
||||
->visible(fn (Package $record) => ! $record->trashed())
|
||||
->after(fn (Package $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
RestoreAction::make()
|
||||
->visible(fn (Package $record) => $record->trashed()),
|
||||
->visible(fn (Package $record) => $record->trashed())
|
||||
->after(fn (Package $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'restored',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
ForceDeleteAction::make()
|
||||
->visible(fn (Package $record) => $record->trashed())
|
||||
->requiresConfirmation(),
|
||||
->requiresConfirmation()
|
||||
->after(fn (Package $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'force_deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
ForceDeleteBulkAction::make()->requiresConfirmation(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
RestoreBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'restored',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
ForceDeleteBulkAction::make()
|
||||
->requiresConfirmation()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'force_deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
namespace App\Filament\Resources\PackageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackageResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
|
||||
class CreatePackage extends CreateRecord
|
||||
class CreatePackage extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = PackageResource::class;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
namespace App\Filament\Resources\PackageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackageResource;
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPackage extends EditRecord
|
||||
class EditPackage extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PackageResource::class;
|
||||
|
||||
@@ -14,7 +15,12 @@ class EditPackage extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Filament/Resources/Pages/AuditedCreateRecord.php
Normal file
19
app/Filament/Resources/Pages/AuditedCreateRecord.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Pages;
|
||||
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class AuditedCreateRecord extends CreateRecord
|
||||
{
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$this->record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($this->form->getState()),
|
||||
static::class
|
||||
);
|
||||
}
|
||||
}
|
||||
25
app/Filament/Resources/Pages/AuditedEditRecord.php
Normal file
25
app/Filament/Resources/Pages/AuditedEditRecord.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Pages;
|
||||
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class AuditedEditRecord extends EditRecord
|
||||
{
|
||||
protected function afterSave(): void
|
||||
{
|
||||
$changed = array_keys($this->record->getChanges());
|
||||
|
||||
if ($changed === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$this->record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($changed ?: $this->form->getState()),
|
||||
static::class
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Filament\Clusters\DailyOps\DailyOpsCluster;
|
||||
use App\Filament\Resources\PhotoResource\Pages;
|
||||
use App\Models\Event;
|
||||
use App\Models\Photo;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
@@ -16,6 +17,7 @@ use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class PhotoResource extends Resource
|
||||
@@ -78,29 +80,95 @@ class PhotoResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, Photo $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\Action::make('feature')
|
||||
->label(__('admin.photos.actions.feature'))
|
||||
->visible(fn (Photo $record) => ! $record->is_featured)
|
||||
->action(fn (Photo $record) => $record->update(['is_featured' => true]))
|
||||
->action(function (Photo $record): void {
|
||||
$record->update(['is_featured' => true]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'photo.featured',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_featured']),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->icon('heroicon-o-star'),
|
||||
Actions\Action::make('unfeature')
|
||||
->label(__('admin.photos.actions.unfeature'))
|
||||
->visible(fn (Photo $record) => $record->is_featured)
|
||||
->action(fn (Photo $record) => $record->update(['is_featured' => false]))
|
||||
->action(function (Photo $record): void {
|
||||
$record->update(['is_featured' => false]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'photo.unfeatured',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_featured']),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->icon('heroicon-o-star'),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn (Photo $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\BulkAction::make('feature')
|
||||
->label(__('admin.photos.actions.feature_selected'))
|
||||
->icon('heroicon-o-star')
|
||||
->action(fn ($records) => $records->each->update(['is_featured' => true])),
|
||||
->action(function ($records): void {
|
||||
$records->each->update(['is_featured' => true]);
|
||||
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->record(
|
||||
'photo.featured',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_featured']),
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
Actions\BulkAction::make('unfeature')
|
||||
->label(__('admin.photos.actions.unfeature_selected'))
|
||||
->icon('heroicon-o-star')
|
||||
->action(fn ($records) => $records->each->update(['is_featured' => false])),
|
||||
Actions\DeleteBulkAction::make(),
|
||||
->action(function ($records): void {
|
||||
$records->each->update(['is_featured' => false]);
|
||||
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->record(
|
||||
'photo.unfeatured',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_featured']),
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Resources\PhotoResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\PhotoResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPhoto extends EditRecord
|
||||
class EditPhoto extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PhotoResource::class;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\PhotoboothSettings\PhotoboothSettingResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPhotoboothSetting extends EditRecord
|
||||
class EditPhotoboothSetting extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PhotoboothSettingResource::class;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Tables;
|
||||
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
@@ -29,7 +30,13 @@ class PhotoboothSettingsTable
|
||||
->label(__('Aktualisiert')),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
])
|
||||
->headerActions([])
|
||||
->bulkActions([]);
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Filament\Resources\PurchaseResource\Pages;
|
||||
use App\Models\PackagePurchase;
|
||||
use App\Notifications\Customer\RefundReceipt;
|
||||
use App\Notifications\Ops\RefundProcessed;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use App\Services\Paddle\PaddleTransactionService;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
@@ -27,6 +28,7 @@ use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
@@ -178,7 +180,13 @@ class PurchaseResource extends Resource
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, PackagePurchase $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Action::make('refund')
|
||||
->label('Refund')
|
||||
->color('danger')
|
||||
@@ -234,11 +242,29 @@ class PurchaseResource extends Resource
|
||||
if ($opsEmail) {
|
||||
Notification::route('mail', $opsEmail)->notify(new RefundProcessed($record, $refundSuccess, $reason, $errorMessage));
|
||||
}
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'purchase.refunded',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['refunded', 'metadata']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
])
|
||||
->emptyStateHeading('No Purchases Found')
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Resources\PurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
use App\Filament\Resources\PurchaseResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePurchase extends CreateRecord
|
||||
class CreatePurchase extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = PurchaseResource::class;
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace App\Filament\Resources\PurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\PurchaseResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPurchase extends EditRecord
|
||||
class EditPurchase extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = PurchaseResource::class;
|
||||
|
||||
@@ -14,7 +15,12 @@ class EditPurchase extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\PurchaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PurchaseResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -13,8 +14,19 @@ class ViewPurchase extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
Actions\Action::make('refund')
|
||||
->label('Refund')
|
||||
->color('danger')
|
||||
@@ -24,6 +36,13 @@ class ViewPurchase extends ViewRecord
|
||||
->action(function ($record) {
|
||||
$record->update(['refunded' => true]);
|
||||
// TODO: Call Paddle API for actual refund
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'purchase.refunded',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['refunded']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\TaskResource\Pages;
|
||||
use App\Models\Task;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
@@ -17,6 +18,7 @@ use Filament\Schemas\Components\Tabs\Tab as SchemaTab;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class TaskResource extends Resource
|
||||
@@ -163,11 +165,33 @@ class TaskResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, Task $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn (Task $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use App\Models\Task;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
@@ -14,7 +15,9 @@ use Illuminate\Support\Facades\Storage;
|
||||
class ImportTasks extends Page
|
||||
{
|
||||
protected static string $resource = TaskResource::class;
|
||||
|
||||
protected string $view = 'filament.resources.task-resource.pages.import-tasks';
|
||||
|
||||
protected ?string $heading = null;
|
||||
|
||||
public ?string $file = null;
|
||||
@@ -37,6 +40,7 @@ class ImportTasks extends Page
|
||||
$path = $this->form->getState()['file'] ?? null;
|
||||
if (! $path || ! Storage::disk('public')->exists($path)) {
|
||||
Notification::make()->danger()->title(__('admin.notifications.file_not_found'))->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,7 +101,7 @@ class ImportTasks extends Page
|
||||
$eventTypeId = DB::table('event_types')->where('slug', $eventTypeSlug)->value('id');
|
||||
}
|
||||
|
||||
Task::create([
|
||||
$task = Task::create([
|
||||
'emotion_id' => $emotionId,
|
||||
'event_type_id' => $eventTypeId,
|
||||
'title' => [
|
||||
@@ -117,6 +121,13 @@ class ImportTasks extends Page
|
||||
'is_active' => (int) ($row[$map['is_active']] ?? 1) ? 1 : 0,
|
||||
]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$task,
|
||||
SuperAdminAuditLogger::fieldsMetadata($task->getChanges()),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
$ok++;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
@@ -125,6 +136,7 @@ class ImportTasks extends Page
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return [$ok, $fail];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
@@ -13,7 +14,13 @@ class ManageTasks extends ManageRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'created',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\Action::make('import')
|
||||
->label(__('admin.common.import_csv'))
|
||||
->icon('heroicon-o-arrow-up-tray')
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\TenantFeedbackResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TenantFeedbackResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -13,7 +14,12 @@ class ViewTenantFeedback extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\TenantFeedbackResource\Tables;
|
||||
|
||||
use App\Models\TenantFeedback;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
@@ -182,11 +183,23 @@ class TenantFeedbackTable
|
||||
'moderated_at' => now(),
|
||||
'moderated_by' => Filament::auth()->id(),
|
||||
]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant_feedback.'.$status,
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'status',
|
||||
'moderation_notes',
|
||||
'moderated_at',
|
||||
'moderated_by',
|
||||
]),
|
||||
source: self::class
|
||||
);
|
||||
}
|
||||
|
||||
private static function applyModerationToRecords(Collection $records, string $status, ?string $notes): int
|
||||
{
|
||||
return TenantFeedback::query()
|
||||
$updated = TenantFeedback::query()
|
||||
->whereIn('id', $records->pluck('id'))
|
||||
->update([
|
||||
'status' => $status,
|
||||
@@ -194,6 +207,24 @@ class TenantFeedbackTable
|
||||
'moderated_at' => now(),
|
||||
'moderated_by' => Filament::auth()->id(),
|
||||
]);
|
||||
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->record(
|
||||
'tenant_feedback.'.$status,
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'status',
|
||||
'moderation_notes',
|
||||
'moderated_at',
|
||||
'moderated_by',
|
||||
]),
|
||||
source: self::class
|
||||
);
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
private static function statusLabels(): array
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\TenantPackageResource\Pages;
|
||||
use App\Models\TenantPackage;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
@@ -20,6 +21,7 @@ use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class TenantPackageResource extends Resource
|
||||
{
|
||||
@@ -68,13 +70,35 @@ class TenantPackageResource extends Resource
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, TenantPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
DeleteAction::make()
|
||||
->after(fn (TenantPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
]),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantPackageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
use App\Filament\Resources\TenantPackageResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTenantPackage extends CreateRecord
|
||||
class CreateTenantPackage extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = TenantPackageResource::class;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantPackageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\TenantPackageResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTenantPackage extends EditRecord
|
||||
class EditTenantPackage extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = TenantPackageResource::class;
|
||||
|
||||
@@ -14,7 +15,12 @@ class EditTenantPackage extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\TenantPackageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TenantPackageResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -13,8 +14,19 @@ class ViewTenantPackage extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Filament\Resources\TenantResource\Schemas\TenantInfolist;
|
||||
use App\Jobs\AnonymizeAccount;
|
||||
use App\Models\Tenant;
|
||||
use App\Notifications\InactiveTenantDeletionWarning;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use App\Services\Tenant\TenantLifecycleLogger;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
@@ -27,6 +28,7 @@ use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\Notification as NotificationFacade;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use UnitEnum;
|
||||
@@ -180,7 +182,13 @@ class TenantResource extends Resource
|
||||
])
|
||||
->filters([])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, Tenant $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\Action::make('add_package')
|
||||
->label('Package hinzufügen')
|
||||
->icon('heroicon-o-plus')
|
||||
@@ -213,6 +221,13 @@ class TenantResource extends Resource
|
||||
'price' => 0,
|
||||
'metadata' => ['reason' => $data['reason'] ?? 'manual assignment'],
|
||||
]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.package_added',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Actions\Action::make('export')
|
||||
->label('Daten exportieren')
|
||||
@@ -225,7 +240,18 @@ class TenantResource extends Resource
|
||||
->icon('heroicon-o-shield-exclamation'),
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\DeleteBulkAction::make(),
|
||||
Actions\DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -269,6 +295,13 @@ class TenantResource extends Resource
|
||||
actor: Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.activated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_active']),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
return $updated;
|
||||
}),
|
||||
Actions\Action::make('deactivate')
|
||||
@@ -287,6 +320,13 @@ class TenantResource extends Resource
|
||||
actor: Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.deactivated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_active']),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
return $updated;
|
||||
}),
|
||||
Actions\Action::make('suspend')
|
||||
@@ -305,6 +345,13 @@ class TenantResource extends Resource
|
||||
actor: Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.suspended',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_suspended']),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
return $updated;
|
||||
}),
|
||||
Actions\Action::make('unsuspend')
|
||||
@@ -322,6 +369,13 @@ class TenantResource extends Resource
|
||||
actor: Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.unsuspended',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['is_suspended']),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
return $updated;
|
||||
}),
|
||||
Actions\Action::make('schedule_deletion')
|
||||
@@ -374,6 +428,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.deletion_scheduled',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->successNotificationTitle(__('admin.tenants.actions.schedule_deletion_success')),
|
||||
Actions\Action::make('cancel_deletion')
|
||||
@@ -398,6 +459,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.deletion_cancelled',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['pending_deletion_at', 'deletion_warning_sent_at']),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->successNotificationTitle(__('admin.tenants.actions.cancel_deletion_success')),
|
||||
Actions\Action::make('anonymize_now')
|
||||
@@ -415,6 +483,13 @@ class TenantResource extends Resource
|
||||
'anonymize_requested',
|
||||
actor: Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.anonymize_requested',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata([]),
|
||||
source: static::class
|
||||
);
|
||||
})
|
||||
->successNotificationTitle(__('admin.tenants.actions.anonymize_success')),
|
||||
];
|
||||
@@ -472,6 +547,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.limits_updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Actions\Action::make('update_subscription_expires_at')
|
||||
->label(__('admin.tenants.actions.update_subscription_expires_at'))
|
||||
@@ -508,6 +590,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.subscription_expires_at_updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Actions\Action::make('set_grace_period')
|
||||
->label(__('admin.tenants.actions.set_grace_period'))
|
||||
@@ -537,6 +626,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.grace_period_set',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Actions\Action::make('clear_grace_period')
|
||||
->label(__('admin.tenants.actions.clear_grace_period'))
|
||||
@@ -560,6 +656,13 @@ class TenantResource extends Resource
|
||||
],
|
||||
Filament::auth()->user()
|
||||
);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant.grace_period_cleared',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['grace_period_ends_at']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\TenantResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTenant extends EditRecord
|
||||
class EditTenant extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = TenantResource::class;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Resources\TenantResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TenantResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
@@ -23,7 +24,13 @@ class ViewTenant extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
...TenantResource::lifecycleActions(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\TenantResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TenantResource;
|
||||
use App\Filament\Resources\TenantResource\Schemas\TenantLifecycleInfolist;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -25,7 +26,13 @@ class ViewTenantLifecycle extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\EditAction::make()
|
||||
->after(fn (array $data, $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Actions\ActionGroup::make(TenantResource::lifecycleActions())
|
||||
->label(__('admin.tenants.actions.lifecycle'))
|
||||
->icon('heroicon-o-shield-exclamation'),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\RelationManagers;
|
||||
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -15,6 +16,7 @@ use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PackagePurchasesRelationManager extends RelationManager
|
||||
{
|
||||
@@ -130,7 +132,18 @@ class PackagePurchasesRelationManager extends RelationManager
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,22 +2,19 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PurchasesRelationManager extends RelationManager
|
||||
{
|
||||
@@ -123,10 +120,19 @@ class PurchasesRelationManager extends RelationManager
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Filament\Resources\TenantResource\RelationManagers;
|
||||
|
||||
use App\Models\TenantPackage;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -17,6 +19,7 @@ use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class TenantPackagesRelationManager extends RelationManager
|
||||
{
|
||||
@@ -92,22 +95,57 @@ class TenantPackagesRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, TenantPackage $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
Action::make('activate')
|
||||
->label('Aktivieren')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->color('success')
|
||||
->action(fn ($record) => $record->update(['active' => true])),
|
||||
->action(function (TenantPackage $record): void {
|
||||
$record->update(['active' => true]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant_package.activated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['active']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
Action::make('deactivate')
|
||||
->label('Deaktivieren')
|
||||
->icon('heroicon-o-x-circle')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(fn ($record) => $record->update(['active' => false])),
|
||||
->action(function (TenantPackage $record): void {
|
||||
$record->update(['active' => false]);
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'tenant_package.deactivated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata(['active']),
|
||||
source: static::class
|
||||
);
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Clusters\WeeklyOps\WeeklyOpsCluster;
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Models\User;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
@@ -19,6 +20,7 @@ use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
@@ -98,12 +100,29 @@ class UserResource extends Resource
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
EditAction::make()
|
||||
->after(fn (array $data, User $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'updated',
|
||||
$record,
|
||||
SuperAdminAuditLogger::fieldsMetadata($data),
|
||||
static::class
|
||||
)),
|
||||
]),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
DeleteBulkAction::make()
|
||||
->after(function (Collection $records): void {
|
||||
$logger = app(SuperAdminAuditLogger::class);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$logger->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
);
|
||||
}
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedCreateRecord;
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
class CreateUser extends AuditedCreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
|
||||
@@ -2,19 +2,25 @@
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\Pages\AuditedEditRecord;
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
class EditUser extends AuditedEditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->after(fn ($record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
|
||||
'deleted',
|
||||
$record,
|
||||
source: static::class
|
||||
)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\SuperAdmin\Pages;
|
||||
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Models\GuestPolicySetting;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
@@ -163,6 +164,26 @@ class GuestPolicySettingsPage extends Page
|
||||
$settings->guest_notification_ttl_hours = $this->guest_notification_ttl_hours;
|
||||
$settings->save();
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'guest_policy.updated',
|
||||
$settings,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'guest_downloads_enabled',
|
||||
'guest_sharing_enabled',
|
||||
'guest_upload_visibility',
|
||||
'per_device_upload_limit',
|
||||
'join_token_failure_limit',
|
||||
'join_token_failure_decay_minutes',
|
||||
'join_token_access_limit',
|
||||
'join_token_access_decay_minutes',
|
||||
'join_token_download_limit',
|
||||
'join_token_download_decay_minutes',
|
||||
'share_link_ttl_hours',
|
||||
'guest_notification_ttl_hours',
|
||||
]),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title(__('admin.guest_policy.notifications.saved'))
|
||||
->success()
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\SuperAdmin\Pages;
|
||||
|
||||
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
|
||||
use App\Models\WatermarkSetting;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
@@ -128,6 +129,21 @@ class WatermarkSettingsPage extends Page
|
||||
$settings->offset_y = $this->offset_y;
|
||||
$settings->save();
|
||||
|
||||
app(SuperAdminAuditLogger::class)->record(
|
||||
'watermark_settings.updated',
|
||||
$settings,
|
||||
SuperAdminAuditLogger::fieldsMetadata([
|
||||
'asset',
|
||||
'position',
|
||||
'opacity',
|
||||
'scale',
|
||||
'padding',
|
||||
'offset_x',
|
||||
'offset_y',
|
||||
]),
|
||||
source: static::class
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title('Wasserzeichen aktualisiert')
|
||||
->success()
|
||||
|
||||
40
app/Models/SuperAdminActionLog.php
Normal file
40
app/Models/SuperAdminActionLog.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Prunable;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class SuperAdminActionLog extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\SuperAdminActionLogFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use Prunable;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'occurred_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function actor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'actor_id');
|
||||
}
|
||||
|
||||
public function subject(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function prunable(): Builder
|
||||
{
|
||||
return static::query()->where('occurred_at', '<=', now()->subMonths(6));
|
||||
}
|
||||
}
|
||||
122
app/Services/Audit/SuperAdminAuditLogger.php
Normal file
122
app/Services/Audit/SuperAdminAuditLogger.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Audit;
|
||||
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\User;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SuperAdminAuditLogger
|
||||
{
|
||||
/**
|
||||
* @param array<string, array<int, string>> $metadata
|
||||
*/
|
||||
public function record(
|
||||
string $action,
|
||||
?Model $subject = null,
|
||||
array $metadata = [],
|
||||
?User $actor = null,
|
||||
?string $source = null
|
||||
): ?SuperAdminActionLog {
|
||||
$actor = $actor ?? Filament::auth()->user();
|
||||
|
||||
if (! $this->shouldLog($actor)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = $this->sanitizeMetadata($metadata);
|
||||
|
||||
return SuperAdminActionLog::create([
|
||||
'actor_id' => $actor?->getKey(),
|
||||
'action' => $action,
|
||||
'subject_type' => $subject?->getMorphClass(),
|
||||
'subject_id' => $subject?->getKey(),
|
||||
'source' => $source,
|
||||
'metadata' => $metadata ?: null,
|
||||
'occurred_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<int, string>> $metadata
|
||||
*/
|
||||
public function recordModelMutation(
|
||||
string $operation,
|
||||
Model $record,
|
||||
array $metadata = [],
|
||||
?string $source = null,
|
||||
?User $actor = null
|
||||
): ?SuperAdminActionLog {
|
||||
$action = $this->formatAction($record, $operation);
|
||||
|
||||
return $this->record(
|
||||
$action,
|
||||
$record,
|
||||
$metadata,
|
||||
$actor,
|
||||
$source
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string>|array<string, mixed> $data
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
public static function fieldsMetadata(array $data): array
|
||||
{
|
||||
$fields = array_is_list($data) ? $data : array_keys($data);
|
||||
|
||||
return ['fields' => array_values(array_unique($fields))];
|
||||
}
|
||||
|
||||
private function shouldLog(?User $actor): bool
|
||||
{
|
||||
if (! $actor || $actor->role !== 'super_admin') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$panel = Filament::getCurrentPanel();
|
||||
|
||||
if ($panel) {
|
||||
return $panel->getId() === 'superadmin';
|
||||
}
|
||||
|
||||
if (app()->runningInConsole()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return request()->is('super-admin*');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<int, string>> $metadata
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
private function sanitizeMetadata(array $metadata): array
|
||||
{
|
||||
$sanitized = [];
|
||||
|
||||
foreach ($metadata as $key => $value) {
|
||||
if (! is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values = array_values(array_filter($value, fn ($item) => is_string($item) && $item !== ''));
|
||||
|
||||
if ($values === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized[$key] = $values;
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
private function formatAction(Model $record, string $operation): string
|
||||
{
|
||||
return Str::kebab(class_basename($record)).'.'.$operation;
|
||||
}
|
||||
}
|
||||
42
database/factories/SuperAdminActionLogFactory.php
Normal file
42
database/factories/SuperAdminActionLogFactory.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\SuperAdminActionLog>
|
||||
*/
|
||||
class SuperAdminActionLogFactory extends Factory
|
||||
{
|
||||
protected $model = SuperAdminActionLog::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'actor_id' => User::factory(),
|
||||
'action' => $this->faker->randomElement([
|
||||
'tenant.updated',
|
||||
'photo.approved',
|
||||
'user.created',
|
||||
]),
|
||||
'subject_type' => User::class,
|
||||
'subject_id' => User::factory(),
|
||||
'source' => $this->faker->randomElement([
|
||||
'App\\Filament\\Resources\\TenantResource',
|
||||
'App\\Filament\\Clusters\\DailyOps\\Resources\\Photos\\PhotoResource',
|
||||
]),
|
||||
'metadata' => [
|
||||
'fields' => ['status', 'moderated_at'],
|
||||
],
|
||||
'occurred_at' => now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('super_admin_action_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('actor_id')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->string('action', 120);
|
||||
$table->nullableMorphs('subject');
|
||||
$table->string('source', 200)->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamp('occurred_at')->useCurrent();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['action', 'occurred_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('super_admin_action_logs');
|
||||
}
|
||||
};
|
||||
19
database/seeders/SuperAdminActionLogSeeder.php
Normal file
19
database/seeders/SuperAdminActionLogSeeder.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SuperAdminActionLogSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
SuperAdminActionLog::factory()
|
||||
->count(10)
|
||||
->create();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Services\Monitoring\PackageLimitMetrics;
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
@@ -21,3 +23,7 @@ Artisan::command('metrics:package-limits {--reset}', function () {
|
||||
$this->comment('Package limit metrics cache was reset.');
|
||||
}
|
||||
})->purpose('Inspect package limit monitoring counters and optionally reset them');
|
||||
|
||||
Schedule::command('model:prune', [
|
||||
'--model' => [SuperAdminActionLog::class],
|
||||
])->daily();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Feature;
|
||||
use App\Filament\Clusters\DailyOps\Resources\Photos\Pages\ListPhotos;
|
||||
use App\Models\Event;
|
||||
use App\Models\Photo;
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Filament\Actions\Testing\TestAction;
|
||||
@@ -40,6 +41,10 @@ class PhotoModerationQueueTest extends TestCase
|
||||
$this->assertSame('Looks good.', $photo->moderation_notes);
|
||||
$this->assertNotNull($photo->moderated_at);
|
||||
$this->assertSame($user->id, $photo->moderated_by);
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'photo.approved')
|
||||
->where('subject_id', $photo->id)
|
||||
->exists());
|
||||
}
|
||||
|
||||
public function test_superadmin_can_bulk_reject_pending_photos(): void
|
||||
@@ -74,6 +79,10 @@ class PhotoModerationQueueTest extends TestCase
|
||||
$this->assertNotNull($photoB->moderated_at);
|
||||
$this->assertSame($user->id, $photoA->moderated_by);
|
||||
$this->assertSame($user->id, $photoB->moderated_by);
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'photo.rejected')
|
||||
->whereIn('subject_id', [$photoA->id, $photoB->id])
|
||||
->count() === 2);
|
||||
}
|
||||
|
||||
private function bootSuperAdminPanel(User $user): void
|
||||
|
||||
141
tests/Feature/SuperAdminAuditLogMutationTest.php
Normal file
141
tests/Feature/SuperAdminAuditLogMutationTest.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\GiftVoucherResource\Pages\ListGiftVouchers;
|
||||
use App\Filament\Resources\MediaStorageTargetResource\Pages\EditMediaStorageTarget;
|
||||
use App\Filament\Resources\TaskResource\Pages\ImportTasks;
|
||||
use App\Models\Emotion;
|
||||
use App\Models\EventType;
|
||||
use App\Models\GiftVoucher;
|
||||
use App\Models\MediaStorageTarget;
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Services\GiftVouchers\GiftVoucherService;
|
||||
use Filament\Actions\Testing\TestAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SuperAdminAuditLogMutationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_save_without_changes_does_not_create_audit_log(): void
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'super_admin']);
|
||||
$target = MediaStorageTarget::query()->create([
|
||||
'key' => 'hot-storage',
|
||||
'name' => 'Hot Storage',
|
||||
'driver' => 'local',
|
||||
'config' => [],
|
||||
'is_hot' => true,
|
||||
'is_default' => false,
|
||||
'is_active' => true,
|
||||
'priority' => 0,
|
||||
]);
|
||||
|
||||
$this->bootSuperAdminPanel($user);
|
||||
|
||||
Livewire::test(EditMediaStorageTarget::class, ['record' => $target->getKey()])
|
||||
->call('save');
|
||||
|
||||
$this->assertFalse(SuperAdminActionLog::query()->exists());
|
||||
}
|
||||
|
||||
public function test_gift_voucher_issue_action_creates_audit_log(): void
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'super_admin']);
|
||||
$voucher = GiftVoucher::factory()->create();
|
||||
|
||||
$this->bootSuperAdminPanel($user);
|
||||
|
||||
$this->mock(GiftVoucherService::class, function ($mock) use ($voucher): void {
|
||||
$mock->shouldReceive('issueFromPaddle')
|
||||
->once()
|
||||
->andReturn($voucher);
|
||||
});
|
||||
|
||||
Livewire::test(ListGiftVouchers::class)
|
||||
->callAction(TestAction::make('issue'), [
|
||||
'amount' => 25,
|
||||
'currency' => 'EUR',
|
||||
'purchaser_email' => 'buyer@example.com',
|
||||
]);
|
||||
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'gift-voucher.issued')
|
||||
->where('subject_id', $voucher->id)
|
||||
->exists());
|
||||
}
|
||||
|
||||
public function test_task_import_creates_audit_logs(): void
|
||||
{
|
||||
Storage::fake('public');
|
||||
|
||||
$user = User::factory()->create(['role' => 'super_admin']);
|
||||
$emotion = Emotion::factory()->create(['name' => ['de' => 'Freude', 'en' => 'Joy']]);
|
||||
$eventType = EventType::factory()->create(['slug' => 'party']);
|
||||
|
||||
$headers = [
|
||||
'emotion_name',
|
||||
'emotion_name_de',
|
||||
'emotion_name_en',
|
||||
'event_type_slug',
|
||||
'title_de',
|
||||
'title_en',
|
||||
'description_de',
|
||||
'description_en',
|
||||
'difficulty',
|
||||
'example_text_de',
|
||||
'example_text_en',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
$row = [
|
||||
'',
|
||||
$emotion->name['de'],
|
||||
$emotion->name['en'],
|
||||
$eventType->slug,
|
||||
'Aufgabe',
|
||||
'Task',
|
||||
'Beschreibung',
|
||||
'Description',
|
||||
'easy',
|
||||
'Beispiel',
|
||||
'Example',
|
||||
'1',
|
||||
'1',
|
||||
];
|
||||
|
||||
$csv = implode(',', $headers)."\n".implode(',', $row)."\n";
|
||||
|
||||
Storage::disk('public')->put('imports/tasks.csv', $csv);
|
||||
|
||||
$this->bootSuperAdminPanel($user);
|
||||
|
||||
$component = app(ImportTasks::class);
|
||||
$method = new \ReflectionMethod($component, 'importTasksCsv');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($component, Storage::disk('public')->path('imports/tasks.csv'));
|
||||
|
||||
$this->assertSame(1, Task::query()->count());
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'task.created')
|
||||
->exists());
|
||||
}
|
||||
|
||||
private function bootSuperAdminPanel(User $user): void
|
||||
{
|
||||
$panel = Filament::getPanel('superadmin');
|
||||
|
||||
$this->assertNotNull($panel);
|
||||
|
||||
Filament::setCurrentPanel($panel);
|
||||
Filament::bootCurrentPanel();
|
||||
Filament::auth()->login($user);
|
||||
}
|
||||
}
|
||||
42
tests/Feature/SuperAdminAuditLogSettingsTest.php
Normal file
42
tests/Feature/SuperAdminAuditLogSettingsTest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\SuperAdmin\Pages\GuestPolicySettingsPage;
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\User;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SuperAdminAuditLogSettingsTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_superadmin_settings_save_creates_audit_log(): void
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'super_admin']);
|
||||
|
||||
$this->bootSuperAdminPanel($user);
|
||||
|
||||
Livewire::test(GuestPolicySettingsPage::class)
|
||||
->set('guest_downloads_enabled', false)
|
||||
->call('save');
|
||||
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'guest_policy.updated')
|
||||
->exists());
|
||||
}
|
||||
|
||||
private function bootSuperAdminPanel(User $user): void
|
||||
{
|
||||
$panel = Filament::getPanel('superadmin');
|
||||
|
||||
$this->assertNotNull($panel);
|
||||
|
||||
Filament::setCurrentPanel($panel);
|
||||
Filament::bootCurrentPanel();
|
||||
Filament::auth()->login($user);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\TenantResource\Pages\ListTenants;
|
||||
use App\Jobs\AnonymizeAccount;
|
||||
use App\Models\SuperAdminActionLog;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\TenantLifecycleEvent;
|
||||
use App\Models\User;
|
||||
@@ -79,6 +80,10 @@ class TenantLifecycleActionsTest extends TestCase
|
||||
|
||||
$tenant->refresh();
|
||||
$this->assertFalse((bool) $tenant->is_active);
|
||||
$this->assertTrue(SuperAdminActionLog::query()
|
||||
->where('action', 'tenant.deactivated')
|
||||
->where('subject_id', $tenant->id)
|
||||
->exists());
|
||||
$this->assertTrue(TenantLifecycleEvent::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('type', 'deactivated')
|
||||
|
||||
Reference in New Issue
Block a user