2
0

DiscussionController.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <?php
  2. namespace App\Http\Controllers\V3;
  3. use App\Http\Controllers\Concerns\ResolvesCurrentUser;
  4. use App\Http\Controllers\Controller;
  5. use App\Http\Requests\V3\IndexDiscussionRequest;
  6. use App\Http\Requests\V3\StoreDiscussionRequest;
  7. use App\Http\Requests\V3\UpdateDiscussionRequest;
  8. use App\Http\Resources\V3\DiscussionResource;
  9. use App\Models\Discussion;
  10. use App\Services\V3\AgentGrantService;
  11. use App\Services\V3\DiscussionService;
  12. use Illuminate\Http\Request;
  13. /**
  14. * 评论线程:挂在句子、逐词、文章、术语、任务上的话题与回复;以及挂在句子上的批注
  15. * (note 边注、commentary 义注 / 复注对应——巴利注释书,不是普通评论)。
  16. *
  17. * 取代 `GET/POST /v2/discussion`(view=question / res_id / question-by-topic)、
  18. * `GET/PUT/DELETE /v2/discussion/{discussion}`。v2 那份在 dashboard-v4 下线前原样保留。
  19. * 回复见 {@see DiscussionReplyController}(取代 view=answer 与带 parent 的 POST)。
  20. *
  21. * 底层仍是 `discussions` 表。批注会注入阅读页,规则与评论不同,见各方法说明。
  22. *
  23. * 写操作(新建、修改、删除,以及回复)支持 AI 代写:模型用模型 token 登录,并带请求头
  24. * `X-Agent-Grant`(人类经 `POST /v3/agent-grants` 签发)。此时 editor 记授权人、
  25. * co_editor 记模型,权限按授权人判断,且只能写授权覆盖的 channel / book 里的句子上的内容。
  26. * 模型不带授权写入一律 403。
  27. */
  28. class DiscussionController extends Controller
  29. {
  30. use ResolvesCurrentUser;
  31. public function __construct(
  32. private readonly DiscussionService $discussions,
  33. private readonly AgentGrantService $grants,
  34. ) {}
  35. /**
  36. * 列出某个对象上的话题
  37. *
  38. * 只列话题(不含回复),删除了但还有回复的话题以墓碑出现(deleted_at 有值、内容为空)。
  39. * 未登录看不到不公开的话题(discussion);basic 角色在自己无权管理的对象上只看得到
  40. * 自己发起的不公开话题。各状态的数量与「能发起哪些类型」见 `GET /v3/discussions/summary`。
  41. * 不传 type 时只列评论线程(discussion / qa / help / issue / glossary),批注要显式 type=note / commentary。
  42. * 要一段经文(一章、一段段落区间)里的全部话题,用 `GET /v3/tipitaka-discussions/{channel}`。
  43. *
  44. * @unauthenticated
  45. *
  46. * @queryParam target_type string required 挂靠对象的类型。Enum: sentence,wbw,article,term,task
  47. * @queryParam target_id string required 挂靠对象的 uuid
  48. * @queryParam type string 类型,不传则只列评论线程。Enum: discussion,qa,help,issue,glossary,note,commentary
  49. * @queryParam status string 话题状态,不传则两种都要。Enum: active,close
  50. * @queryParam severity string 严重程度,逗号分隔多选。Example: error,warning
  51. * @queryParam sort string 排序,前缀 - 表示倒序。Enum: created_at,-created_at,updated_at,-updated_at Default: -created_at
  52. * @queryParam page integer 页码。Default: 1
  53. * @queryParam per_page integer 每页数量,最大 200。Default: 15
  54. */
  55. public function index(IndexDiscussionRequest $request)
  56. {
  57. return DiscussionResource::collection($this->discussions->listTopics(
  58. $request->validated(),
  59. $this->currentUserUid($request),
  60. $request->integer('per_page', 15),
  61. ));
  62. }
  63. /**
  64. * 发起话题或新建批注
  65. *
  66. * discussion / issue 登录即可发;qa / help 只能发在文章上,且只有文章的编辑者能发。
  67. * severity 的取值随 type:issue 为 error / warning / suggestion,help 为 urgent / normal,
  68. * 其它类型不收。默认会通知对象的作者(站内信 + webhook),`notify=false` 关闭。
  69. *
  70. * 批注(note / commentary)只能加在句子上,且只有该句所在 channel 的编辑者能加;
  71. * 标题可省、正文必填,commentary 的正文必须是义注句子模板 `{{book-para-start-end}}`
  72. * (可多句并列);可带锚点 pos_* / quote_*;不发通知。
  73. *
  74. * glossary(外挂术语:不改译文,给锚点处的译文挂上术语)是评论线程,title 必填、填术语词头;
  75. * 与批注一样只能挂在句子上、只有该句所在 channel 的编辑者能发,对所有人公开可见。
  76. * 锚点只有 note / commentary / issue / glossary 收,其它类型传了 422。
  77. *
  78. * @responseStatus 201 创建成功
  79. * @responseStatus 403 当前用户不能在这个对象上发起这种类型的话题
  80. *
  81. * @bodyParam target_type string required 挂靠对象的类型。Enum: sentence,wbw,article,term,task
  82. * @bodyParam target_id string required 挂靠对象的 uuid
  83. * @bodyParam type string 类型。Enum: discussion,qa,help,issue,glossary,note,commentary Default: discussion
  84. * @bodyParam title string 标题,最长 256 字符;评论线程必填(glossary 填术语词头),批注可省
  85. * @bodyParam content string 正文(markdown);批注必填
  86. * @bodyParam severity string 严重程度。Enum: error,warning,suggestion,urgent,normal
  87. * @bodyParam pos_start integer 锚点在句子文本中的起始偏移(只限 note / commentary / issue / glossary)
  88. * @bodyParam pos_end integer 锚点的结束偏移,批注插在这里(只限 note / commentary / issue / glossary)
  89. * @bodyParam quote_exact string 锚定的原文片段(只限 note / commentary / issue / glossary)
  90. * @bodyParam quote_prefix string 锚定片段前的上下文,用于重新定位(只限 note / commentary / issue / glossary)
  91. * @bodyParam quote_suffix string 锚定片段后的上下文,用于重新定位(只限 note / commentary / issue / glossary)
  92. * @bodyParam tpl_id string 话题模板(文章)的 uuid
  93. * @bodyParam notify boolean 是否发通知(批注不发)。Default: true
  94. */
  95. public function store(StoreDiscussionRequest $request)
  96. {
  97. return DiscussionResource::make($this->discussions->createTopic(
  98. $this->grants->actor(
  99. $this->currentUser($request)['user_uid'],
  100. $request->header(AgentGrantService::HEADER),
  101. ),
  102. $request->validated(),
  103. ));
  104. }
  105. /**
  106. * 获取单条话题或回复
  107. *
  108. * 删除了但还有回复的话题返回墓碑;删除了的回复、删除了且没有回复的话题返回 404。
  109. * 当前用户看不见的话题同样 404。
  110. *
  111. * @unauthenticated
  112. *
  113. * @urlParam discussion string required 话题或回复的 uuid
  114. */
  115. public function show(Request $request, Discussion $discussion)
  116. {
  117. return DiscussionResource::make(
  118. $this->discussions->show($discussion, $this->currentUserUid($request))
  119. );
  120. }
  121. /**
  122. * 修改话题或回复(局部更新)
  123. *
  124. * 只改传了的字段。title / status / type / severity 只对话题有效,回复传了 422。
  125. * 作者或对象的管理者(句子、逐词所在 channel 的编辑者,文章的编辑者)可改;
  126. * 改 type 时回复跟着改,新类型不收原来的 severity 时 severity 清空(锚点保留,改回可带锚点的类型时恢复);
  127. * 改成 glossary 须挂在句子上、且是该句的管理者。
  128. * 批注:没有 status,type 只能在 note / commentary 之间改(不能与评论线程互转),
  129. * 正文不能清空,可改锚点。
  130. * 改了内容(标题、正文、锚点)时 co_editor 跟着这次写入走:人亲自改则清空,AI 代改则换成该模型;
  131. * 只改 status / type / severity 不动 co_editor。
  132. *
  133. * @responseStatus 403 没有修改权限
  134. *
  135. * @urlParam discussion string required 话题或回复的 uuid
  136. *
  137. * @bodyParam title string 标题(只限话题)
  138. * @bodyParam content string 正文(markdown)
  139. * @bodyParam status string 状态(只限话题)。Enum: active,close
  140. * @bodyParam type string 类型(只限话题;评论线程与批注不能互转)。Enum: discussion,qa,help,issue,glossary,note,commentary
  141. * @bodyParam severity string 严重程度(只限话题),null 表示清空。Enum: error,warning,suggestion,urgent,normal
  142. * @bodyParam pos_start integer 锚点起始偏移(只限 note / commentary / issue / glossary)
  143. * @bodyParam pos_end integer 锚点结束偏移(只限 note / commentary / issue / glossary)
  144. * @bodyParam quote_exact string 锚定的原文片段(只限 note / commentary / issue / glossary)
  145. * @bodyParam quote_prefix string 锚定片段前的上下文(只限 note / commentary / issue / glossary)
  146. * @bodyParam quote_suffix string 锚定片段后的上下文(只限 note / commentary / issue / glossary)
  147. */
  148. public function update(UpdateDiscussionRequest $request, Discussion $discussion)
  149. {
  150. return DiscussionResource::make($this->discussions->update(
  151. $discussion,
  152. $this->grants->actor(
  153. $this->currentUser($request)['user_uid'],
  154. $request->header(AgentGrantService::HEADER),
  155. ),
  156. $request->validated(),
  157. ));
  158. }
  159. /**
  160. * 删除话题或回复(软删除)
  161. *
  162. * 作者本人或挂靠对象的管理者(句子 / 逐词所在 channel、文章的编辑者)能删;
  163. * v2 只允许作者。删话题不动回复:有回复的话题以墓碑形式留在列表里。
  164. * 返回删除后的资源(200),不是 204——软删的资源并没有消失。
  165. *
  166. * @responseStatus 403 既不是作者,也不是挂靠对象的管理者
  167. *
  168. * @urlParam discussion string required 话题或回复的 uuid
  169. */
  170. public function destroy(Request $request, Discussion $discussion)
  171. {
  172. return DiscussionResource::make($this->discussions->delete(
  173. $discussion,
  174. $this->grants->actor(
  175. $this->currentUser($request)['user_uid'],
  176. $request->header(AgentGrantService::HEADER),
  177. ),
  178. ));
  179. }
  180. }