im profil kann ein nutzer nun seine daten exportieren. man kann seinen account löschen. nach 2 jahren werden inaktive accounts gelöscht, 1 monat vorher wird eine email geschickt. Hilfetexte und Legal Pages in der Guest PWA korrigiert und vom layout her optimiert (dark mode).

This commit is contained in:
Codex Agent
2025-11-10 19:55:46 +01:00
parent 447a90a742
commit 2587b2049d
37 changed files with 1650 additions and 50 deletions

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Services\Compliance;
use App\Models\CheckoutSession;
use App\Models\Event;
use App\Models\EventJoinToken;
use App\Models\EventMediaAsset;
use App\Models\EventMember;
use App\Models\PackagePurchase;
use App\Models\Photo;
use App\Models\Tenant;
use App\Models\TenantPackage;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class AccountAnonymizer
{
public function anonymize(User $user): void
{
DB::transaction(function () use ($user) {
$tenant = $user->tenant;
if ($tenant) {
$this->purgeTenantMedia($tenant);
$this->scrubTenantData($tenant);
}
$this->scrubUser($user);
});
}
public function anonymizeTenantOnly(Tenant $tenant): void
{
DB::transaction(function () use ($tenant) {
$this->purgeTenantMedia($tenant);
$this->scrubTenantData($tenant);
});
}
protected function purgeTenantMedia(Tenant $tenant): void
{
EventMediaAsset::query()
->whereHas('event', fn ($query) => $query->where('tenant_id', $tenant->id))
->chunkById(100, function ($assets) {
foreach ($assets as $asset) {
if ($asset->disk && $asset->path) {
Storage::disk($asset->disk)->delete($asset->path);
}
$asset->delete();
}
});
Photo::query()
->where(function ($query) use ($tenant) {
$query->where('tenant_id', $tenant->id)
->orWhereHas('event', fn ($inner) => $inner->where('tenant_id', $tenant->id));
})
->chunkById(200, function ($photos) {
foreach ($photos as $photo) {
$photo->delete();
}
});
}
protected function scrubTenantData(Tenant $tenant): void
{
$eventIds = Event::query()
->where('tenant_id', $tenant->id)
->pluck('id');
EventJoinToken::query()->whereIn('event_id', $eventIds)->delete();
EventMember::query()->where('tenant_id', $tenant->id)->delete();
Event::query()
->whereIn('id', $eventIds)
->chunkById(100, function ($events) {
foreach ($events as $event) {
$event->forceFill([
'name' => ['de' => 'Anonymisiertes Event', 'en' => 'Anonymized Event'],
'description' => null,
'location' => null,
'slug' => 'anonymized-event-'.$event->id,
'status' => 'archived',
'photobooth_enabled' => false,
'photobooth_path' => null,
])->save();
}
});
PackagePurchase::query()
->where('tenant_id', $tenant->id)
->update([
'ip_address' => null,
'user_agent' => null,
]);
CheckoutSession::query()
->where('tenant_id', $tenant->id)
->update([
'tenant_id' => null,
'user_id' => null,
'provider_metadata' => [],
]);
TenantPackage::query()
->where('tenant_id', $tenant->id)
->update(['active' => false]);
$tenant->forceFill([
'name' => 'Anonymisierter Tenant #'.$tenant->id,
'slug' => 'anonymized-tenant-'.$tenant->id,
'contact_name' => null,
'contact_email' => null,
'contact_phone' => null,
'email' => null,
'domain' => null,
'custom_domain' => null,
'user_id' => null,
'anonymized_at' => now(),
'pending_deletion_at' => null,
'deletion_warning_sent_at' => null,
'subscription_status' => 'deleted',
'is_active' => 0,
'is_suspended' => 1,
])->save();
}
protected function scrubUser(User $user): void
{
$placeholderEmail = sprintf('deleted+%s@fotospiel.app', $user->id);
$user->forceFill([
'name' => 'Anonymisierter Benutzer',
'email' => $placeholderEmail,
'username' => null,
'first_name' => null,
'last_name' => null,
'address' => null,
'phone' => null,
'password' => Str::random(40),
'remember_token' => null,
'pending_purchase' => false,
'email_verified_at' => null,
'role' => 'user',
])->save();
}
}

View File

@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
use RuntimeException;
use Symfony\Component\Finder\SplFileInfo;
@@ -22,6 +23,7 @@ class HelpSyncService
{
$environment = new Environment;
$environment->addExtension(new CommonMarkCoreExtension);
$environment->addExtension(new TableExtension);
$this->converter = new MarkdownConverter($environment);
}