资源附件+共享:补 /resources/<id>/{attach,shares} API 端点 #25
用户原话(2026-10-28):「鸿蒙端对齐网页端」。资源端上明说「附件与共享 还在网页端」,根因是资源 API 只有 title/content,附件与共享没接口。 补两组子端点(模型 Resource::ATTACH=resource、shares/setShare/dropShare 早就有): · GET/POST/DELETE /api/v1/resources/<id>/attach —— 附件列表 / 传 / 删 (删走 attachment_id query,与 tasks 同招;可写者才允许,别人家的 404) · GET/POST/DELETE /api/v1/resources/<id>/shares —— 共享列表 / 加 / 取消 (canWrite 才允许,越权 404 —— find 对看不到的资源返 null,不泄露存在性) 判据:_i25_resource_api.py 21/0(传/列/删附件 · 共享增/列/删 · 越权被拒且库里没变) · _i25_falsify.py 注掉 attach 列表与 shares 分支各自变红。
This commit is contained in:
+69
-2
@@ -109,7 +109,7 @@ final class ApiHandler
|
|||||||
case 'vehicles': $this->vehicles($id, $method); return;
|
case 'vehicles': $this->vehicles($id, $method); return;
|
||||||
case 'nodetypes': $this->nodeTypes($id, $method); return;
|
case 'nodetypes': $this->nodeTypes($id, $method); return;
|
||||||
case 'ledger': $this->ledger($id, $sub, $method); return;
|
case 'ledger': $this->ledger($id, $sub, $method); return;
|
||||||
case 'resources': $this->resources($id, $method); return;
|
case 'resources': $this->resources($id, $sub, $method); return;
|
||||||
case 'books': $this->books($id, $sub, $method); return;
|
case 'books': $this->books($id, $sub, $method); return;
|
||||||
case 'notes': $this->notes($id, $sub, $method); return;
|
case 'notes': $this->notes($id, $sub, $method); return;
|
||||||
// 🔴 v45:登录(手机号 + 密码 → API 令牌)。
|
// 🔴 v45:登录(手机号 + 密码 → API 令牌)。
|
||||||
@@ -2623,12 +2623,79 @@ final class ApiHandler
|
|||||||
资源 / 书 / 笔记
|
资源 / 书 / 笔记
|
||||||
═══════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
private function resources(int $id, string $method): void
|
private function resources(int $id, string $sub, string $method): void
|
||||||
{
|
{
|
||||||
[$fam, $pid] = Api::me();
|
[$fam, $pid] = Api::me();
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$r = Resource::find($fam, $id, $pid);
|
$r = Resource::find($fam, $id, $pid);
|
||||||
if (!$r) { Api::notFound('这条资源'); }
|
if (!$r) { Api::notFound('这条资源'); }
|
||||||
|
|
||||||
|
/* 🔴 工单 #25(用户 2026-10-28「鸿蒙端对齐网页端」):资源附件 + 共享。
|
||||||
|
端上原来明说「附件与共享还在网页端」,服务端这里补 API。
|
||||||
|
⚠️ 三段路由(`Api::dispatch` 只解析 /resources/<id>/<sub>),
|
||||||
|
删附件用 `attachment_id` 参数(与 tasks 同招),不是第 4 段。 */
|
||||||
|
if ($sub === 'attach') {
|
||||||
|
if ($method === 'GET') {
|
||||||
|
Api::ok(['items' => \App\Attachment::list(Resource::ATTACH, $id)]);
|
||||||
|
}
|
||||||
|
Api::requireWrite();
|
||||||
|
if (!Resource::canWrite($r, $pid)) {
|
||||||
|
Api::fail('这条资源不能改。', 'forbidden', 403);
|
||||||
|
}
|
||||||
|
if ($method === 'POST') {
|
||||||
|
$file = $_FILES['file'] ?? null;
|
||||||
|
if (!is_array($file)) {
|
||||||
|
Api::fail('没有收到文件。', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
$res = \App\Attachment::save($fam, Resource::ATTACH, $id, $file, $pid);
|
||||||
|
if (empty($res['ok'])) {
|
||||||
|
$m = (string)($res['error'] ?? '');
|
||||||
|
Api::fail($m !== '' ? $m : '文件没存下来', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
Api::ok(['id' => (int)($res['id'] ?? 0)], 201);
|
||||||
|
}
|
||||||
|
if ($method === 'DELETE') {
|
||||||
|
$aid = Api::int('attachment_id');
|
||||||
|
if ($aid <= 0) {
|
||||||
|
Api::fail('要指定删哪个附件。', 'bad_request', 400);
|
||||||
|
}
|
||||||
|
if (!\App\Attachment::removeFrom(Resource::ATTACH, $id, $aid)) {
|
||||||
|
Api::notFound('这个附件');
|
||||||
|
}
|
||||||
|
Api::ok(['deleted' => $aid]);
|
||||||
|
}
|
||||||
|
Api::fail('不支持的方法。', 'method_not_allowed', 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sub === 'shares') {
|
||||||
|
$isOwner = (int)($r['owner_id'] ?? 0) === $pid;
|
||||||
|
if ($method === 'GET') {
|
||||||
|
Api::ok(['items' => Resource::shares($id), 'is_owner' => $isOwner]);
|
||||||
|
}
|
||||||
|
// 只有 owner 或可写的人能改共享(Web 端 resources/share 的 canWrite 语义)
|
||||||
|
Api::requireWrite();
|
||||||
|
if (!Resource::canWrite($r, $pid)) {
|
||||||
|
Api::fail('这条资源不能共享。', 'forbidden', 403);
|
||||||
|
}
|
||||||
|
if ($method === 'POST') {
|
||||||
|
$who = Api::int('person_id');
|
||||||
|
if ($who <= 0) {
|
||||||
|
Api::fail('要共享给谁?缺 `person_id`。', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
Resource::setShare($id, (int)$r['owner_id'], $who, Api::str('perm', 'read'));
|
||||||
|
Api::ok(['items' => Resource::shares($id)]);
|
||||||
|
}
|
||||||
|
if ($method === 'DELETE') {
|
||||||
|
$who = Api::int('person_id');
|
||||||
|
if ($who <= 0) {
|
||||||
|
Api::fail('要取消谁的共享?缺 `person_id`。', 'unprocessable', 422);
|
||||||
|
}
|
||||||
|
Resource::dropShare($id, $who);
|
||||||
|
Api::ok(['items' => Resource::shares($id)]);
|
||||||
|
}
|
||||||
|
Api::fail('不支持的方法。', 'method_not_allowed', 405);
|
||||||
|
}
|
||||||
|
|
||||||
if ($method === 'GET') { Api::ok($r); }
|
if ($method === 'GET') { Api::ok($r); }
|
||||||
if ($method === 'PATCH' || $method === 'PUT') {
|
if ($method === 'PATCH' || $method === 'PUT') {
|
||||||
Api::requireWrite();
|
Api::requireWrite();
|
||||||
|
|||||||
Reference in New Issue
Block a user