StoreDiscussionRequest.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. <?php
  2. namespace App\Http\Requests\V3;
  3. use App\Models\Discussion;
  4. use Illuminate\Foundation\Http\FormRequest;
  5. use Illuminate\Validation\Rule;
  6. /**
  7. * `POST /v3/discussions` 的入参:发起话题,或新建一条批注。
  8. *
  9. * 不收 content_type:v3 只写 markdown(v6 已没有 html 编辑器)。
  10. * severity 的取值随 type 而定,见 {@see Discussion::SEVERITIES};不收 severity 的类型传了就 422。
  11. *
  12. * 批注(note / commentary)与评论线程的规则不同:只能挂在句子上、标题可省、正文必填,
  13. * commentary 的正文必须是义注句子模板;锚点(pos_* / quote_*)只有批注收。
  14. */
  15. class StoreDiscussionRequest extends FormRequest
  16. {
  17. private const ANCHOR_FIELDS = ['pos_start', 'pos_end', 'quote_exact', 'quote_prefix', 'quote_suffix'];
  18. public function authorize(): bool
  19. {
  20. return true;
  21. }
  22. /**
  23. * @return array<string, array<int, mixed>>
  24. */
  25. public function rules(): array
  26. {
  27. $type = $this->input('type', 'discussion');
  28. $severities = Discussion::SEVERITIES[$type] ?? [];
  29. $isAnnotation = in_array($type, Discussion::INJECTED_TYPES, true);
  30. // 锚点只有批注收:评论线程传了 422,免得静默丢掉
  31. $anchor = fn (array $rules) => $isAnnotation ? ['sometimes', 'nullable', ...$rules] : ['prohibited'];
  32. return [
  33. 'target_type' => ['required', 'string', Rule::in($isAnnotation ? ['sentence'] : Discussion::TARGET_TYPES)],
  34. 'target_id' => ['required', 'uuid'],
  35. 'type' => ['sometimes', 'string', Rule::in(Discussion::COMMENT_TYPES)],
  36. 'title' => $isAnnotation
  37. ? ['sometimes', 'nullable', 'string', 'max:256']
  38. : ['required', 'string', 'max:256'],
  39. 'content' => match ($type) {
  40. Discussion::TYPE_COMMENTARY => ['required', 'string', 'max:20000', 'regex:'.Discussion::COMMENTARY_PATTERN],
  41. Discussion::TYPE_NOTE => ['required', 'string', 'max:20000'],
  42. default => ['sometimes', 'nullable', 'string', 'max:20000'],
  43. },
  44. 'severity' => $severities === []
  45. ? ['prohibited']
  46. : ['sometimes', 'nullable', 'string', Rule::in($severities)],
  47. 'pos_start' => $anchor(['integer', 'min:0']),
  48. 'pos_end' => $anchor(['integer', 'min:0', 'gte:pos_start']),
  49. 'quote_exact' => $anchor(['string', 'max:1024']),
  50. 'quote_prefix' => $anchor(['string', 'max:1024']),
  51. 'quote_suffix' => $anchor(['string', 'max:1024']),
  52. 'tpl_id' => ['sometimes', 'nullable', 'uuid'],
  53. 'notify' => ['sometimes', 'boolean'],
  54. ];
  55. }
  56. /**
  57. * @return array<string, string>
  58. */
  59. public function messages(): array
  60. {
  61. $messages = [
  62. 'content.regex' => __('messages.discussion_commentary_format'),
  63. 'severity.prohibited' => __('messages.discussion_severity_not_allowed'),
  64. ];
  65. if (in_array($this->input('type'), Discussion::INJECTED_TYPES, true)) {
  66. $messages['target_type.in'] = __('messages.discussion_annotation_target');
  67. }
  68. foreach (self::ANCHOR_FIELDS as $field) {
  69. $messages["{$field}.prohibited"] = __('messages.discussion_anchor_thread');
  70. }
  71. return $messages;
  72. }
  73. }