|
@@ -2,22 +2,38 @@
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
+use App\Http\Resources\V3Resource;
|
|
|
use App\Models\PaliText;
|
|
use App\Models\PaliText;
|
|
|
use App\Services\PaliContentService;
|
|
use App\Services\PaliContentService;
|
|
|
-use Illuminate\Http\JsonResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Request;
|
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Str;
|
|
|
|
|
+use Illuminate\Validation\ValidationException;
|
|
|
|
|
|
|
|
class TipitakaReadChapterController extends Controller
|
|
class TipitakaReadChapterController extends Controller
|
|
|
{
|
|
{
|
|
|
/**
|
|
/**
|
|
|
- * 阅读模式章节内容。输入章节起始 book/para,按 pagesize 分批返回段落。
|
|
|
|
|
|
|
+ * 按章节读取译文
|
|
|
*
|
|
*
|
|
|
- * pagesize 两种写法:
|
|
|
|
|
- * - `20000b` 按字节,累加 pali_texts.lenght 直到超过上限(每页至少一段)
|
|
|
|
|
- * - `10p` 按段落数
|
|
|
|
|
|
|
+ * 给定 book + 段落号,定位它所属的章节,返回该章节的段落内容并分页。
|
|
|
|
|
+ * 章节不存在或没有内容返回 404。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 分页是手工做的:按字节时从头累加 `pali_texts.lenght` 逐页推进,所以 meta
|
|
|
|
|
+ * 里用 `page_size`(领域语法,非整数)而不是框架的 `per_page`,并附带
|
|
|
|
|
+ * `first_para` / `last_para` / `has_more`。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @unauthenticated
|
|
|
|
|
+ *
|
|
|
|
|
+ * @queryParam book integer required 典籍 id
|
|
|
|
|
+ * @queryParam para integer required 章节内任一段落号,用于定位章节
|
|
|
|
|
+ * @queryParam channel string required 译文 channel 的 uuid
|
|
|
|
|
+ * @queryParam format string 内容格式。Enum: html,markdown,react,text Default: html
|
|
|
|
|
+ * @queryParam view string 输出口径:display 整段合并,sentences 逐句,all 两者都给。
|
|
|
|
|
+ * Enum: display,sentences,all Default: display
|
|
|
|
|
+ * @queryParam pagesize string 每页大小,两种写法:`20000b` 按字节累加段落长度
|
|
|
|
|
+ * (每页至少一段),`10p` 按段落数。Default: 20000b
|
|
|
|
|
+ * @queryParam page integer 页码,从 1 开始。超出范围返回 422。Default: 1
|
|
|
*/
|
|
*/
|
|
|
- public function index(Request $request, PaliContentService $paliService): JsonResponse
|
|
|
|
|
|
|
+ public function index(Request $request, PaliContentService $paliService)
|
|
|
{
|
|
{
|
|
|
$data = $request->validate([
|
|
$data = $request->validate([
|
|
|
'book' => 'required|integer',
|
|
'book' => 'required|integer',
|
|
@@ -39,17 +55,32 @@ class TipitakaReadChapterController extends Controller
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 同 index,id 格式 {book}-{para}
|
|
|
|
|
|
|
+ * 按 id 读取章节
|
|
|
|
|
+ *
|
|
|
|
|
+ * 与 index 等价,只是把 book 与 para 合成一个路径参数。格式不对或 channel
|
|
|
|
|
+ * 不是 uuid 返回 422。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @unauthenticated
|
|
|
|
|
+ *
|
|
|
|
|
+ * @urlParam tipitaka_read_chapter string required 章节 id,格式 {book}-{para}。Example: 9002-1
|
|
|
|
|
+ *
|
|
|
|
|
+ * @queryParam channel string required 译文 channel 的 uuid
|
|
|
|
|
+ * @queryParam format string 内容格式。Enum: html,markdown,react,text Default: html
|
|
|
|
|
+ * @queryParam view string 输出口径:display 整段合并,sentences 逐句,all 两者都给。
|
|
|
|
|
+ * Enum: display,sentences,all Default: display
|
|
|
|
|
+ * @queryParam pagesize string 每页大小,两种写法:`20000b` 按字节累加段落长度
|
|
|
|
|
+ * (每页至少一段),`10p` 按段落数。Default: 20000b
|
|
|
|
|
+ * @queryParam page integer 页码,从 1 开始。超出范围返回 422。Default: 1
|
|
|
*/
|
|
*/
|
|
|
- public function show(Request $request, string $id, PaliContentService $paliService): JsonResponse
|
|
|
|
|
|
|
+ public function show(Request $request, string $id, PaliContentService $paliService)
|
|
|
{
|
|
{
|
|
|
$arrId = explode('-', $id);
|
|
$arrId = explode('-', $id);
|
|
|
if (count($arrId) !== 2 || ! is_numeric($arrId[0]) || ! is_numeric($arrId[1])) {
|
|
if (count($arrId) !== 2 || ! is_numeric($arrId[0]) || ! is_numeric($arrId[1])) {
|
|
|
- return $this->error('invalid id');
|
|
|
|
|
|
|
+ throw ValidationException::withMessages(['id' => __('site.invalid_parameter')]);
|
|
|
}
|
|
}
|
|
|
$channel = $request->input('channel');
|
|
$channel = $request->input('channel');
|
|
|
if (! Str::isUuid($channel)) {
|
|
if (! Str::isUuid($channel)) {
|
|
|
- return $this->error('invalid channel');
|
|
|
|
|
|
|
+ throw ValidationException::withMessages(['channel' => __('site.invalid_parameter')]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return $this->chapter(
|
|
return $this->chapter(
|
|
@@ -70,7 +101,7 @@ class TipitakaReadChapterController extends Controller
|
|
|
string $channel,
|
|
string $channel,
|
|
|
array $param,
|
|
array $param,
|
|
|
PaliContentService $paliService
|
|
PaliContentService $paliService
|
|
|
- ): JsonResponse {
|
|
|
|
|
|
|
+ ) {
|
|
|
$format = $param['format'] ?? 'html';
|
|
$format = $param['format'] ?? 'html';
|
|
|
$view = $param['view'] ?? 'display';
|
|
$view = $param['view'] ?? 'display';
|
|
|
$pageSize = $param['pagesize'] ?? '10p';
|
|
$pageSize = $param['pagesize'] ?? '10p';
|
|
@@ -78,7 +109,7 @@ class TipitakaReadChapterController extends Controller
|
|
|
|
|
|
|
|
$chapter = PaliText::where('book', $book)->where('paragraph', $para)->first();
|
|
$chapter = PaliText::where('book', $book)->where('paragraph', $para)->first();
|
|
|
if (! $chapter) {
|
|
if (! $chapter) {
|
|
|
- return $this->error('chapter not found');
|
|
|
|
|
|
|
+ abort(404, __('site.not_found'));
|
|
|
}
|
|
}
|
|
|
$to = $para + max(1, (int) $chapter->chapter_len) - 1;
|
|
$to = $para + max(1, (int) $chapter->chapter_len) - 1;
|
|
|
|
|
|
|
@@ -90,12 +121,12 @@ class TipitakaReadChapterController extends Controller
|
|
|
->all();
|
|
->all();
|
|
|
$total = count($paragraphs);
|
|
$total = count($paragraphs);
|
|
|
if ($total === 0) {
|
|
if ($total === 0) {
|
|
|
- return $this->error('chapter is empty');
|
|
|
|
|
|
|
+ abort(404, __('site.not_found'));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$slice = $this->slice($paragraphs, $pageSize, $page);
|
|
$slice = $this->slice($paragraphs, $pageSize, $page);
|
|
|
if ($slice === null) {
|
|
if ($slice === null) {
|
|
|
- return $this->error('page out of range');
|
|
|
|
|
|
|
+ throw ValidationException::withMessages(['page' => __('site.invalid_parameter')]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$items = [];
|
|
$items = [];
|
|
@@ -110,18 +141,17 @@ class TipitakaReadChapterController extends Controller
|
|
|
$first = $slice[0]->paragraph;
|
|
$first = $slice[0]->paragraph;
|
|
|
$last = $slice[count($slice) - 1]->paragraph;
|
|
$last = $slice[count($slice) - 1]->paragraph;
|
|
|
|
|
|
|
|
- return $this->ok([
|
|
|
|
|
- 'items' => $items,
|
|
|
|
|
- 'pagination' => [
|
|
|
|
|
- 'page' => $page,
|
|
|
|
|
- 'pageSize' => $pageSize,
|
|
|
|
|
- 'total' => $total,
|
|
|
|
|
- 'book' => $book,
|
|
|
|
|
- 'from' => (int) $first,
|
|
|
|
|
- 'to' => (int) $last,
|
|
|
|
|
- 'hasMore' => $last < $paragraphs[$total - 1]->paragraph,
|
|
|
|
|
- ],
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ return V3Resource::collection($items)->additional(['meta' => [
|
|
|
|
|
+ 'current_page' => $page,
|
|
|
|
|
+ 'total' => $total,
|
|
|
|
|
+ // pagesize 是领域特有语法("2p" = 2 个段落,纯数字 = 字节数),
|
|
|
|
|
+ // 不是 Laravel 的整数 per_page,所以单列一个键
|
|
|
|
|
+ 'page_size' => $pageSize,
|
|
|
|
|
+ 'book' => $book,
|
|
|
|
|
+ 'first_para' => (int) $first,
|
|
|
|
|
+ 'last_para' => (int) $last,
|
|
|
|
|
+ 'has_more' => $last < $paragraphs[$total - 1]->paragraph,
|
|
|
|
|
+ ]]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|