StoreDiscussionRequest.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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 的正文必须是义注句子模板。glossary 也只能挂在句子上。
  14. * 锚点(pos_* / quote_*)只有 {@see Discussion::ANCHORED_TYPES} 收。
  15. */
  16. class StoreDiscussionRequest extends FormRequest
  17. {
  18. private const ANCHOR_FIELDS = ['pos_start', 'pos_end', 'quote_exact', 'quote_prefix', 'quote_suffix'];
  19. public function authorize(): bool
  20. {
  21. return true;
  22. }
  23. /**
  24. * @return array<string, array<int, mixed>>
  25. */
  26. public function rules(): array
  27. {
  28. $type = $this->input('type', 'discussion');
  29. $severities = Discussion::SEVERITIES[$type] ?? [];
  30. $isAnnotation = in_array($type, Discussion::INJECTED_TYPES, true);
  31. $sentenceOnly = in_array($type, Discussion::SENTENCE_ONLY_TYPES, true);
  32. // 不收锚点的类型传了 422,免得静默丢掉
  33. $anchor = fn (array $rules) => in_array($type, Discussion::ANCHORED_TYPES, true)
  34. ? ['sometimes', 'nullable', ...$rules]
  35. : ['prohibited'];
  36. return [
  37. 'target_type' => ['required', 'string', Rule::in($sentenceOnly ? ['sentence'] : Discussion::TARGET_TYPES)],
  38. 'target_id' => ['required', 'uuid'],
  39. 'type' => ['sometimes', 'string', Rule::in(Discussion::COMMENT_TYPES)],
  40. 'title' => $isAnnotation
  41. ? ['sometimes', 'nullable', 'string', 'max:256']
  42. : ['required', 'string', 'max:256'],
  43. 'content' => match ($type) {
  44. Discussion::TYPE_COMMENTARY => ['required', 'string', 'max:20000', 'regex:'.Discussion::COMMENTARY_PATTERN],
  45. Discussion::TYPE_NOTE => ['required', 'string', 'max:20000'],
  46. default => ['sometimes', 'nullable', 'string', 'max:20000'],
  47. },
  48. 'severity' => $severities === []
  49. ? ['prohibited']
  50. : ['sometimes', 'nullable', 'string', Rule::in($severities)],
  51. 'pos_start' => $anchor(['integer', 'min:0']),
  52. 'pos_end' => $anchor(['integer', 'min:0', 'gte:pos_start']),
  53. 'quote_exact' => $anchor(['string', 'max:1024']),
  54. 'quote_prefix' => $anchor(['string', 'max:1024']),
  55. 'quote_suffix' => $anchor(['string', 'max:1024']),
  56. 'tpl_id' => ['sometimes', 'nullable', 'uuid'],
  57. 'notify' => ['sometimes', 'boolean'],
  58. ];
  59. }
  60. /**
  61. * @return array<string, string>
  62. */
  63. public function messages(): array
  64. {
  65. $messages = [
  66. 'content.regex' => __('messages.discussion_commentary_format'),
  67. 'severity.prohibited' => __('messages.discussion_severity_not_allowed'),
  68. ];
  69. if (in_array($this->input('type'), Discussion::INJECTED_TYPES, true)) {
  70. $messages['target_type.in'] = __('messages.discussion_annotation_target');
  71. } elseif ($this->input('type') === Discussion::TYPE_GLOSSARY) {
  72. $messages['target_type.in'] = __('messages.discussion_glossary_target');
  73. }
  74. foreach (self::ANCHOR_FIELDS as $field) {
  75. $messages["{$field}.prohibited"] = __('messages.discussion_anchor_thread');
  76. }
  77. return $messages;
  78. }
  79. }