['required', 'array', 'min:1', 'max:500'], 'events.*.user' => ['required', 'string', 'max:255'], 'events.*.source_ref' => ['required', 'string', 'max:255'], 'events.*.topic' => ['sometimes', 'nullable', 'string', 'max:255'], 'events.*.due_at' => ['required', 'string'], 'events.*.fire_at' => ['required', 'date_format:Y-m-d'], 'events.*.after_due' => ['required', 'in:keep,expire'], 'events.*.payload' => ['required', 'array'], 'events.*.payload.title' => ['required', 'string'], 'events.*.payload.subtitle' => ['sometimes', 'nullable', 'string'], 'events.*.payload.deep_link' => ['sometimes', 'nullable', 'string', 'max:2048', 'regex:#^(/|https?://)#'], 'events.*.payload.done_label' => ['sometimes', 'nullable', 'string', 'max:64'], ]; } public function messages(): array { return [ 'events.*.payload.deep_link.regex' => 'deep_link должен быть относительным путём или http(s)-ссылкой', ]; } public function after(): array { return [function (Validator $v) { foreach ((array) $this->input('events', []) as $i => $item) { if (! is_array($item) || ! isset($item['due_at'], $item['fire_at']) || ! is_string($item['due_at'])) { continue; } try { $due = DueAtParser::parse($item['due_at']); } catch (InvalidArgumentException $e) { $v->errors()->add("events.$i.due_at", $e->getMessage()); continue; } // Проверка fire_at <= день срока — по поясу по умолчанию; для fixed это // приближение, точный день пользователя известен только при upsert. if ($v->errors()->missing("events.$i.fire_at") && $item['fire_at'] > $due->dayFor(config('hado.default_tz'))) { $v->errors()->add("events.$i.fire_at", 'fire_at не может быть позже дня срока'); } } }]; } /** @return list> */ public function items(): array { return array_map(function (array $item) { $item['topic'] = (string) ($item['topic'] ?? ''); return $item; }, $this->validated()['events']); } }