电影影评+评论:修半成品并补全 /movies/<id>/{reviews,review,comments} #27
用户原话(2026-10-28):「鸿蒙端对齐网页端」。观影详情只能看平均分, 不能写影评/评论。盘点发现服务端其实有份**半成品且带 bug**: · comments GET 写死"只有我写过影评才返回" —— 错,改为独立 reviews 端点; · comments POST 调 addComment 参数**完全错位**(parent_id 传给了 feelings、 target_person 传给了 parentId)—— 评论实际存不进; · 缺影评/评论的删除端点。 现在对齐 Web 端 reviews 四条路由: · GET /movies/<id>/reviews 全部影评(含评论树)+ 我的影评 + 统计 + 感受目录 · POST /movies/<id>/review 写/改我的影评 · DELETE /movies/<id>/review 删我的影评 · POST /movies/<id>/comments 评论/回复(review_id/content/parent_id) · DELETE /movies/<id>/comments?comment_id=N 删我的评论 判据:_i27_movie_review_api.py 22/0(写/改影评 · 列表含 mine · 评论/回复 · 删评论级联 · 删影评 · 越权删别人影评被拒),_i27_falsify.py 注掉写分支变红。
This commit is contained in:
+78
-15
@@ -2220,24 +2220,44 @@ final class ApiHandler
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sub === 'comments') {
|
/* ────────────────────────────────────────────────────────────────
|
||||||
|
🔴 工单 #27(用户 2026-10-28「鸿蒙端对齐网页端」):影评 + 评论。
|
||||||
|
|
||||||
|
这里原来是一份**半成品且带 bug**:
|
||||||
|
· `comments` GET 写死"只有我写过影评才返回"——错,应返回全部;
|
||||||
|
· `comments` POST 只能评论"我自己的影评",且 `addComment` 参数
|
||||||
|
**完全错位**(`parent_id` 传给了 `feelings`、`target_person`
|
||||||
|
传给了 `parentId`)——评论实际上存不进;
|
||||||
|
· 缺影评/评论的**删除**端点。
|
||||||
|
|
||||||
|
现在对齐 Web 端 `movies/review_save / review_del / comment_add /
|
||||||
|
comment_del` 四条路由,端点是:
|
||||||
|
GET /movies/<id>/reviews 全部影评(含评论)+ 我的影评
|
||||||
|
POST /movies/<id>/review 写/改我的影评(rating/content/feelings)
|
||||||
|
DELETE /movies/<id>/review 删我的影评
|
||||||
|
POST /movies/<id>/comments 评论/回复某条影评(review_id/content/parent_id)
|
||||||
|
DELETE /movies/<id>/comments?comment_id=N 删我的评论
|
||||||
|
|
||||||
|
⚠️ review_id / comment_id 走 body 或 query(`Api::param`)
|
||||||
|
—— 路由只解析三段,第 4 段会被丢(tasks 同款教训)。
|
||||||
|
⚠️ 评论归属校验:`addComment` 内部已判 review_id 归属,
|
||||||
|
拒跨影评评论(见 MovieReview::addComment)。
|
||||||
|
──────────────────────────────────────────────────────────────── */
|
||||||
|
if ($sub === 'reviews') {
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
$rv = MovieReview::mine($id, $pid);
|
Api::ok([
|
||||||
Api::ok($rv ? MovieReview::forEvent($id) : []);
|
'items' => MovieReview::forEvent($id),
|
||||||
}
|
'mine' => MovieReview::mine($id, $pid),
|
||||||
if ($method === 'POST') {
|
'stats' => MovieReview::stats($id),
|
||||||
Api::requireWrite();
|
'catalog' => MovieReview::CATALOG,
|
||||||
$rv = MovieReview::mine($id, $pid);
|
]);
|
||||||
if (!$rv) { Api::fail('先写一条评价,才能评论。', 'unprocessable', 422); }
|
|
||||||
$txt = Api::str('content', '', 4000);
|
|
||||||
if ($txt === '') { Api::fail('评论不能是空的。', 'unprocessable', 422); }
|
|
||||||
$cid = MovieReview::addComment((int)$rv['id'], $pid, $txt,
|
|
||||||
Api::int('parent_id'), Api::str('target_person', ''));
|
|
||||||
if ($cid <= 0) { Api::fail('评论没保存下来(可能内容太长)。', 'unprocessable', 422); }
|
|
||||||
Api::ok(['id' => $cid], 201);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($sub === 'review' && ($method === 'POST' || $method === 'PUT' || $method === 'PATCH')) {
|
if ($sub === 'review') {
|
||||||
|
if ($method === 'GET') {
|
||||||
|
Api::ok(MovieReview::mine($id, $pid));
|
||||||
|
}
|
||||||
|
if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH') {
|
||||||
Api::requireWrite();
|
Api::requireWrite();
|
||||||
$my = MovieReview::mine($id, $pid);
|
$my = MovieReview::mine($id, $pid);
|
||||||
$err = MovieReview::save(
|
$err = MovieReview::save(
|
||||||
@@ -2250,6 +2270,49 @@ final class ApiHandler
|
|||||||
if ($err !== '') { Api::fail($err, 'unprocessable', 422); }
|
if ($err !== '') { Api::fail($err, 'unprocessable', 422); }
|
||||||
Api::ok(MovieReview::mine($id, $pid));
|
Api::ok(MovieReview::mine($id, $pid));
|
||||||
}
|
}
|
||||||
|
if ($method === 'DELETE') {
|
||||||
|
Api::requireWrite();
|
||||||
|
$my = MovieReview::mine($id, $pid);
|
||||||
|
if ($my === null) {
|
||||||
|
Api::fail('你没有这条记录的评价。', 'not_found', 404);
|
||||||
|
}
|
||||||
|
if (!MovieReview::deleteMine((int)$my['id'], $pid)) {
|
||||||
|
Api::fail('只能删除自己的评价。', 'forbidden', 403);
|
||||||
|
}
|
||||||
|
Api::ok(['deleted' => (int)$my['id']]);
|
||||||
|
}
|
||||||
|
Api::fail('不支持的方法。', 'method_not_allowed', 405);
|
||||||
|
}
|
||||||
|
if ($sub === 'comments') {
|
||||||
|
if ($method === 'POST') {
|
||||||
|
Api::requireWrite();
|
||||||
|
$reviewId = Api::int('review_id');
|
||||||
|
if ($reviewId <= 0) {
|
||||||
|
Api::fail('要评论哪条评价?缺 `review_id`。', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
$txt = Api::str('content', '', 4000);
|
||||||
|
if ($txt === '') { Api::fail('评论不能是空的。', 'unprocessable', 422); }
|
||||||
|
$cid = MovieReview::addComment(
|
||||||
|
$reviewId, $pid, $txt,
|
||||||
|
array_values((array)Api::param('feelings', [])),
|
||||||
|
Api::int('parent_id')
|
||||||
|
);
|
||||||
|
if (is_string($cid)) { Api::fail($cid, 'unprocessable', 422); }
|
||||||
|
Api::ok(['id' => $cid], 201);
|
||||||
|
}
|
||||||
|
if ($method === 'DELETE') {
|
||||||
|
Api::requireWrite();
|
||||||
|
$cid = Api::int('comment_id');
|
||||||
|
if ($cid <= 0) {
|
||||||
|
Api::fail('要删哪条评论?缺 `comment_id`。', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
if (!MovieReview::deleteComment($cid, $pid)) {
|
||||||
|
Api::fail('只能删除自己的评论。', 'forbidden', 403);
|
||||||
|
}
|
||||||
|
Api::ok(['deleted' => $cid]);
|
||||||
|
}
|
||||||
|
Api::fail('不支持的方法。', 'method_not_allowed', 405);
|
||||||
|
}
|
||||||
|
|
||||||
if ($method === 'GET') { Api::ok($m); }
|
if ($method === 'GET') { Api::ok($m); }
|
||||||
if ($method === 'PATCH' || $method === 'PUT') {
|
if ($method === 'PATCH' || $method === 'PUT') {
|
||||||
|
|||||||
Reference in New Issue
Block a user