Files
FamilyBook/api.php
T
laoyang 075ac6bbeb v44 第 44 批:界面 5 条 + 移动端 API v1 全量
界面(用户 2026-10-12 第 1~5 条)
1. 新建日程「日历本」并入 .cs 自绘下拉,颜色画进候选项,三字与下拉同行。
2. 新建日程「开始/结束」改「日期范围:」,删旧日期控件;
   重复相关三行仅选重复时出现;删「(含第一次)」与「这段时间算忙碌」。
3. 条目浮层加「地点」、去掉「状态」行;首页红线按分钟定位(原只取整小时);
   去掉左侧红点只留红线。
4. 任务页删掉状态说明提示(STATUS_HINT 三值清空)。
5. 新建任务:标题在前树框在后;截止时间改最新日历控件;负责人默认自己;
   删冗余提示;「上面要做什么」→「任务名称」;清理括号说明。

移动端 API(用户第 6 条:导航模块除外,其余全量)
- 新增 Bearer 令牌鉴权(api_tokens 表,Schema v32),与登录口令 / DAV 口令三套分立。
- Api.php 内核(鉴权/取参/信封/分页/分发 + _method 覆写)+
  ApiHandler.php 17 条资源 + ApiToken.php + api.php 入口。
- 路由:router.php / .htaccess / index.php 三处 /api/ 兜底(部署脚本自动追加线上规则)。
- 个人设置新增「App 接口令牌」卡;docs/API.md 接口文档;
  docs/API-QUESTION-LIST.md 自主决策与待确认缺口清单。

验证:本地 e2e 1510/0、v43 真机 33/0、v43 反向 9/0、
v44 真机 41/0、v44 API 63/0、v44 反向 19/0;全流水线 PUSH_RC=0。
2026-10-12 15:31:03 +08:00

79 lines
3.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 🔴 v44(用户 2026-10-12 第 6 条)—— **移动端 API 入口**。
*
* 路径形态:`/api/v1/<资源>[/<id>][/<子动作>]`
* GET /api/v1/ping
* GET /api/v1/me
* GET /api/v1/tasks
* POST /api/v1/tasks
* PATCH /api/v1/tasks/12
* POST /api/v1/tasks/12/status
* DELETE /api/v1/events/9?scope=future&occ=2026-10-20
*
* ⚠️ 鉴权**不在这个文件里**(见 `App\Api::actor()`):
* 这里只负责"把请求交进去",鉴权与出参格式都在 Api 内核里统一做。
* (分散在入口做鉴权,是"某个入口忘了加"的经典温床。)
*
* ⚠️ 与 DAV 那套(dav.php / carddav.php)**完全并列**,互不影响:
* DAV 走 Basic + XML,是给第三方同步客户端的;
* API 走 Bearer + JSON,是给自家 App 的。
*
* ⚠️ `.htaccess` / `router.php` 里各有一条把 `/api/` 交给本文件的重写 ——
* 两条都要有:Apache 线上走 .htaccess,本地 `php -S` 走 router.php。
* (只写一条的话,会出现"本地能调、上线 404"或者反过来。)
*/
declare(strict_types=1);
require __DIR__ . '/app/bootstrap.php';
// App 端常常会发 OPTIONS 预检(跨域或自定义头)—— 直接给 204,别让它落到业务里。
if (strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'OPTIONS') {
header('Allow: GET, POST, PATCH, PUT, DELETE, OPTIONS');
// ⚠️ 这里**不**开 CORS 通配:API 给自家 App 用,浏览器跨域不是目标场景。
// 真需要时由用户在站点配置里显式加,而不是我们默认放开(放开会让
// "浏览器里的任意页面拿着用户令牌调我们的 API"变得可能)。
http_response_code(204);
exit;
}
if (!\App\Database::hasTables()) {
\App\Api::fail('还没安装。', 'not_installed', 503);
}
/* 🔴 v44:**API 入口自己也要跑一次结构升级** ——
网页那条路(index.php 开头)用"源码指纹"判要不要跑 `Schema::ensure()`;
而 App 可能**先于任何人打开网页**就发请求(新装的库、或手机先连上)。
那时 `api_tokens` 表还不存在 ⇒ 谁都没法签发令牌 ⇒ 死锁。
⚠️ 指纹判据是**每个家庭**一份(`Setting::get($familyId, …)`),
所以这里按"当前令牌属于谁"取家庭;没令牌时退回第 1 个家庭/全局。
⚠️ 与 index.php 那段同样**容错**:升级失败不该把 API 整个挡住
(失败会在下次请求重试,且错误进 error_log)。 */
$schemaFam = 0;
$tok = \App\Api::bearer();
if ($tok !== '') {
$hit = \App\ApiToken::verify($tok);
if ($hit !== null) {
$p = \App\Person::find((int)$hit[0]);
$schemaFam = (int)($p['family_id'] ?? 0);
}
}
try {
if ($schemaFam > 0 ? \App\Schema::needsUpgrade($schemaFam) : true) {
\App\Schema::ensure();
if ($schemaFam > 0) { \App\Schema::markUpgraded($schemaFam); }
}
} catch (\Throwable $e) {
error_log('[familybook] API 侧结构升级失败(已跳过):' . $e->getMessage());
}
/* ⚠️ 走路由 query 传进来的 `r`(`?r=api/v1/tasks`)也要认:
个别老网络库 / 代理会把 PATH_INFO 吃掉,只留 query。
两条入口共用一个 dispatch,不复制逻辑。 */
$viaQuery = trim((string)($_GET['r'] ?? ''));
if ($viaQuery !== '' && strpos($viaQuery, 'api/') === 0) {
// `api/v1/tasks` → `/api/v1/tasks`(与上面那条重写、以及 index.php 的兜底同一口径)
$_SERVER['REQUEST_URI'] = '/api/' . substr($viaQuery, 4);
}
\App\Api::dispatch();