user(); assert($user !== null); return Inertia::render('settings/notifications', [ 'types' => array_map(fn (NotificationTypeDefinition $type): array => [ 'key' => $type->key, 'label' => $type->label, 'email_enabled' => $this->preferences->emailEnabledFor($user, $type), ], $this->emailable()), ]); } public function update(Request $request): RedirectResponse { $user = $request->user(); assert($user !== null); $keys = $this->emailableKeys(); $validated = $request->validate([ // Bounded by the registry, and unique on the type. The // Rule::in below checks each value; it says nothing about how // many there are or whether they repeat, and the loop writes // one row per element. The count comes from the registry // rather than a literal because the registry is open -- // modules register their own types into it, so a number here // would be wrong the moment one does. 'preferences' => ['required', 'array', 'max:'.count($keys)], // Against the registry, not merely "a string": a preference row // for a type nothing can send is a row that will never be read // again, and the screen only ever offers back what edit() gave // it. 'preferences.*.type' => ['required', 'string', 'distinct', Rule::in($keys)], 'preferences.*.email_enabled' => ['required', 'boolean'], ]); foreach ($validated['preferences'] as $preference) { NotificationPreference::query()->updateOrCreate( ['user_id' => $user->id, 'type' => $preference['type']], ['email_enabled' => $preference['email_enabled']], ); } return back(); } /** * Only types that can email at all have anything to opt in or out of — * a pure in-app type has no toggle to show. Either route counts: * Notifier sending a mail class directly, or the digest buffering and * sending one. * * Shared by both halves on purpose, so what the screen offers and what * it accepts back cannot drift apart. * * @return list */ private function emailable(): array { return array_values(array_filter( $this->types->all(), fn (NotificationTypeDefinition $type): bool => $type->mailNotification !== null || $type->digestMail !== null, )); } /** * @return list */ private function emailableKeys(): array { return array_map(fn (NotificationTypeDefinition $type): string => $type->key, $this->emailable()); } }