Feature: Data Access and Data Export Admin Service
This commit introduces the data retention and data export Admin Services, accessed by the Boboko admin UI
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Privacy\RelationManagers;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Infolists\Components\RepeatableEntry;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Modules\Core\Customer\RelationManagers\UserRelationManager as CoreUserRelationManager;
|
||||
use Modules\Core\Privacy\Models\DataErasureRequest;
|
||||
use Modules\Core\Privacy\PrivacyService;
|
||||
|
||||
/**
|
||||
* Extends core's own Customer -> User relation manager to add:
|
||||
* - a "Privacy Requests" row action, since a User's erasure/export requests
|
||||
* can't be shown as a nested relation manager two levels deep (Customer ->
|
||||
* User -> Requests isn't a shape Filament relation managers support) — a
|
||||
* modal listing that specific User's requests is the practical alternative.
|
||||
* - a "Request Erasure" row action — the User-scoped panel entry point,
|
||||
* mirroring Modules\Core\Privacy\Filament\Extensions\
|
||||
* CustomerErasureActionsExtension on the Customer side, including the same
|
||||
* "erase immediately" checkbox for a staff-triggered urgent request.
|
||||
* See docs/privacy.md "User-scope vs Customer-scope".
|
||||
*/
|
||||
class UserRelationManager extends CoreUserRelationManager
|
||||
{
|
||||
public function getDefaultTable(Table $table): Table
|
||||
{
|
||||
$table = parent::getDefaultTable($table);
|
||||
|
||||
return $table->actions([
|
||||
...$table->getActions(),
|
||||
Action::make('privacyRequests')
|
||||
->label('Privacy Requests')
|
||||
->icon('heroicon-o-shield-exclamation')
|
||||
->modalHeading(fn (Model $record) => "Privacy requests for {$record->name}")
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('Close')
|
||||
->infolist(fn (Model $record) => $this->requestsInfolist($record)),
|
||||
Action::make('requestErasure')
|
||||
->label('Request Erasure')
|
||||
->icon('heroicon-o-shield-exclamation')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Opens a cancellable grace-period erasure request for this individual — deactivates their login and detaches them from every linked Customer account once it completes. No Customer account\'s own data is affected.')
|
||||
->form([
|
||||
Checkbox::make('immediate')
|
||||
->label('Erase immediately (skip the 30-day grace period)')
|
||||
->helperText('Staff-only, for a formal legal request or regulator inquiry that genuinely requires urgency — not a routine deletion. Runs synchronously, cannot be cancelled once submitted.')
|
||||
->default(false),
|
||||
])
|
||||
->action(function (Model $record, array $data) {
|
||||
$privacyService = app(PrivacyService::class);
|
||||
$staff = auth('staff')->user();
|
||||
|
||||
if ($data['immediate']) {
|
||||
$privacyService->requestImmediateErasureForUser($record, $staff);
|
||||
|
||||
Notification::make()
|
||||
->title('User erased')
|
||||
->body('Erasure ran immediately — see the Erasure Requests list for the outcome.')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$privacyService->requestErasureForUser($record, $staff);
|
||||
|
||||
Notification::make()
|
||||
->title('Erasure requested')
|
||||
->body('The grace period starts now, and this user\'s login is deactivated immediately — see the Erasure Requests list.')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
private function requestsInfolist(Model $record): array
|
||||
{
|
||||
return [
|
||||
TextEntry::make('erasure_heading')
|
||||
->label('')
|
||||
->state('Erasure Requests'),
|
||||
RepeatableEntry::make('erasureRequests')
|
||||
->label('')
|
||||
->state(fn () => $record->erasureRequests()->latest()->get())
|
||||
->schema([
|
||||
TextEntry::make('status')->formatStateUsing(fn ($state) => ucfirst($state->value)),
|
||||
TextEntry::make('scheduled_for')->dateTime(),
|
||||
TextEntry::make('requestedBy')->label('Requested by')->state(
|
||||
fn (DataErasureRequest $record) => DataErasureRequest::displayNameFor($record->requestedBy)
|
||||
),
|
||||
])
|
||||
->columns(3),
|
||||
TextEntry::make('export_heading')
|
||||
->label('')
|
||||
->state('Export Requests'),
|
||||
RepeatableEntry::make('exportRequests')
|
||||
->label('')
|
||||
->state(fn () => $record->exportRequests()->latest()->get())
|
||||
->schema([
|
||||
TextEntry::make('status')->formatStateUsing(fn ($state) => ucfirst($state->value)),
|
||||
TextEntry::make('created_at')->label('Requested at')->dateTime(),
|
||||
])
|
||||
->columns(2),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user