fix: final review wave — public routes, inbox 419, url schemes, deep_link, tests
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BCrwHHnGCB5XH968Nxokqw
This commit is contained in:
@@ -91,7 +91,7 @@ class InboxController extends Controller
|
||||
/** Пропускает только относительные пути и http(s)-ссылки; прочее (напр. javascript:) отбрасывает. */
|
||||
public static function safeLink(?string $link): ?string
|
||||
{
|
||||
return $link !== null && preg_match('#^(/(?!/)|https?://)#i', $link) === 1 ? $link : null;
|
||||
return $link !== null && preg_match('#^(/(?![/\\\\])|https?://)#i', $link) === 1 ? $link : null;
|
||||
}
|
||||
|
||||
public static function relative(int $diff): string
|
||||
|
||||
@@ -31,8 +31,8 @@ class ChannelsController extends Controller
|
||||
$data = $request->validate([
|
||||
'type' => ['required', 'in:webhook'],
|
||||
'config' => ['required', 'array'],
|
||||
'config.deliver_url' => ['required', 'url'],
|
||||
'config.presence_url' => ['required', 'url'],
|
||||
'config.deliver_url' => ['required', 'url:http,https'],
|
||||
'config.presence_url' => ['required', 'url:http,https'],
|
||||
], ['type.in' => 'Через API добавляется только webhook; Telegram подключается через /start.']);
|
||||
|
||||
$channel = $user->channels()->create([
|
||||
|
||||
@@ -10,9 +10,11 @@ use App\Ingest\EventUpserter;
|
||||
use App\Models\Event;
|
||||
use App\Models\Source;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class EventsController extends Controller
|
||||
{
|
||||
@@ -22,11 +24,14 @@ class EventsController extends Controller
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->attributes->get('user');
|
||||
$request->validate([
|
||||
'state' => ['sometimes', Rule::in(array_map(fn ($s) => $s->value, EventState::cases()))],
|
||||
]);
|
||||
$states = $request->query('state')
|
||||
? [$request->query('state')]
|
||||
: EventState::nonTerminalValues();
|
||||
|
||||
$events = $user->events()->whereIn('state', $states)->orderBy('due_date')->orderBy('due_instant')->get();
|
||||
$events = $user->events()->with('source')->whereIn('state', $states)->orderBy('due_date')->orderBy('due_instant')->get();
|
||||
|
||||
return response()->json(['events' => $events->map(fn (Event $e) => ApiPresenter::present($e) + ['source' => $e->source->name])->values()]);
|
||||
}
|
||||
@@ -66,7 +71,7 @@ class EventsController extends Controller
|
||||
'source_ref' => 'manual:'.Str::ulid(),
|
||||
'topic' => '',
|
||||
'due_at' => $dueAt,
|
||||
'fire_at' => \Carbon\CarbonImmutable::createFromFormat('Y-m-d', $data['due_date'])->subDays(30)->format('Y-m-d'),
|
||||
'fire_at' => CarbonImmutable::createFromFormat('Y-m-d', $data['due_date'])->subDays(30)->format('Y-m-d'),
|
||||
'after_due' => 'keep',
|
||||
'payload' => array_filter([
|
||||
'title' => $data['title'],
|
||||
|
||||
@@ -10,7 +10,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
@@ -33,7 +33,7 @@ class ProfileController extends Controller
|
||||
$start = $data['quiet_start'] ?? substr($user->quiet_start, 0, 5);
|
||||
$end = $data['quiet_end'] ?? substr($user->quiet_end, 0, 5);
|
||||
if ($end >= $start) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
throw ValidationException::withMessages([
|
||||
'quiet_end' => 'Тихие часы должны переходить через полночь: quiet_end раньше quiet_start',
|
||||
]);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class ProfileController extends Controller
|
||||
'tz' => $user->tz,
|
||||
'quiet_start' => substr($user->quiet_start, 0, 5),
|
||||
'quiet_end' => substr($user->quiet_end, 0, 5),
|
||||
'badge' => $user->events()->whereIn('state', [EventState::Today->value, EventState::Overdue->value])->count(),
|
||||
'badge' => $user->events()->whereIn('state', EventState::badgeValues())->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class UpsertEventsRequest extends FormRequest
|
||||
'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.deep_link' => ['sometimes', 'nullable', 'string', 'max:2048', 'regex:#^(/(?![/\\\\])|https?://)#i'],
|
||||
'events.*.payload.done_label' => ['sometimes', 'nullable', 'string', 'max:64'],
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user