StoreDiscussionRequest.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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. * assessment(译文评估):score(0–100 的整数)与 severity(评估方法)必填,标题可省,理由写 content;
  17. * 其它类型传 score 就 422。
  18. */
  19. class StoreDiscussionRequest extends FormRequest
  20. {
  21. private const ANCHOR_FIELDS = ['pos_start', 'pos_end', 'quote_exact', 'quote_prefix', 'quote_suffix'];
  22. public function authorize(): bool
  23. {
  24. return true;
  25. }
  26. /**
  27. * @return array<string, array<int, mixed>>
  28. */
  29. public function rules(): array
  30. {
  31. $type = $this->input('type', 'discussion');
  32. $severities = Discussion::SEVERITIES[$type] ?? [];
  33. $isAssessment = $type === Discussion::TYPE_ASSESSMENT;
  34. $isAnnotation = in_array($type, Discussion::INJECTED_TYPES, true);
  35. $sentenceOnly = in_array($type, Discussion::SENTENCE_ONLY_TYPES, true);
  36. // 不收锚点的类型传了 422,免得静默丢掉
  37. $anchor = fn (array $rules) => in_array($type, Discussion::ANCHORED_TYPES, true)
  38. ? ['sometimes', 'nullable', ...$rules]
  39. : ['prohibited'];
  40. return [
  41. 'target_type' => ['required', 'string', Rule::in($sentenceOnly ? ['sentence'] : Discussion::TARGET_TYPES)],
  42. 'target_id' => ['required', 'uuid'],
  43. 'type' => ['sometimes', 'string', Rule::in(Discussion::COMMENT_TYPES)],
  44. 'title' => $isAnnotation || $isAssessment
  45. ? ['sometimes', 'nullable', 'string', 'max:256']
  46. : ['required', 'string', 'max:256'],
  47. 'content' => match ($type) {
  48. Discussion::TYPE_COMMENTARY => ['required', 'string', 'max:20000', 'regex:'.Discussion::COMMENTARY_PATTERN],
  49. Discussion::TYPE_NOTE => ['required', 'string', 'max:20000'],
  50. default => ['sometimes', 'nullable', 'string', 'max:20000'],
  51. },
  52. 'severity' => match (true) {
  53. $isAssessment => ['required', 'string', 'regex:'.Discussion::ASSESSMENT_METHOD_PATTERN],
  54. $severities === [] => ['prohibited'],
  55. default => ['sometimes', 'nullable', 'string', Rule::in($severities)],
  56. },
  57. 'score' => $isAssessment
  58. ? ['required', 'integer', 'between:0,100']
  59. : ['prohibited'],
  60. 'pos_start' => $anchor(['integer', 'min:0']),
  61. 'pos_end' => $anchor(['integer', 'min:0', 'gte:pos_start']),
  62. 'quote_exact' => $anchor(['string', 'max:1024']),
  63. 'quote_prefix' => $anchor(['string', 'max:1024']),
  64. 'quote_suffix' => $anchor(['string', 'max:1024']),
  65. 'tpl_id' => ['sometimes', 'nullable', 'uuid'],
  66. 'notify' => ['sometimes', 'boolean'],
  67. ];
  68. }
  69. /**
  70. * @return array<string, string>
  71. */
  72. public function messages(): array
  73. {
  74. $messages = [
  75. 'content.regex' => __('messages.discussion_commentary_format'),
  76. 'severity.prohibited' => __('messages.discussion_severity_not_allowed'),
  77. 'severity.regex' => __('messages.discussion_assessment_method'),
  78. 'score.prohibited' => __('messages.discussion_score_not_allowed'),
  79. ];
  80. if (in_array($this->input('type'), Discussion::INJECTED_TYPES, true)) {
  81. $messages['target_type.in'] = __('messages.discussion_annotation_target');
  82. } elseif ($this->input('type') === Discussion::TYPE_GLOSSARY) {
  83. $messages['target_type.in'] = __('messages.discussion_glossary_target');
  84. } elseif ($this->input('type') === Discussion::TYPE_ASSESSMENT) {
  85. $messages['target_type.in'] = __('messages.discussion_assessment_target');
  86. }
  87. foreach (self::ANCHOR_FIELDS as $field) {
  88. $messages["{$field}.prohibited"] = __('messages.discussion_anchor_thread');
  89. }
  90. return $messages;
  91. }
  92. }