界面(用户 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。
79 lines
3.5 KiB
PHP
79 lines
3.5 KiB
PHP
<?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(); |