updateOrCreate ersetzt durch eine Ownership-Prüfung: ein fremder, gesetzter Eigentümer wird abgelehnt (409), bestätigte Abos werden nicht still ent-bestätigt. Verwaiste Datensätze (user_id NULL nach Konto- Löschung/Legacy-Import) dürfen neu gebunden werden, statt dauerhaft gesperrt zu bleiben. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
3.1 KiB
PHP
80 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\V1;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Api\V1\SubscribeNewsletterRequest;
|
|
use App\Models\NewsletterSubscription;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class NewsletterSubscriptionController extends Controller
|
|
{
|
|
public function store(SubscribeNewsletterRequest $request): JsonResponse
|
|
{
|
|
$validated = $request->validated();
|
|
|
|
$email = mb_strtolower($validated['email']);
|
|
|
|
$subscription = DB::transaction(function () use ($request, $validated, $email): NewsletterSubscription {
|
|
$subscription = NewsletterSubscription::withoutGlobalScopes()
|
|
->where('portal', $validated['portal'])
|
|
->where('email', $email)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
// Nur ein fremder, tatsächlich gesetzter Eigentümer blockiert (409).
|
|
// Verwaiste Datensätze (user_id NULL nach Konto-Löschung/Legacy-Import)
|
|
// dürfen neu gebunden werden — sonst wäre die portal+email-Kombination
|
|
// dauerhaft nicht mehr abonnierbar.
|
|
if ($subscription !== null && $subscription->user_id !== null && $subscription->user_id !== $request->user()->id) {
|
|
abort(409, 'This email address is already subscribed.');
|
|
}
|
|
|
|
if ($subscription === null) {
|
|
return NewsletterSubscription::withoutGlobalScopes()->create([
|
|
'portal' => $validated['portal'],
|
|
'email' => $email,
|
|
'user_id' => $request->user()->id,
|
|
'salutation_key' => $validated['salutation_key'] ?? null,
|
|
'first_name' => $validated['first_name'] ?? null,
|
|
'last_name' => $validated['last_name'] ?? null,
|
|
'ip_address' => $request->ip(),
|
|
'is_confirmed' => false,
|
|
'confirmation_token' => Str::random(32),
|
|
'subscribed_at' => now(),
|
|
'unsubscribed_at' => null,
|
|
]);
|
|
}
|
|
|
|
$attributes = [
|
|
'user_id' => $request->user()->id,
|
|
'salutation_key' => $validated['salutation_key'] ?? null,
|
|
'first_name' => $validated['first_name'] ?? null,
|
|
'last_name' => $validated['last_name'] ?? null,
|
|
'ip_address' => $request->ip(),
|
|
'subscribed_at' => now(),
|
|
'unsubscribed_at' => null,
|
|
];
|
|
|
|
if (! $subscription->is_confirmed) {
|
|
$attributes['confirmation_token'] = Str::random(32);
|
|
}
|
|
|
|
$subscription->update($attributes);
|
|
|
|
return $subscription;
|
|
});
|
|
|
|
return response()->json([
|
|
'message' => 'Newsletter subscription created.',
|
|
'data' => [
|
|
'id' => $subscription->id,
|
|
'portal' => $subscription->portal->value,
|
|
'email' => $subscription->email,
|
|
'is_confirmed' => $subscription->is_confirmed,
|
|
],
|
|
], 201);
|
|
}
|
|
}
|