补齐后端最小功能和前端
This commit is contained in:
1 parent
a8c22b1b07
commit
a6179aba6a
56 files changed
+8126
-326
No files matched your search
+14
-10
@@ -15,7 +15,8 @@
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/health` | 公共,API 存活,不代表数据库或发布同步健康 |
|
||||
| GET | `/api/posts` | 公共,`{commit,posts:[metadata]}`,仅已发布,日期倒序 |
|
||||
| GET | `/api/posts/:slug` | 公共,`{meta,markdown,format,author,article_id,assets:[{name,url}]}`,草稿及不存在均 404 |
|
||||
| GET | `/api/posts/:slug` | 公共,`{meta,markdown,format,author,article_id,entry,pages:[{name,url}],assets:[{name,url}]}`,草稿及不存在均 404 |
|
||||
| GET | `/api/posts/:slug/pages/:name` | 公共,指定页面的正文、元信息、格式、页面列表和资源地址;非公开或不存在均 404 |
|
||||
| GET | `/api/assets/:slug/:name` | 公共,仅已发布文章的已验证图片 |
|
||||
| GET | `/api/stats` | 公共,文章数、标签数、正文源字节数、启动以来文章请求数 |
|
||||
| POST | `/api/auth/register` | 公共 + Origin;`{username,password}`,创建 reader;可配置关闭 |
|
||||
@@ -26,7 +27,7 @@
|
||||
| PUT | `/api/account/password` | 登录 + Origin + CSRF;`{old_password,new_password}`,成功后全部会话失效 |
|
||||
| GET | `/api/admin/users` | 管理员;返回用户启用状态、角色、前缀及公钥,不含哈希 |
|
||||
| PATCH | `/api/admin/users/:username` | 管理员 + Origin + CSRF;`{enabled,role}`;兼容可选的旧 `prefixes` 字段 |
|
||||
| GET | `/api/admin/status` | 管理员;`{commit,total_posts,drafts,indexed_commit}` |
|
||||
| GET | `/api/admin/status` | 管理员;`{commit,total_posts,drafts,indexed_commit,pending}` |
|
||||
|
||||
## 权限与账户
|
||||
|
||||
@@ -56,13 +57,16 @@
|
||||
|
||||
## 文章与资源
|
||||
|
||||
- 文章以作者仓库的 `index.txt` 为登记依据,每行三列:`文章ID posts/文件夹 正文文件名`。
|
||||
- 站内 `:slug` 为 `作者名~文章ID`,例如 `alice~first`;索引 ID 不随文件夹或文件名变化。
|
||||
- `markdown` 保留现有正文源字段(含 Frontmatter);`format` 为 `markdown` 或 `text`,纯文本不得当作 HTML 渲染。
|
||||
- 未登记内容及草稿不通过公共文章/资源接口返回;删除索引项会自动撤回阅读内容并同步删除派生数据库记录。
|
||||
文章由作者仓库的 `index.toml` 登记,结构、字段和多页样例见[文章发布](publishing.md)。站内 `:slug` 为 `作者名~文章ID`,目录或入口改名不会改变 ID。
|
||||
|
||||
## 同步状态
|
||||
`GET /api/posts/:slug` 返回入口正文。`markdown` 是正文源文本,服务端不再把 Frontmatter 解析为元信息,`entry` 是入口文件名,`pages` 和 `assets` 提供该文章可达页面及已导入图片的 URL。`format` 为 `markdown` 或 `text`,阅读端不得将纯文本直接当作 HTML。
|
||||
|
||||
- `/api/admin/status` 的 `commit` 和 `indexed_commit` 为整个作者集合的版本摘要,不是单个作者的 Git HEAD。
|
||||
- 作者各自的源提交记录在数据库 `blog_articles.git_commit` 和私有集合快照中。
|
||||
- 数据库失败时 Git 接收与阅读仍可继续,索引在恢复后自动重建;站长审核暂未启用。
|
||||
`meta` 包含 `title/description/tags/cover/slug/author/created_at/date/updated/status`。`cover` 是文件名或 null,对应 `assets` 中的资源。`author` 由服务端认证身份填写;`created_at` 是首次导入时间,`date` 是首次公开时间,未公开时为空字符串;`updated` 是元信息、可达页面或有效图片实际变化的时间。时间使用 UTC RFC 3339 字符串,不接受作者在索引或 Git 提交中指定。
|
||||
|
||||
页面链接如 `chapter.md#part` 由阅读端根据 `pages` 映射到页面 URL,并保留锚点。非公开内容、未登记目录、未链接页面与未引用图片不会由公共接口返回。移除索引条目或入口缺失时,页面和资源入口同时撤回。
|
||||
|
||||
## 同步与暂存状态
|
||||
|
||||
`/api/admin/status` 的 `commit` 和 `indexed_commit` 为整个生效作者集合的版本摘要,不是单个作者的 Git HEAD。`pending` 的格式为 `{作者名: {commit, reason}}`,仅管理员可读。TOML 无效时保留原生效版本及其数据库记录,暂存状态跨进程重启保存;修复后的正常推送会替换暂存状态并重新导入。
|
||||
|
||||
作者的源提交记录在数据库 `blog_articles.git_commit` 和私有集合快照中。数据库失败时 Git 接收与已导入文章阅读仍可继续,恢复后自动重建。站长审核暂未启用。
|
||||
+18
-2
@@ -11,6 +11,22 @@
|
||||
scripts/deploy.sh
|
||||
```
|
||||
|
||||
## 服务与路由
|
||||
|
||||
Compose 编排 Astro 前端 `web`、Rust API `api`、文章 SSH 接收 `ssh`、ChuSQL 数据库 `db` 和 Caddy。`init` 初始化持久卷,`db-init` 用于首次数据库授权。
|
||||
|
||||
`web` 是博客前端,不是 ChuSQL Web。数据库只构建 `chusql-server`、`csql-bootstrap` 与存储库,不构建或启动 `chusql-web`、`csql-web` 或数据库管理网站。
|
||||
|
||||
Caddy 将 `/api/*` 转发到 `api:3001`,其余请求转发到 `web:4321`。Node、API 和数据库只加入内部网络,不映射宿主端口;公网入口为 Caddy 的 HTTP/HTTPS 和文章 SSH。前端容器使用非 root 用户、只读根文件系统和独立资源上限。
|
||||
|
||||
## 腾讯云镜像
|
||||
|
||||
`.env.example` 默认配置 `REGISTRY_PREFIX=mirror.ccs.tencentyun.com/library/`,为 Debian、Node 与 Caddy 使用腾讯云内网 Docker 镜像。该地址仅供腾讯云内网访问,见[腾讯云官方说明](https://cloud.tencent.com/document/practice/1207/45596)。
|
||||
|
||||
在腾讯云内网部署时保留该值;其他环境将 `REGISTRY_PREFIX` 留空以使用原始镜像名,或改为可访问且受信的镜像前缀。项目不修改宿主机 Docker 配置、不关闭 TLS,也不重启 Docker 守护进程。
|
||||
|
||||
镜像注册表与 Debian 软件包源是不同配置。`REGISTRY_PREFIX` 控制基础镜像和 Caddy,`DEBIAN_MIRROR` 控制容器构建时的 apt 源。
|
||||
|
||||
## 首次部署
|
||||
|
||||
先按[开发与依赖](development.md)初始化子模块,再执行以下同架构构建步骤。使用 `scripts/deploy.sh` 时,也须先准备 `.env`。
|
||||
@@ -22,7 +38,7 @@ cp .env.example .env
|
||||
chmod 600 .env
|
||||
# 从标准输入输入管理员口令,Ctrl-D 结束;不要把明文写进命令行历史。
|
||||
./deploy/bin/chublog-api hash-password
|
||||
# 将输出以单引号包裹写入 .env 的 ADMIN_PASSWORD_HASH;同时配置域名及两份不同的随机 DB 口令。
|
||||
# 将输出以单引号包裹写入 .env 的 ADMIN_PASSWORD_HASH;同时配置域名、镜像前缀及两份不同的随机 DB 口令。
|
||||
docker compose --env-file .env -f deploy/compose.yaml up -d --build
|
||||
# db 启动就绪后,创建 chublog 库和非超级用户 chublog_index;失败时检查日志后重试。
|
||||
docker compose --env-file .env -f deploy/compose.yaml run --rm db-init
|
||||
@@ -42,7 +58,7 @@ docker compose --env-file .env -f deploy/compose.yaml run --rm db-init
|
||||
|
||||
## 管理员初始化
|
||||
|
||||
首次启动从环境中的哈希创建管理员,后续账号与权限以持久文件为准,修改初始哈希不会覆盖已有账户。
|
||||
浏览器打开配置的站点即可看到前端。`SITE_TITLE` 控制页眉名称。首次启动从环境中的哈希创建管理员,后续账号与权限以持久文件为准,修改初始哈希不会覆盖已有账户。
|
||||
|
||||
## 域名与网络
|
||||
|
||||
|
||||
+5
-1
@@ -55,16 +55,20 @@ cargo fmt --check
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
前端开发和构建见 [Astro 前端](frontend.md)。
|
||||
|
||||
## 联调
|
||||
|
||||
```sh
|
||||
python3 scripts/prepare-deploy.py --build-database
|
||||
python3 scripts/smoke.py
|
||||
python3 scripts/smoke.py --key-type rsa
|
||||
# 安装 Python Playwright 和 Chromium 后,可追加前端真实浏览器验证。
|
||||
python3 scripts/smoke.py --key-type rsa --browser-test
|
||||
```
|
||||
|
||||
- 联调使用独立随机命名的 Compose 项目、本机 18080/18443/12222 端口,测试结束清理自己创建的容器与卷。
|
||||
- 覆盖真实 SSH 身份与授权、作者间 Git 对象隔离、非法推送、索引删除、ChuSQL 元信息创建/删除、发布撤回、断库阅读、重启与撤权。
|
||||
- 覆盖真实 SSH 身份与授权、作者间 Git 对象隔离、非法推送、TOML 暂存与修复、多页链接、系统时间、索引删除、ChuSQL 元信息创建/删除、发布撤回、断库阅读、重启与撤权。
|
||||
- 该测试会拉取基础镜像,需要 Docker 可用。
|
||||
|
||||
## 历史清理后的协作
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Astro 前端
|
||||
|
||||
[返回项目首页](../README.md)
|
||||
|
||||
前端位于 `apps/web`,使用 Astro、TypeScript 和 Node 服务端渲染。首页按要求展示居中的“个人博客正在施工中”,下方居中列出已完成和未完成部分。
|
||||
|
||||
## 页面
|
||||
|
||||
| 地址 | 功能 |
|
||||
| --- | --- |
|
||||
| `/` | 施工说明、进度与导航,内容全部居中 |
|
||||
| `/articles` | 文章列表、标题/简介/作者/标签搜索、标签筛选和分页 |
|
||||
| `/posts/作者名~文章ID` | 阅读入口文档、图片和文章元信息 |
|
||||
| `/posts/作者名~文章ID/页面名.md` | 阅读同文章的其他可达页面,保留相对链接和锚点 |
|
||||
| `/login`、`/login?mode=register` | 登录、注册与错误反馈 |
|
||||
| `/account` | 公钥登记、密码修改、退出和 Git 发布说明;管理员可授权账户并查看待修复索引 |
|
||||
|
||||
未实现的评论、互动、内部文章阅读、审核和热度排序不提供占位操作按钮。文章写入继续通过 Git,不提供在线编辑。
|
||||
|
||||
## 本地开发
|
||||
|
||||
需要 Node.js 22.22 或更高版本,Docker 前端镜像使用 Node.js 24。先启动 API,并将其 `PUBLIC_ORIGIN` 设为浏览器使用的地址,例如 `http://127.0.0.1:4321`。
|
||||
|
||||
```sh
|
||||
cd apps/web
|
||||
npm ci
|
||||
cp .env.example .env
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打开 `http://127.0.0.1:4321`。`API_BASE_URL` 默认 `http://127.0.0.1:3001`,开发服务器将 `/api` 请求转发到该地址。`PUBLIC_ORIGIN` 用于显示正确的服务器 SSH 主机名,应与后端配置及浏览器地址一致。`SSH_PORT` 控制发布说明中的端口。
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
构建同时检查 Astro 和 TypeScript。生产运行 `npm start` 只启动页面服务,API 同源转发由 Caddy 提供,见[部署说明](deployment.md)。
|
||||
|
||||
## 安全与访问
|
||||
|
||||
浏览器使用后端 HttpOnly Cookie,CSRF 只保存在内存,不写入 localStorage。修改请求携带当前会话的 CSRF,Origin 由浏览器发送;后端继续执行全部权限检查。
|
||||
|
||||
Markdown 禁用原始 HTML,并在渲染后清理输出。文章内链接只映射到 API 返回的同文章页面和图片;普通外部链接仍受协议限制。纯文本通过转义输出显示。页面设置 CSP、禁用缓存和内容类型嗅探,不加载第三方字体、脚本或跟踪服务。
|
||||
|
||||
文章服务故障返回可重试提示,不填充示例数据。注册被关闭、会话失效和限流错误直接显示后端反馈。
|
||||
|
||||
## 浏览器检查
|
||||
|
||||
浏览器联调需要 Python Playwright 及 Chromium:
|
||||
|
||||
```sh
|
||||
python3 -m pip install playwright
|
||||
python3 -m playwright install chromium
|
||||
python3 scripts/smoke.py --key-type rsa --browser-test
|
||||
```
|
||||
|
||||
该命令使用隔离的 Docker 项目,验证真实注册登录、密码修改、公钥保存、站长授权、多页阅读及手机布局,测试后清理自己创建的容器与卷。截图默认放在 `/tmp/chublog-web-screenshots`。系统缺少浏览器库时,按 Playwright 安装说明准备运行环境。
|
||||
|
||||
## 项目技能
|
||||
|
||||
按维护者要求下载到本项目 `.agents/skills/`,作为开发参考,不是运行时依赖:
|
||||
|
||||
- [frontend-design](../.agents/skills/frontend-design/SKILL.md),来源为 [Anthropic skills](https://github.com/anthropics/skills/tree/main/skills/frontend-design)。
|
||||
- [webapp-testing](../.agents/skills/webapp-testing/SKILL.md),来源为 [Anthropic skills](https://github.com/anthropics/skills/tree/main/skills/webapp-testing)。
|
||||
- [web-design-guidelines](../.agents/skills/web-design-guidelines/SKILL.md),来源为 [Vercel agent skills](https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines)。
|
||||
|
||||
后续会话可继续发现这些技能。本轮已直接读取,按项目要求采用居中施工页、克制排版、键盘焦点、表单标签和真实浏览器检查。
|
||||
+9
-3
@@ -17,7 +17,7 @@
|
||||
## 数据库故障与索引重建
|
||||
|
||||
- 数据库索引失败不会修改作者 Git 或阻塞已导入文章的 HTTP 阅读;新集合会触发同步,失败至少间隔 30 秒重试,并定期检查索引是否丢失。
|
||||
- `blog_articles` 保存全部有效登记文章(含草稿)的作者、本地 ID、正文路径、格式、源提交和元信息;`blog_posts/blog_tags` 保留公开检索索引。
|
||||
- `blog_articles` 保存全部有效登记文章(含草稿)的作者、本地 ID、正文路径、格式、源提交、封面和系统时间;`blog_pages` 保存有效页面路径,`blog_article_tags` 保存全部文章标签;`blog_posts/blog_tags` 保留公开检索索引。
|
||||
- 恢复数据库后可自动重建;离线命令为 `chublog-api rebuild-index`(必须停止同一数据卷上的 API 进程后运行,防止并发),或等待在线自动重试。
|
||||
- `indexed_commit:null` 表示尚无已确认可用的索引,旧值表示尚未追平当前内容。
|
||||
- 重建上述派生表使用 ChuSQL 事务;数据库不可用时保留阅读快照,恢复后重建。
|
||||
@@ -28,7 +28,7 @@
|
||||
- 接收 Git 可额外镜像备份。
|
||||
- 恢复时先停止写入,从同一备份恢复各卷,再启动;不要只恢复 ChuSQL,也不要直接向正式仓库推送。
|
||||
- 应用启动加载私有集合快照,并继续从各作者 Git 校验导入。
|
||||
- 丢失派生快照时,可在停机并保留备份后移走损坏的 `collection.json`,由各作者接收仓库重新导入;不要丢失作者仓库或账户授权。
|
||||
- `collection.json` 还保存生效版本、暂存状态与服务端生成的时间,必须备份。丢失时可从有效作者仓库重新导入正文,但无法还原原始系统时间;若作者当前索引损坏,还需要备份中的上一个生效版本。不要将它当作可随意丢弃的缓存。
|
||||
- 进程重启会注销所有会话。
|
||||
|
||||
本实现对文件写入执行原子替换与 fsync,Git 引用采用受控更新;磁盘故障、主机断电和备份恢复仍需真实环境演练。不要对含正式数据的项目执行 `docker compose down -v`。
|
||||
@@ -40,9 +40,15 @@
|
||||
1. 从旧共享仓库导出各作者最新的文章文件(含草稿),按原发布前缀及管理员确认的所有权分给作者;不要复制 `.git` 或旧提交历史。
|
||||
2. 管理员离线将 `/auth/publishers.json` 的 `repository` 改为 `/srv/ingress/authors`(或自定义 `INGRESS_DIR`),保留用户和公钥授权。新版本 init 服务会创建该根目录。
|
||||
3. 将旧 API 数据卷根目录的 `content.git` 移入只对管理员可见的备份位置;旧 `published.json` 同样备份。旧 SSH 共享仓库不再供服务使用,应移出接收卷离线保管。不要删除备份或清空用户数据。
|
||||
4. 启动新服务。每位作者重新 clone 自己的空仓库,复制自己的导出文件,创建三列 `index.txt`,整理成一个首次提交并推送。旧 `slug` 如保留,须与索引 ID 一致。
|
||||
4. 启动新服务。每位作者重新 clone 自己的空仓库,复制自己的导出文件,将原 Frontmatter 中的标题、简介和标签迁入 `index.toml`,将原发布状态映射到 `public`;为每篇文章指定 ID、目录和入口,移除正文 Frontmatter,整理成一个首次提交并推送。作者和时间由服务端重新记录。
|
||||
5. 核对草稿隔离、所有文章、数据库索引和新的 `作者名~ID` URL 后再恢复对外服务。旧版本已被其他人克隆的内容无法收回。
|
||||
|
||||
- 已有授权配置仍指向旧仓库时,新版本拒绝启动;不会自动重新分发旧历史。
|
||||
- 原链接到新 ID 的重定向尚未实现,迁移时需另行安排。
|
||||
- 若自定义 `INGRESS_DIR`,需同步调整镜像内 `deploy/gitconfig` 的受信目录范围;默认仅信任 `/srv/ingress/authors/*`,不使用全局 `safe.directory=*`。
|
||||
|
||||
## 从个人仓库的文本索引迁移
|
||||
|
||||
停写并备份作者仓库和 `collection.json`,然后把 `index.txt` 改为根目录 `index.toml`。保留原文章 ID 和目录,从正文 Frontmatter 迁移允许的元信息,正文入口不叫 `main.md` 时显式填写 `entry`。删除旧 `index.txt` 和正文 Frontmatter 后,作为一个提交推送。沿用原私有集合快照可保留已有系统时间;旧正文中的日期不会被新版本采纳。
|
||||
|
||||
不要提交两种索引并存的仓库。仅根目录的 `index.toml` 在白名单内,其他 TOML 配置仍被拒绝。派生的 `blog_articles` 表若缺少新列,会由索引重建程序重建;该表不得混存评论、授权、审核或站长管理数据。
|
||||
+100
-67
@@ -16,92 +16,125 @@
|
||||
- 设回 `reader` 或 `enabled:false` 会撤回发布授权,并使现有网站会话失效。
|
||||
- 禁止管理员停用自己或取消自己的管理角色。
|
||||
|
||||
## 本地 Git 工作流
|
||||
## 克隆个人文章仓库
|
||||
|
||||
- 先通过可信渠道核对服务器 Ed25519 主机密钥指纹;不要禁用主机密钥检查。
|
||||
- 主机密钥保存在独立持久卷,重建容器不会更换。
|
||||
- 所有发布者使用同一个 OS 账户 `git`,身份由对应公钥的强制命令绑定,不由 Git author/email 决定。
|
||||
先通过可信渠道核对服务器主机密钥指纹,再在本机 `~/.ssh/config` 配置连接:
|
||||
|
||||
```sh
|
||||
# ~/.ssh/config
|
||||
```sshconfig
|
||||
Host chublog
|
||||
HostName blog.example.com
|
||||
Port 2222
|
||||
User git
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
```
|
||||
|
||||
# 无论作者是谁,别名都是 content.git;服务端按认证身份映射个人仓库。
|
||||
# 首次 clone 自动创建服务器裸仓库,作者无须自己创建裸仓库。
|
||||
将域名、端口和私钥路径替换为部署者提供的值。主机密钥检查应保持开启。服务器根据公钥识别作者,不使用 Git 的 author/email 判定身份。
|
||||
|
||||
```sh
|
||||
git clone chublog:content.git my-articles
|
||||
cd my-articles
|
||||
# 仓库已有内容时,每轮编辑前先 git pull --ff-only。
|
||||
# 创建 index.txt 和正文,格式见下文。
|
||||
git add index.txt posts/first
|
||||
```
|
||||
|
||||
每位作者都使用 `content.git` 别名。服务器将别名映射到该作者的独立仓库,并在首次访问时创建裸仓库,作者无需服务器 shell。仓库已有提交时,每轮编辑前先执行 `git pull --ff-only`。
|
||||
|
||||
## 创建 TOML 索引
|
||||
|
||||
在仓库根目录创建 UTF-8 文件 `index.toml`,每篇文章使用一个 `[[articles]]` 表:
|
||||
|
||||
```toml
|
||||
[[articles]]
|
||||
id = "first"
|
||||
folder = "posts/first"
|
||||
title = "第一篇文章"
|
||||
description = "文章简介"
|
||||
tags = ["Rust", "随笔"]
|
||||
public = true
|
||||
# cover = "cover.png"
|
||||
# entry = "main.md"
|
||||
```
|
||||
|
||||
`id` 和 `folder` 用于标识文章及其目录。文章元信息仅接受以下字段:
|
||||
|
||||
| 字段 | 含义与默认值 |
|
||||
| --- | --- |
|
||||
| `title` | 必填标题,最多 240 字节 |
|
||||
| `description` | 简介,省略时为空,最多 1000 字节 |
|
||||
| `tags` | 标签,默认空数组,最多 12 项,每项最多 80 字节 |
|
||||
| `cover` | 可选封面,填写同文章目录中的图片文件名 |
|
||||
| `public` | 是否公开,默认 `false` |
|
||||
| `entry` | 入口文档名,默认 `main.md` |
|
||||
|
||||
作者身份由 SSH 公钥绑定的账户确定。创建时间、首次公开时间和修改时间由服务端生成,以 UTC 时间保存。索引不接受 `author`、`date`、`updated`、精华或权限等额外字段。正文不再使用 Frontmatter。
|
||||
|
||||
文章 ID 在作者内唯一,允许 1 到 40 位小写字母、数字和中间短横线。目录为 `posts/<目录名>`,目录名最多 80 字节并遵循 slug 规则。ID 和目录名不能使用保留名称,例如 `admin` 或 `api`。同一目录不能登记多次。
|
||||
|
||||
文件名只允许 ASCII 字母、数字、短横线、下划线和点,最多 100 字节,不能以点开头或含 `..`。入口允许 `.md`、`.markdown` 和 `.txt`,扩展名须为小写。索引最多 64 KiB、1000 篇文章。要撤回全部文章,明确写入 `articles = []`;空文件缺少文章字段,会暂存并保留原文章。
|
||||
|
||||
## 编写多页文章
|
||||
|
||||
示例目录:
|
||||
|
||||
```text
|
||||
index.toml
|
||||
posts/
|
||||
└── first/
|
||||
├── main.md
|
||||
├── chapter.md
|
||||
└── cover.png
|
||||
```
|
||||
|
||||
`main.md` 内容:
|
||||
|
||||
```markdown
|
||||
# 第一篇文章
|
||||
|
||||
[阅读第二页](chapter.md#details)
|
||||
|
||||

|
||||
```
|
||||
|
||||
`chapter.md` 内容:
|
||||
|
||||
```markdown
|
||||
# 第二页
|
||||
|
||||
## Details
|
||||
|
||||
[返回入口](./main.md)
|
||||
```
|
||||
|
||||
服务端从入口沿文章内相对链接收集页面,允许循环链接,每篇最多 64 个可达页面。链接目标必须是同目录的文本文件,可以带 `#锚点`,不支持子目录、`../`、编码路径和查询参数。相对链接缺少目标文件时,整份索引暂存,原文章不更新。普通 HTTP、HTTPS、mailto 和站内绝对链接继续受安全检查。
|
||||
|
||||
未登记目录、从入口无法到达的文档,以及未被页面引用的图片不进入阅读快照或数据库。封面即使未在正文引用,也作为文章资源导入。所有上传文件仍受白名单、安全检查和仓库配额约束。`.txt` 页面通过 `format: text` 返回,阅读端须作为纯文本显示。
|
||||
|
||||
参考[索引样例](../content/index.toml)、[入口样例](../content/posts/welcome/main.md)和[第二页样例](../content/posts/welcome/chapter.md)。
|
||||
|
||||
## 提交并验证发布
|
||||
|
||||
```sh
|
||||
git add index.toml posts/first
|
||||
git commit -m 'Publish first article'
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## 文章目录与清单
|
||||
服务端会校验提交并自动导入。站内文章 ID 为 `作者名~文章ID`,例如 `/api/posts/alice~first`。文章 API 返回入口正文、页面列表和资源地址;页面通过 `/api/posts/alice~first/pages/chapter.md` 读取。前端需将文档相对链接映射到返回的页面 URL,并保留锚点。
|
||||
|
||||
仓库结构示例:
|
||||
|
||||
```text
|
||||
index.txt
|
||||
posts/
|
||||
└── first/
|
||||
├── article.md
|
||||
└── cover.png
|
||||
```
|
||||
|
||||
`index.txt` 为 UTF-8 三列文本,以空格或 Tab 分隔,无表头,允许空行:
|
||||
|
||||
```text
|
||||
first posts/first article.md
|
||||
notes posts/my-notes notes.markdown
|
||||
```
|
||||
|
||||
- 三列依次为**文章 ID、相对文件夹、正文文件名**。
|
||||
- ID 在作者内唯一,支持 1–40 位小写字母、数字及中间短横线,另须避开保留名称。
|
||||
- 目录必须是 `posts/<目录名>`,目录名遵循 slug 规则,最多 80 字节;文件名只允许 ASCII 字母、数字、短横线、下划线与点,不能以点开头或含 `..`。
|
||||
- 同一目录不能重复登记;正文可以任意命名,扩展名为 `.md`、`.markdown` 或 `.txt`。
|
||||
- 索引所指目录和正文必须存在,路径穿越和子目录正文被拒绝。
|
||||
- 索引上限 64 KiB、1000 项。
|
||||
|
||||
## 正文与图片
|
||||
|
||||
- 正文沿用 YAML Frontmatter(参考[正文样例](../content/posts/welcome/index.md)),包含 `title/date/updated/status/tags/description`;`slug` 可省略,如填写则必须等于索引 ID。
|
||||
- `.txt` 正文也需要 Frontmatter,API 通过 `format: text` 表明正文应作为纯文本展示;其他正文为 `format: markdown`。
|
||||
- 图片置于同一目录,通过 `` 引用。
|
||||
|
||||
## 新增、修改与撤回
|
||||
|
||||
| 作者操作 | 系统处理 |
|
||||
| 提交内容 | 结果 |
|
||||
| --- | --- |
|
||||
| 新建正文并在索引新增一行 | 校验后导入文章,在 ChuSQL 创建作者、ID、正文路径、状态等元信息 |
|
||||
| 修改正文或索引中的目录/文件名,保留 ID | 更新同一文章;站内 ID 不随目录移动而变化 |
|
||||
| 删除索引中的一行 | 移除当前阅读内容、图片入口及对应数据库元信息,无需先删除本地正文 |
|
||||
| 把索引清空 | 移除该作者所有当前文章,不影响其他作者 |
|
||||
| 有效索引、新增文章且入口存在 | 导入文章并创建数据库元信息与页面记录 |
|
||||
| 有效索引、修改正文或元信息 | 更新文章,保留首次公开时间;内容实际变化时更新修改时间 |
|
||||
| 有效索引、删除文章表 | 撤回文章、页面、资源入口及数据库派生记录 |
|
||||
| 有效索引、入口文件或目录缺失 | 将该文章视为不存在,撤回旧版本;其他有效文章仍可导入 |
|
||||
| TOML 缺失、语法错误、未知字段或元信息无效 | Git 提交暂存,不修改该作者原有文章,等待修复后的下一次推送 |
|
||||
| 脚本、非法文件类型、危险路径或内容超限 | 拒绝整个推送,远端分支不前进 |
|
||||
|
||||
- 未被索引选中的正文及目录视为无效内容,不导入阅读快照或数据库;图片只有被选中正文引用才作为公开资源导入;仍计算仓库容量并执行安全校验。
|
||||
- 删除正文但保留其索引项属于错误,整次推送会被拒绝。
|
||||
- `status: draft` 可保留登记但不公开;物理删除文件和撤回文章均不会擦除 Git 历史。
|
||||
`public = false` 的内容不会由公共文章、页面或资源接口返回。登录后查看内部文章的功能仍在待办中。删除和撤回不擦除个人 Git 历史,也不收回作者已克隆的副本。
|
||||
|
||||
## 提交与并发限制
|
||||
## 提交限制与状态查询
|
||||
|
||||
- MVP 每次只接受**一个直接继承个人远端 main 的提交**,首次仅允许一个根提交。
|
||||
- 禁止 merge、force push、删除 main、标签及其他分支;多次本地提交应先整理为一次提交,再变基到最新远端。
|
||||
- 同一作者在多台电脑操作仍需处理并发冲突,不同作者互不争用同一个仓库锁。
|
||||
每次推送只接受一个直接继承个人远端 `main` 的提交,首次必须是根提交。禁止 merge、force push、删除 `main`、标签和其他分支。多次本地提交须先整理为一次提交,再变基到最新远端。同一作者在多台电脑操作仍需处理冲突,不同作者使用不同仓库锁。
|
||||
|
||||
## 作者隔离与文章地址
|
||||
管理员通过 `GET /api/admin/status` 查看状态。`commit` 是当前生效作者提交映射的集合摘要,`indexed_commit` 是数据库已同步的集合版本。`pending` 按作者返回暂存提交和索引错误。暂存提交不会替换生效版本,API 重启后仍保留原文章和暂存状态。
|
||||
|
||||
- 站内文章 ID 为 `作者名~文章ID`,例如 Alice 的 `first` 对应 `/api/posts/alice~first`;不同作者可以使用相同的本地 ID。
|
||||
- 服务端分别保存每位作者的 Git 对象与历史,SSH 不能指定别人的仓库路径;作者无法通过 clone 或按提交哈希 fetch 读取其他作者草稿。
|
||||
- 作者自己已克隆的历史仍可在本地保留,不能靠撤回收回副本。
|
||||
|
||||
## 发布状态
|
||||
|
||||
- API 自动同步,一般约 2 秒,数据库重建、失败重试或较多作者可能延迟。
|
||||
- `GET /api/admin/status` 的 `commit` 是作者提交映射的集合摘要,不是任一作者 Git 提交;`indexed_commit` 表示数据库已同步的集合版本。
|
||||
- Git 接受表示接收校验完成,不保证数据库已同步。
|
||||
- 当前不做站长内容审核,审核计划见 [TODO](../TODO.md)。
|
||||
同步通常在下一轮后台处理时完成。数据库失败至少间隔 30 秒重试,作者较多或索引重建时可能延迟。Git 接受不代表文章已公开或数据库已同步。站长审核尚未启用,计划见 [TODO](../TODO.md)。
|
||||
+7
-2
@@ -4,14 +4,14 @@
|
||||
|
||||
## 文件白名单
|
||||
|
||||
- 仅允许根目录 `index.txt` 和 `posts/<目录>/<文件>` 内的 `.txt/.md/.markdown/.png/.jpg/.jpeg/.webp` 普通文件;扩展名使用小写。
|
||||
- 仅允许根目录 `index.toml`(唯一允许的 TOML 文件) 和 `posts/<目录>/<文件>` 内的 `.txt/.md/.markdown/.png/.jpg/.jpeg/.webp` 普通文件;扩展名使用小写。
|
||||
- 拒绝证书扩展名、隐藏文件、`.gitignore`、脚本、配置、符号链接、可执行位、子模块和其他类型。
|
||||
- 文本中的 PEM 证书/密钥标记也会拒绝。
|
||||
- Git 接收层能直接拦截,因此非白名单内容使整次推送失败,不静默改变作者提交。
|
||||
|
||||
## 容量配额
|
||||
|
||||
- 每个文本文件上限 256 KiB;图片单张 2 MiB、每目录最多 12 张、尺寸 4096×4096;每个目录合计 6 MiB。
|
||||
- 根索引上限 64 KiB,每个文本文件上限 256 KiB;图片单张 2 MiB、每目录最多 12 张、尺寸 4096×4096;每个目录合计 6 MiB。
|
||||
- 每位作者最多 1000 个目录、14001 个文件、原始文件合计 64 MiB;全站导入快照另限 64 MiB、1000 篇,超出时等待管理员处理。
|
||||
|
||||
## Git 接收限额
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
## 正文解析
|
||||
|
||||
- 同一文章的相对链接仅可引用同目录已验证的文本或图片,禁止跨目录读取;只有从入口可达的最多 64 个页面、页面图片和封面可被导入。
|
||||
- 使用成熟 Markdown 解析器,拒绝原始 HTML、危险链接协议、外链图片、控制符和双向隐藏字符;代码块是惰性文本,绝不执行 MDX、JS、仓库脚本、构建任务或上传内容。
|
||||
|
||||
## SSH 命令与密钥
|
||||
@@ -48,3 +49,7 @@
|
||||
|
||||
- 服务器只返回 JSON Markdown 与图片;后续前端必须把元数据当文本输出,并继续禁用原始 HTML、MDX和危险链接,不能将接口字段直接作为 HTML 执行。
|
||||
- 隔离不能替代 Git/OpenSSH/解码库的持续安全更新。
|
||||
|
||||
## 索引暂存与生效版本
|
||||
|
||||
TOML 的语法或元信息错误只更新作者接收仓库中的暂存提交,不替换现有文章或时间记录。文件类型、模式、配额和正文安全检查仍会执行,不能通过破坏 TOML 绕过检查。有效索引中的入口不存在时,该文章不再生效。非公开文章的所有页面和资源均返回 404,防止绕过入口直读。
|
||||
Reference in new issue
Block a user