2
0

ChannelController.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Api\ChannelApi;
  4. use App\Http\Api\PaliTextApi;
  5. use App\Http\Api\ShareApi;
  6. use App\Http\Api\StudioApi;
  7. use App\Http\Resources\ChannelResource;
  8. use App\Models\Channel;
  9. use App\Models\CustomBook;
  10. use App\Models\DhammaTerm;
  11. use App\Models\PaliSentence;
  12. use App\Models\Sentence;
  13. use App\Models\WbwBlock;
  14. use App\Services\AuthService;
  15. use Illuminate\Http\Request;
  16. use Illuminate\Http\Response;
  17. use Illuminate\Support\Arr;
  18. use Illuminate\Support\Facades\DB;
  19. class ChannelController extends Controller
  20. {
  21. /**
  22. * 列出 channel(译文集)
  23. *
  24. * 按 view 指定的口径返回 channel 列表,支持关键字搜索、分页、排序。
  25. * 需要登录的口径:studio、studio-all、user-edit、user-in-chapter。
  26. *
  27. * @queryParam view string required 查询口径。
  28. * Enum: public,studio,studio-all,user-edit,user-in-chapter,system,paragraphs,id
  29. * @queryParam name string studio 名称,view=studio / studio-all 时必填
  30. * @queryParam view2 string view=studio 时的二级口径:my=我的,其余值=协作的。Default: my
  31. * @queryParam collaborator string view=studio 且 view2 为协作时,按协作者 uid 过滤。Default: all
  32. * @queryParam id string view=id 时的 channel uid 列表,逗号分隔
  33. * @queryParam book integer view=user-in-chapter 时的典籍 id;传入后会联查该章节的翻译进度
  34. * @queryParam para string view=user-in-chapter 时的段落号
  35. * @queryParam book_id integer view=paragraphs 时的典籍 id
  36. * @queryParam paragraph string 联查 progress_chapters 时的段落号,与 book 配合使用
  37. * @queryParam progress string 传入则在每行附带 final 逐句完成情况,依赖 book 与 para
  38. * @queryParam type integer 按 channel 类型过滤
  39. * @queryParam updated_at string 只返回该时间之后更新的记录,用于离线包增量同步。
  40. * Example: 2023-09-18T05:39:51.000000Z
  41. * @queryParam created_at string 只返回该时间之后新建的记录
  42. */
  43. public function index(Request $request)
  44. {
  45. //
  46. $result = false;
  47. // $chapter 只有 view=user-in-chapter 会赋值,下面按进度取数时要先判断
  48. $chapter = null;
  49. $indexCol = [
  50. 'channels.uid',
  51. 'name',
  52. 'channels.summary',
  53. 'type',
  54. 'owner_uid',
  55. 'channels.lang',
  56. 'status',
  57. 'is_system',
  58. 'channels.updated_at',
  59. 'channels.created_at',
  60. ];
  61. if ($request->has('book')) {
  62. $indexCol[] = 'progress_chapters.progress';
  63. }
  64. // FIXME: switch 没有 default 分支,view 缺失或不在枚举内时 $table 始终未定义,
  65. // 下面的 $table->count() 会直接 500。应补 default 返回参数错误。
  66. switch ($request->input('view')) {
  67. case 'public':
  68. $table = Channel::select($indexCol)
  69. ->where('status', 30);
  70. break;
  71. case 'studio':
  72. // 获取studio内所有channel
  73. $user = AuthService::current($request);
  74. if (! $user) {
  75. return $this->error(__('auth.failed'));
  76. }
  77. // 判断当前用户是否有指定的studio的权限
  78. $studioId = StudioApi::getIdByName($request->input('name'));
  79. if (! StudioApi::userCanList($user['user_uid'], $studioId)) {
  80. return $this->error(__('auth.failed'), 403, 403);
  81. }
  82. $table = Channel::select($indexCol);
  83. if ($request->input('view2', 'my') === 'my') {
  84. $table = $table->where('owner_uid', $studioId);
  85. } else {
  86. // 协作
  87. $resList = ShareApi::getResList($studioId, 2);
  88. $resId = [];
  89. foreach ($resList as $res) {
  90. $resId[] = $res['res_id'];
  91. }
  92. $table = $table->whereIn('channels.uid', $resId);
  93. if ($request->input('collaborator', 'all') !== 'all') {
  94. $table = $table->where('owner_uid', $request->input('collaborator'));
  95. } else {
  96. $table = $table->where('owner_uid', '<>', $studioId);
  97. }
  98. }
  99. break;
  100. case 'studio-all':
  101. /**
  102. * studio 的和协作的
  103. */
  104. // 获取user所有有权限的channel列表
  105. $user = AuthService::current($request);
  106. if (! $user) {
  107. return $this->error(__('auth.failed'));
  108. }
  109. // 判断当前用户是否有指定的studio的权限
  110. if ($user['user_uid'] !== StudioApi::getIdByName($request->input('name'))) {
  111. return $this->error(__('auth.failed'));
  112. }
  113. $channelById = [];
  114. $channelId = [];
  115. // 获取共享channel
  116. $allSharedChannels = ShareApi::getResList($user['user_uid'], 2);
  117. foreach ($allSharedChannels as $key => $value) {
  118. // code...
  119. $channelId[] = $value['res_id'];
  120. $channelById[$value['res_id']] = $value;
  121. }
  122. $table = Channel::select($indexCol)
  123. ->whereIn('uid', $channelId)
  124. ->orWhere('owner_uid', $user['user_uid']);
  125. break;
  126. case 'user-edit':
  127. /**
  128. * 某用户有编辑权限的
  129. */
  130. // 获取user所有有权限的channel列表
  131. $user = AuthService::current($request);
  132. if (! $user) {
  133. return $this->error(__('auth.failed'));
  134. }
  135. $channelById = [];
  136. $channelId = [];
  137. // 获取共享channel
  138. $allSharedChannels = ShareApi::getResList($user['user_uid'], 2);
  139. foreach ($allSharedChannels as $key => $value) {
  140. // code...
  141. if ($value['power'] >= 20) {
  142. $channelId[] = $value['res_id'];
  143. $channelById[$value['res_id']] = $value;
  144. }
  145. }
  146. $table = Channel::select($indexCol)
  147. ->whereIn('uid', $channelId)
  148. ->orWhere('owner_uid', $user['user_uid']);
  149. break;
  150. case 'user-in-chapter':
  151. // 获取user 在某章节 所有有权限的channel列表
  152. $user = AuthService::current($request);
  153. if (! $user) {
  154. return $this->error(__('auth.failed'));
  155. }
  156. $channelById = [];
  157. $channelId = [];
  158. // 获取共享channel
  159. $allSharedChannels = ShareApi::getResList($user['user_uid'], 2);
  160. foreach ($allSharedChannels as $key => $value) {
  161. // code...
  162. $channelId[] = $value['res_id'];
  163. $channelById[$value['res_id']] = $value;
  164. }
  165. // 获取全网公开channel
  166. $chapter = PaliTextApi::getChapterStartEnd($request->input('book'), $request->input('para'));
  167. $publicChannelsWithContent = Sentence::where('book_id', $request->input('book'))
  168. ->whereBetween('paragraph', $chapter)
  169. ->where('strlen', '>', 0)
  170. ->where('status', 30)
  171. ->groupBy('channel_uid')
  172. ->select('channel_uid')
  173. ->get();
  174. foreach ($publicChannelsWithContent as $key => $value) {
  175. // code...
  176. $value['res_id'] = $value->channel_uid;
  177. $value['power'] = 10;
  178. $value['type'] = 2;
  179. if (! isset($channelById[$value['res_id']])) {
  180. $channelId[] = $value['res_id'];
  181. $channelById[$value['res_id']] = $value;
  182. }
  183. }
  184. $table = Channel::select($indexCol)
  185. ->whereIn('uid', $channelId)
  186. ->orWhere('owner_uid', $user['user_uid']);
  187. break;
  188. case 'system':
  189. $table = Channel::select($indexCol)
  190. ->where('owner_uid', config('mint.admin.root_uuid'));
  191. break;
  192. case 'paragraphs':
  193. $channels = Sentence::where('book_id', $request->input('book_id'))
  194. ->whereIn('paragraph', explode(',', $request->input('para')))
  195. ->groupBy('channel_uid')->select('channel_uid')->get();
  196. if (count($channels) > 0) {
  197. $channelIds = array_map(fn ($item) => $item['channel_uid'], $channels->toArray());
  198. $table = Channel::select($indexCol)
  199. ->whereIn('uid', $channelIds);
  200. } else {
  201. // FIXME: whereIsNull 不是 Eloquent 方法,会被动态 where 解析成 where('is_null', 'uid'),
  202. // 查不存在的列直接报 SQL 错误。本意应是返回空结果集,改用 ->whereRaw('1 = 0') 或 whereNull('uid')。
  203. $table = Channel::select($indexCol)->whereIsNull('uid');
  204. }
  205. break;
  206. case 'id':
  207. $table = Channel::select($indexCol)
  208. ->whereIn('uid', explode(',', $request->input('id')));
  209. }
  210. if ($request->has('book')) {
  211. if ($request->input('view') === 'public') {
  212. $table = $table->leftJoin('progress_chapters', 'channels.uid', '=', 'progress_chapters.channel_id')
  213. ->where('progress_chapters.book', $request->input('book'))
  214. ->where('progress_chapters.para', $request->input('paragraph'));
  215. } else {
  216. $table = $table->leftJoin('progress_chapters', function ($join) use ($request) {
  217. $join->on('channels.uid', '=', 'progress_chapters.channel_id')
  218. ->where('progress_chapters.book', $request->input('book'))
  219. ->where('progress_chapters.para', $request->input('paragraph')); // 条件写在这里!
  220. });
  221. }
  222. }
  223. // 处理搜索
  224. if (! empty($request->input('search'))) {
  225. $table = $table->where('name', 'like', '%'.$request->input('search').'%');
  226. }
  227. if ($request->has('type')) {
  228. $table = $table->where('type', $request->input('type'));
  229. }
  230. if ($request->has('updated_at')) {
  231. $table = $table->where('updated_at', '>', $request->input('updated_at'));
  232. }
  233. if ($request->has('created_at')) {
  234. $table = $table->where('created_at', '>', $request->input('created_at'));
  235. }
  236. // 获取记录总条数
  237. $count = $table->count();
  238. // 处理排序
  239. $table = $table->orderBy(
  240. $request->input('order', 'created_at'),
  241. $request->input('dir', 'desc')
  242. );
  243. // 处理分页
  244. $table = $table->skip($request->input('offset', 0))
  245. ->take($request->input('limit', 200));
  246. // 获取数据
  247. $result = $table->get();
  248. // TODO 将下面代码转移到resource
  249. if ($result) {
  250. if ($request->has('progress') && $chapter) {
  251. // 获取进度
  252. // 获取单句长度
  253. $sentLen = PaliSentence::where('book', $request->input('book'))
  254. ->whereBetween('paragraph', $chapter)
  255. ->orderBy('word_begin')
  256. ->select(['book', 'paragraph', 'word_begin', 'word_end', 'length'])
  257. ->get();
  258. }
  259. foreach ($result as $key => $value) {
  260. if ($request->has('progress') && $chapter) {
  261. // 获取进度
  262. $finalTable = Sentence::where('book_id', $request->input('book'))
  263. ->whereBetween('paragraph', $chapter)
  264. ->where('channel_uid', $value->uid)
  265. ->where('strlen', '>', 0)
  266. ->select(['strlen', 'book_id', 'paragraph', 'word_start', 'word_end']);
  267. if ($finalTable->count() > 0) {
  268. $finished = $finalTable->get();
  269. $final = [];
  270. foreach ($sentLen as $sent) {
  271. // code...
  272. $first = Arr::first($finished, function ($value, $key) use ($sent) {
  273. return $value->book_id == $sent->book &&
  274. $value->paragraph == $sent->paragraph &&
  275. $value->word_start == $sent->word_begin &&
  276. $value->word_end == $sent->word_end;
  277. });
  278. $final[] = [$sent->length, $first ? true : false];
  279. }
  280. $value['final'] = $final;
  281. }
  282. }
  283. // 角色
  284. if (isset($user['user_uid'])) {
  285. if ($value->owner_uid === $user['user_uid']) {
  286. $value['role'] = 'owner';
  287. } else {
  288. if (isset($channelById) && isset($channelById[$value->uid])) {
  289. switch ($channelById[$value->uid]['power']) {
  290. case 10:
  291. // code...
  292. $value['role'] = 'member';
  293. break;
  294. case 20:
  295. $value['role'] = 'editor';
  296. break;
  297. case 30:
  298. $value['role'] = 'owner';
  299. break;
  300. default:
  301. // code...
  302. $value['role'] = $channelById[$value->uid]['power'];
  303. break;
  304. }
  305. }
  306. }
  307. }
  308. // 获取studio信息
  309. $value->studio = StudioApi::getById($value->owner_uid);
  310. }
  311. return $this->ok(['rows' => $result, 'count' => $count]);
  312. } else {
  313. return $this->ok(['rows' => [], 'count' => 0]);
  314. }
  315. }
  316. /**
  317. * 获取我的,和协作channel数量
  318. *
  319. * @return Response
  320. */
  321. public function showMyNumber(Request $request)
  322. {
  323. $user = AuthService::current($request);
  324. if (! $user) {
  325. return $this->error(__('auth.failed'));
  326. }
  327. // 判断当前用户是否有指定的studio的权限
  328. $studioId = StudioApi::getIdByName($request->input('studio'));
  329. if ($user['user_uid'] !== $studioId) {
  330. return $this->error(__('auth.failed'));
  331. }
  332. // 我的
  333. $my = Channel::where('owner_uid', $studioId)->count();
  334. // 协作
  335. $resList = ShareApi::getResList($studioId, 2);
  336. $resId = [];
  337. foreach ($resList as $res) {
  338. $resId[] = $res['res_id'];
  339. }
  340. $collaboration = Channel::whereIn('uid', $resId)->where('owner_uid', '<>', $studioId)->count();
  341. return $this->ok(['my' => $my, 'collaboration' => $collaboration]);
  342. }
  343. /**
  344. * 获取章节的进度
  345. *
  346. * @return Response
  347. */
  348. public function progress(Request $request)
  349. {
  350. $indexCol = ['uid', 'name', 'summary', 'type', 'owner_uid', 'lang', 'status', 'updated_at', 'created_at'];
  351. $sent = $request->input('sentence');
  352. $query = [];
  353. $queryWithChannel = [];
  354. $sentContainer = [];
  355. $sentLenContainer = [];
  356. $paliChannel = ChannelApi::getSysChannel('_System_Pali_VRI_');
  357. $customBookChannel = [];
  358. foreach ($sent as $value) {
  359. $ids = explode('-', $value);
  360. $idWithChannel = $ids;
  361. if (count($ids) === 4) {
  362. if ($ids[0] < 1000) {
  363. $idWithChannel[] = $paliChannel;
  364. } else {
  365. if (! isset($customBookChannel[$ids[0]])) {
  366. $cbChannel = CustomBook::where('book_id', $ids[0])->value('channel_id');
  367. if ($cbChannel) {
  368. $customBookChannel[$ids[0]] = $cbChannel;
  369. } else {
  370. $customBookChannel[$ids[0]] = $paliChannel;
  371. }
  372. }
  373. $idWithChannel[] = $customBookChannel[$ids[0]];
  374. }
  375. $sentContainer[$value] = false;
  376. $query[] = $ids;
  377. $queryWithChannel[] = $idWithChannel;
  378. }
  379. }
  380. // 获取单句长度
  381. if (count($query) > 0) {
  382. $table = Sentence::whereIns([
  383. 'book_id',
  384. 'paragraph',
  385. 'word_start',
  386. 'word_end',
  387. 'channel_uid',
  388. ], $queryWithChannel)
  389. ->select(['book_id', 'paragraph', 'word_start', 'word_end', 'strlen']);
  390. $sentLen = $table->get();
  391. foreach ($sentLen as $value) {
  392. $strlen = $value->strlen;
  393. if (empty($strlen)) {
  394. $strlen = 0;
  395. }
  396. $sentId = "{$value->book_id}-{$value->paragraph}-{$value->word_start}-{$value->word_end}";
  397. $sentLenContainer[$sentId] = $strlen;
  398. }
  399. }
  400. $channelById = [];
  401. $channelId = [];
  402. // 获取全网公开的有译文的channel
  403. if ($request->input('owner') === 'all' || $request->input('owner') === 'public') {
  404. if (count($query) > 0) {
  405. $fields = ['book_id', 'paragraph', 'word_start', 'word_end'];
  406. $publicChannelsWithContent = Sentence::whereIns($fields, $query)
  407. ->where('strlen', '>', 0)
  408. ->where('status', 30)
  409. ->groupBy('channel_uid')
  410. ->select('channel_uid')
  411. ->get();
  412. foreach ($publicChannelsWithContent as $key => $value) {
  413. // code...
  414. $value['res_id'] = $value->channel_uid;
  415. $value['power'] = 10;
  416. $value['type'] = 2;
  417. if (! isset($channelById[$value['res_id']])) {
  418. $channelId[] = $value['res_id'];
  419. $channelById[$value['res_id']] = $value;
  420. }
  421. }
  422. }
  423. }
  424. // 获取 user 在某章节 所有有权限的 channel 列表
  425. $user = AuthService::current($request);
  426. if ($user !== false) {
  427. // 我自己的
  428. if ($request->input('owner') === 'all' || $request->input('owner') === 'my') {
  429. $my = Channel::select($indexCol)->where('owner_uid', $user['user_uid'])->get();
  430. foreach ($my as $key => $value) {
  431. $channelId[] = $value->uid;
  432. $channelById[$value->uid] = [
  433. 'res_id' => $value->uid,
  434. 'power' => 30,
  435. 'type' => 2,
  436. ];
  437. }
  438. }
  439. // 获取共享channel
  440. if ($request->input('owner') === 'all' || $request->input('owner') === 'collaborator') {
  441. $allSharedChannels = ShareApi::getResList($user['user_uid'], 2);
  442. foreach ($allSharedChannels as $key => $value) {
  443. // code...
  444. if (! in_array($value['res_id'], $channelId)) {
  445. $channelId[] = $value['res_id'];
  446. $channelById[$value['res_id']] = $value;
  447. }
  448. }
  449. }
  450. }
  451. // 所有有这些句子译文的channel
  452. if (count($query) > 0) {
  453. $allChannels = Sentence::whereIns(['book_id', 'paragraph', 'word_start', 'word_end'], $query)
  454. ->where('strlen', '>', 0)
  455. ->groupBy('channel_uid')
  456. ->select('channel_uid')
  457. ->get();
  458. }
  459. // 所有需要查询的channel
  460. $table = Channel::select(['uid', 'name', 'summary', 'type', 'owner_uid', 'lang', 'status', 'updated_at', 'created_at'])
  461. ->whereIn('uid', $channelId);
  462. if ($user !== false) {
  463. $table->orWhere('owner_uid', $user['user_uid']);
  464. }
  465. $result = $table->get();
  466. foreach ($result as $key => $value) {
  467. // 角色
  468. if ($user !== false && $value->owner_uid === $user['user_uid']) {
  469. $value['role'] = 'owner';
  470. } else {
  471. if (isset($channelById[$value->uid])) {
  472. switch ($channelById[$value->uid]['power']) {
  473. case 10:
  474. // code...
  475. $value['role'] = 'member';
  476. break;
  477. case 20:
  478. $value['role'] = 'editor';
  479. break;
  480. case 30:
  481. $value['role'] = 'owner';
  482. break;
  483. default:
  484. // code...
  485. $value['role'] = $channelById[$value->uid]['power'];
  486. break;
  487. }
  488. }
  489. }
  490. // 获取studio信息
  491. $result[$key]['studio'] = StudioApi::getById($value->owner_uid);
  492. // 获取进度
  493. if (count($query) > 0) {
  494. $currChannelId = $value->uid;
  495. $hasContent = Arr::first($allChannels, function ($value, $key) use ($currChannelId) {
  496. return $value->channel_uid === $currChannelId;
  497. });
  498. if ($hasContent && count($query) > 0) {
  499. $finalTable = Sentence::whereIns(['book_id', 'paragraph', 'word_start', 'word_end'], $query)
  500. ->where('channel_uid', $currChannelId)
  501. ->where('strlen', '>', 0)
  502. ->select(['strlen', 'book_id', 'paragraph', 'word_start', 'word_end', 'created_at', 'updated_at']);
  503. $created_at = time();
  504. $edit_at = 0;
  505. if ($finalTable->count() > 0) {
  506. $finished = $finalTable->get();
  507. $currChannel = [];
  508. foreach ($finished as $rowFinish) {
  509. $createTime = strtotime($rowFinish->created_at);
  510. $updateTime = strtotime($rowFinish->updated_at);
  511. if ($createTime < $created_at) {
  512. $created_at = $createTime;
  513. }
  514. if ($updateTime > $edit_at) {
  515. $edit_at = $updateTime;
  516. }
  517. $currChannel["{$rowFinish->book_id}-{$rowFinish->paragraph}-{$rowFinish->word_start}-{$rowFinish->word_end}"] = 1;
  518. }
  519. $final = [];
  520. foreach ($sentContainer as $sentId => $rowSent) {
  521. // code...
  522. if (isset($currChannel[$sentId])) {
  523. $final[] = [$sentLenContainer[$sentId], true];
  524. } else {
  525. $final[] = [$sentLenContainer[$sentId], false];
  526. }
  527. }
  528. $result[$key]['final'] = $final;
  529. $result[$key]['content_created_at'] = date('Y-m-d H:i:s', $created_at);
  530. $result[$key]['content_updated_at'] = date('Y-m-d H:i:s', $edit_at);
  531. }
  532. }
  533. }
  534. }
  535. // FIXME: 第二个元素漏写了键名,实际返回的是 {rows: [...], 0: n} 而不是 {rows, count},
  536. // 与本控制器其他列表接口的返回结构不一致。应为 'count' => count($result)。
  537. return $this->ok(['rows' => $result, count($result)]);
  538. }
  539. /**
  540. * 新建 channel
  541. *
  542. * 在指定 studio 下新建译文集。同一 studio 内不允许重名。
  543. * 调用者必须对该 studio 有管理权限。
  544. *
  545. * @bodyParam studio string required 所属 studio 名称
  546. * @bodyParam name string required channel 名称,在同一 studio 内唯一
  547. * @bodyParam type integer required channel 类型
  548. * @bodyParam lang string required 译文语言代码。Example: zh-Hans
  549. */
  550. public function store(Request $request)
  551. {
  552. //
  553. $user = AuthService::current($request);
  554. if (! $user) {
  555. // FIXME: error() 的签名是 error(string $message, mixed $data, int $status),
  556. // 这里把 401 当成了 data 传进去,响应体的 data 会变成数字 401。本文件多处同样写法。
  557. return $this->error(__('auth.failed'), 401, 401);
  558. }
  559. // 判断当前用户是否有指定的studio的权限
  560. $studioId = StudioApi::getIdByName($request->input('studio'));
  561. if (! StudioApi::userCanManage($user['user_uid'], $studioId)) {
  562. return $this->error(__('auth.failed'), 403, 403);
  563. }
  564. $studio = StudioApi::getById($studioId);
  565. // 查询是否重复
  566. if (Channel::where('name', $request->input('name'))
  567. ->where('owner_uid', $studioId)
  568. ->exists()
  569. ) {
  570. return $this->error(__('validation.exists', ['name']), 200, 200);
  571. }
  572. $channel = new Channel;
  573. $channel->id = app('snowflake')->id();
  574. $channel->name = $request->input('name');
  575. $channel->owner_uid = $studioId;
  576. $channel->type = $request->input('type');
  577. $channel->lang = $request->input('lang');
  578. $channel->editor_id = $user['user_id'];
  579. if (isset($studio['roles'])) {
  580. if (in_array('basic', $studio['roles'])) {
  581. $channel->status = 5;
  582. }
  583. }
  584. $channel->create_time = time() * 1000;
  585. $channel->modify_time = time() * 1000;
  586. $channel->save();
  587. return $this->ok($channel);
  588. }
  589. /**
  590. * 获取单个 channel
  591. *
  592. * 返回 channel 详情,附带所属 studio 与作者信息。
  593. *
  594. * @urlParam channel string required channel uid
  595. */
  596. public function show($id)
  597. {
  598. //
  599. $channel = Channel::find($id);
  600. if (! $channel) {
  601. return $this->error('no res');
  602. }
  603. $studio = StudioApi::getById($channel->owner_uid);
  604. $channel->studio = $studio;
  605. $channel->owner_info = ['nickname' => $studio['nickName'], 'username' => $studio['realName']];
  606. return $this->ok($channel);
  607. }
  608. /**
  609. * 按名称获取 channel
  610. *
  611. * @urlParam name string required channel 名称
  612. */
  613. public function showByName(string $name)
  614. {
  615. //
  616. $indexCol = ['uid', 'name', 'summary', 'type', 'owner_uid', 'lang', 'is_system', 'status', 'updated_at', 'created_at'];
  617. $channel = Channel::where('name', $name)->select($indexCol)->first();
  618. if ($channel) {
  619. return $this->ok(new ChannelResource($channel));
  620. } else {
  621. return $this->error('no channel');
  622. }
  623. }
  624. /**
  625. * 整体更新 channel
  626. *
  627. * 覆盖式更新,未传的字段会被写成 null;只改部分字段请用 PATCH /channel。
  628. * 系统 channel 不可修改;非 owner 需要 30 以上的协作权限。
  629. *
  630. * @urlParam channel string required channel uid
  631. *
  632. * @bodyParam name string required channel 名称
  633. * @bodyParam type integer required channel 类型
  634. * @bodyParam summary string required 简介
  635. * @bodyParam lang string required 译文语言代码
  636. * @bodyParam status integer required 状态。30 为全网公开
  637. * @bodyParam source_type string 来源类型,传入才会更新
  638. * @bodyParam source_id string 来源 id,传入才会更新
  639. */
  640. public function update(Request $request, Channel $channel)
  641. {
  642. // 鉴权
  643. $user = AuthService::current($request);
  644. if (! $user) {
  645. return $this->error(__('auth.failed'), 401, 401);
  646. }
  647. if ($channel->is_system) {
  648. return $this->error('system channel', 403, 403);
  649. }
  650. if ($channel->owner_uid !== $user['user_uid']) {
  651. // 判断是否为协作
  652. $power = ShareApi::getResPower($user['user_uid'], $request->input('id'));
  653. if ($power < 30) {
  654. return $this->error(__('auth.failed'), 403, 403);
  655. }
  656. }
  657. $channel->name = $request->input('name');
  658. $channel->type = $request->input('type');
  659. $channel->summary = $request->input('summary');
  660. $channel->lang = $request->input('lang');
  661. $channel->status = $request->input('status');
  662. if ($request->has('source_type')) {
  663. $channel->source_type = $request->input('source_type');
  664. }
  665. if ($request->has('source_id')) {
  666. $channel->source_id = $request->input('source_id');
  667. }
  668. $channel->save();
  669. return $this->ok($channel);
  670. }
  671. /**
  672. * 局部更新 channel
  673. *
  674. * 只更新请求体中出现的字段。系统 channel 不可修改;非 owner 需要 30 以上的协作权限。
  675. *
  676. * @bodyParam name string channel 名称
  677. * @bodyParam type integer channel 类型
  678. * @bodyParam summary string 简介
  679. * @bodyParam lang string 译文语言代码
  680. * @bodyParam status integer 状态。30 为全网公开
  681. * @bodyParam config string channel 配置
  682. */
  683. public function patch(Request $request, Channel $channel)
  684. {
  685. // 鉴权
  686. $user = AuthService::current($request);
  687. if (! $user) {
  688. return $this->error(__('auth.failed'), [], 401);
  689. }
  690. if ($channel->is_system) {
  691. return $this->error('system channel', 403, 403);
  692. }
  693. if ($channel->owner_uid !== $user['user_uid']) {
  694. // 判断是否为协作
  695. $power = ShareApi::getResPower($user['user_uid'], $request->input('id'));
  696. if ($power < 30) {
  697. return $this->error(__('auth.failed'), [], 403);
  698. }
  699. }
  700. if ($request->has('name')) {
  701. $channel->name = $request->input('name');
  702. }
  703. if ($request->has('type')) {
  704. $channel->type = $request->input('type');
  705. }
  706. if ($request->has('summary')) {
  707. $channel->summary = $request->input('summary');
  708. }
  709. if ($request->has('lang')) {
  710. $channel->lang = $request->input('lang');
  711. }
  712. if ($request->has('status')) {
  713. $channel->status = $request->input('status');
  714. }
  715. if ($request->has('config')) {
  716. $channel->config = $request->input('config');
  717. }
  718. $channel->save();
  719. return $this->ok($channel);
  720. }
  721. /**
  722. * 删除 channel
  723. *
  724. * 只有 owner 可以删除。channel 下已有译文、术语或逐词解析数据时拒绝删除。
  725. *
  726. * @urlParam channel string required channel uid
  727. */
  728. public function destroy(Request $request, Channel $channel)
  729. {
  730. //
  731. $user = AuthService::current($request);
  732. if (! $user) {
  733. return $this->error(__('auth.failed'));
  734. }
  735. // 判断当前用户是否有指定的studio的权限
  736. if ($user['user_uid'] !== $channel->owner_uid) {
  737. return $this->error(__('auth.failed'));
  738. }
  739. // 查询其他资源
  740. if (Sentence::where('channel_uid', $channel->uid)->exists()) {
  741. return $this->error('译文有数据无法删除');
  742. }
  743. if (DhammaTerm::where('channal', $channel->uid)->exists()) {
  744. return $this->error('术语有数据无法删除');
  745. }
  746. if (WbwBlock::where('channel_uid', $channel->uid)->exists()) {
  747. return $this->error('逐词解析有数据无法删除');
  748. }
  749. // FIXME: $delete 按值捕获,闭包内的赋值传不出来,接口永远返回 0。
  750. // 应改为 use (&$delete) 或让闭包 return 删除结果。
  751. $delete = 0;
  752. DB::transaction(function () use ($channel, $delete) {
  753. // TODO 删除相关资源
  754. $delete = $channel->delete();
  755. });
  756. return $this->ok($delete);
  757. }
  758. }