docs: README + LICENSE + CONTRIBUTING + CHANGELOG + API reference + issue/PR templates + editorconfig
ci / test (push) Failing after 17m5s

This commit is contained in:
root
2026-10-16 21:19:08 +08:00
parent 9732942749
commit 0f92a3437d
11 changed files with 394 additions and 1 deletions
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+26
View File
@@ -0,0 +1,26 @@
---
name: Bug 报告
about: 报告一个可复现的问题
title: '[Bug] 简述'
labels: bug
---
**环境**
- 部署方式:Docker / 源码
- 端:浏览器 PWA / Windows 桌面 / 移动端
- 版本/提交:
**复现步骤**
1.
2.
**期望行为**
**实际行为**
**日志**(`docker logs footprints-sync --tail 50`,注意脱敏 token/密码)
```
```
+14
View File
@@ -0,0 +1,14 @@
---
name: 功能建议
about: 提议一个新功能或改进
title: '[Feature] 简述'
labels: enhancement
---
**使用场景**(想解决什么问题)
**期望形态**(记录/检索/统计会长什么样)
**替代方案**
**补充**
+15
View File
@@ -0,0 +1,15 @@
## 变更说明
<!-- 做了什么、为什么 -->
## 关联 Issue
- #
## 自查清单
- [ ] `node scripts/check.mjs` 通过
- [ ] `node scripts/test-integration.mjs` 通过(涉及行为变更时补了断言)
- [ ] 服务端保持零 npm 依赖
- [ ] schema 变更兼容旧库平滑迁移
- [ ] UI 改动适配亮/暗主题
+30
View File
@@ -0,0 +1,30 @@
# 更新日志
本项目的所有显著变更都记录在此文件中。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
版本遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [0.1.0] - 2026-10-16
首个公开版本。
### Added
- 云优先多用户架构:所有数据存服务器,按用户独立 SQLite 档案库;浏览器 / PWA / Windows 桌面三端同源
- 留痕本体:自定义记录类型 + 动态表单、维度、指标、全文检索(FTS5 trigram 中文)、时间线、统计总览
- 学业成绩:学期 GPA/排名、课程成绩、绩点梯度自定义与趋势图
- 附件:SHA-256 内容寻址去重存储
- 管理后台:邀请码全生命周期管理(自定义码、备注、启用/停用、删除、批量生成)、用户列表、注册配额
- 演示账号 demo/demo1234:启动自动播种 16 条足迹 + 4 学期成绩(幂等、失败自愈)
- PWA(sw v3:接口永不缓存)与 Tauri 桌面安装包
- 质量设施:pre-push 钩子、静态检查套件、35 条集成断言、部署冒烟脚本、Gitea Actions CI
### Fixed
- 登录持久化与刷新丢会话
- Service Worker 缓存 /auth/me 导致切换账号后仍显示旧用户
- 静态兜底邀请码(INVITE_CODE)注册路径不可达
- 容器内命令层挂载方式不可靠导致的「未知命令」
- 桌面构建可能嵌入过期前端(beforeBuildCommand 缺失)
### Security
- scrypt 口令散列 + timingSafeEqual 校验
- Bearer Token(365 天过期)、按用户数据隔离、邀请码注册门控
+37
View File
@@ -0,0 +1,37 @@
# 贡献指南
欢迎 Issue 与 PR!开始之前:
## 环境要求
- Node.js ≥ 22.5(推荐 24,`node:sqlite` 依赖)
- 前端构建:npm 或 pnpm
- 桌面端构建(可选):Rust stable + MSVC Build Tools(Windows)
## 开发流程
1. Fork / 建分支:`git checkout -b feat/your-feature`
2. 提交信息用约定式前缀:`feat: / fix: / docs: / chore: / ci: / refactor:`
3. 本地跑绿再推:
```bash
git config core.hooksPath .githooks # 每个克隆只需一次
node scripts/check.mjs
node scripts/test-integration.mjs
```
启用钩子后,push 会自动跑同一套检查;工作区有未提交变更或测试失败时 push 会被拒绝。
## 约定
- 服务端保持**零 npm 依赖**(只用 Node 内置模块);确需引入依赖请先开 Issue 讨论
- 改动 `app/server/db.mjs`(命令层)时注意:Web / 桌面 / 播种三处共用它,测试必须全绿
- 涉及 schema 变更:保证老库能平滑迁移(参考 `preMigrateColumns` 的加列模式),并在
`scripts/test-integration.mjs` 补断言
- 涉及 Service Worker:接口类请求(/api /auth /admin /sync)永远不得缓存
- UI 改动请同时适配亮/暗两套主题(令牌见 `app/src/styles/theme.css`)
## Issue
- Bug 报告请附:复现步骤、期望/实际行为、服务端日志(`docker logs footprints-sync`)
- 功能建议请说明使用场景,最好附上你期望的记录/检索形态
+3
View File
@@ -1,5 +1,8 @@
# 个人留痕系统(Footprints)设计方案 # 个人留痕系统(Footprints)设计方案
> ⚠️ 历史底稿:本文写于单人桌面阶段(v0.5),核心数据模型思想仍然有效;
> 现产品已演进为**云优先多用户架构**(见 README)。设计原则与本体模型可继续作为参考。
>
> 版本 v0.5 · 足迹本体是核心;分类、表单、统计、导出、评分全部用户可自定义 > 版本 v0.5 · 足迹本体是核心;分类、表单、统计、导出、评分全部用户可自定义
> 形态:单人使用 · Windows 桌面应用(MVP 已实现)· 预留多端同步 > 形态:单人使用 · Windows 桌面应用(MVP 已实现)· 预留多端同步
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 linyux (footpr.linyux.cn)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+154
View File
@@ -0,0 +1,154 @@
# 足迹 Footprints
> 个人成长档案馆 —— 面向大学生的留痕、检索、量化与多端同步系统。
![CI](https://gitea.linyux.cn/root/FootPrint/badges/workflows/ci.yml/badge.svg)
![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Node](https://img.shields.io/badge/node-%E2%89%A522.5-green.svg)
平时随手留痕,需要时秒级检索:**大二上学期做过什么?拿过哪些省级奖?志愿时长总共多少?**
评奖评优、综测、简历、入党材料的素材库,也是一台记录成长的时光机。
## 功能特性
- **留痕本体** —— 自定义记录类型 + 动态表单模板;竞赛、志愿、学生工作、荣誉、证书、科研、实习皆可配;正面/负面、进行中、草稿全留痕
- **秒级检索** —— FTS5 trigram 中文全文搜索 + 多条件筛选 + 保存视图(同一查询一键复用)
- **时间线** —— 按学年/月分组的完整回顾
- **学业成绩** —— 学期 GPA/排名、课程成绩、可自定义绩点梯度(grade band)与趋势图
- **成长量化** —— 自定义维度与指标(如志愿时长),统计总览与年度对比
- **附件佐证** —— 图片/文档上传,SHA-256 内容寻址去重
- **多用户云优先** —— 所有数据存服务器,按用户独立 SQLite 档案库;浏览器 / PWA / Windows 桌面三端同源同步
- **管理后台** —— 邀请码全生命周期管理(自定义码、备注、启用/停用、删除、批量生成)、用户列表、注册配额
- **演示模式** —— 内置演示账号 `demo / demo1234`,自动播种 16 条足迹 + 4 学期成绩,开箱即看
- **PWA + 桌面** —— 可安装到桌面/手机;Windows Tauri 安装包;亮/暗双主题
## 架构
```
浏览器 PWA ─┐
Windows 桌面 ─┤ HTTPS + Bearer Token(365 天)
移动端 PWA ─┘
│
▼
┌────────────────────────────────┐
│ sync-server(零依赖 Node ≥ 22.5)│
│ node:http + node:sqlite │
│ scrypt 认证 · 邀请码 · LWW 同步 │
└───────────────┬────────────────┘
▼
data/users/<uid>.db 每用户独立 SQLite 档案库
data/blobs/<uid>/ 内容寻址附件存储
```
设计取向:
- **服务器零 npm 依赖**:仅用 Node 内置模块(`node:http` / `node:sqlite` / `node:crypto`),攻击面与供应链风险极小
- **命令层同源**:Web、桌面、数据播种共用同一份 `app/server/db.mjs` 命令层,同一业务逻辑多处不漂移
- **同步元数据内建**:所有表带 uuid/hlc/origin 列 + oplog,为多端增量同步而生
## 快速开始(Docker 部署)
```bash
git clone https://gitea.linyux.cn/root/FootPrint.git footprints
cd footprints/sync-server
# 构建 + 启动(数据持久化在 ./data)
docker compose -f docker-compose.caddy.yml up -d --build
# 国内拉取 node:24-alpine 超时时,换镜像源构建:
docker compose -f docker-compose.caddy.yml build \
--build-arg NODE_IMAGE=docker.1ms.run/library/node:24-alpine
```
反向代理(Caddy 示例,容器网络需与 Caddy 同网):
```caddyfile
footprints.example.com {
reverse_proxy footprints-sync:8788
}
```
> compose 默认加入名为 `mynet` 的外部网络;没有就先 `docker network create mynet`,或把 compose 里的 networks 段改成你自己的。
首次使用:
1. 打开站点 → 注册 —— **第一个注册的账号自动成为管理员**
2. 管理员登录 → 侧边栏「管理」→ 新建/批量生成邀请码,发给需要注册的用户
3. 人齐之后建议收紧注册:compose 里 `REGISTER_ENABLED=false`(或改掉 `INVITE_CODE` 默认值)再 `up -d`
4. 想先看看界面?用演示账号 **demo / demo1234** 登录,数据管够
## 本地开发
```bash
# 1) 启动同步服务器(Node ≥ 22.5,推荐 24;零依赖,无需 npm install)
cd sync-server
PORT=8788 DATA_DIR=./data node src/index.mjs
# 2) 前端开发服务
npm install --prefix app # 或 pnpm -C app install
npm run dev --prefix app
# 3) 让网页版指向本地服务器(浏览器控制台执行一次)
localStorage.setItem('fp-server-url', 'http://127.0.0.1:8788')
# 4) Windows 桌面安装包(需 Rust + MSVC)
pnpm -C app install && pnpm -C app tauri build
```
## 测试与质量
```bash
node scripts/check.mjs # 语法 + 类型 + 构建 + 关键标记 tripwire
node scripts/test-integration.mjs # 35 条集成断言(播种/登录/邀请码全矩阵/角色规则/幂等)
node scripts/smoke.mjs <url> <user> <pass> # 部署后冒烟
git config core.hooksPath .githooks # 启用 pre-push 钩子:工作区不干净或测试红,push 直接拒绝
```
CI:push 自动跑 check + 集成测试(见 `.gitea/workflows/ci.yml`,需在 Gitea 配置 act_runner)。
## 项目结构
```
├─ app/ # 前端 + 桌面端
│ ├─ src/ # React 19 + Vite + Tailwind v4 UI(三端共用)
│ ├─ public/ # PWA:sw.js v3(接口绝不缓存)/ manifest / icons
│ ├─ server/ # ★ 命令层 db.mjs:零依赖数据层(37 个命令)
│ └─ src-tauri/ # Tauri 2 桌面壳
├─ sync-server/ # 自建同步服务器
│ ├─ src/ # index/auth/db/sync/attachments(零依赖)
│ ├─ www/ # 构建产物(部署时以卷挂载,git pull 即更新)
│ └─ docker-compose.caddy.yml
├─ scripts/ # check / test-integration / smoke
├─ .gitea/workflows/ # CI
├─ .githooks/ # pre-push 钩子
└─ docs/api.md # HTTP API 参考
```
## 环境变量(sync-server)
| 变量 | 默认 | 说明 |
|---|---|---|
| `PORT` | `8788` | 监听端口 |
| `DATA_DIR` | `./data` | 数据目录(用户库/附件/同步账本) |
| `REGISTER_ENABLED` | `true` | 是否开放注册 |
| `INVITE_CODE` | 空 | 静态兜底邀请码(动态码由管理员在管理页生成) |
| `MAX_USERS` | `0` | 用户数上限,0 = 不限 |
| `WWW_DIR` | `../www` | 前端静态目录 |
## 文档
- [HTTP API 参考](docs/api.md)
- [设计方案(v0.5 底稿)](DESIGN.md) —— 项目的原始设计思想;现产品已演进为云优先多用户架构
## Roadmap
- [ ] 附件缩略图 / 上传前压缩 / 大文件分片
- [ ] 评奖评优材料包一键导出
- [ ] 对象存储(S3)适配
- [ ] 端到端加密存储
- [ ] 移动端体验优化(手势、离线编辑队列)
- [ ] 英文界面
## License
[MIT](LICENSE)
+82
View File
@@ -0,0 +1,82 @@
# HTTP API 参考
Base URL:部署域名(如 `https://footpr.example.com`)。除 `/health` 与 `/auth/register|login` 外,
所有接口都需要 `Authorization: Bearer <token>`。
统一响应格式:
```json
{ "ok": true, "data": { ... } }
{ "ok": false, "error": "错误原因" }
```
## 健康检查
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/health` | 存活探针,无需鉴权 |
## 认证
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/auth/register` | 注册(`REGISTER_ENABLED`/`INVITE_CODE`/`MAX_USERS` 门控;首位用户自动成为管理员) |
| POST | `/auth/login` | 登录,返回 Bearer Token(365 天) |
| GET | `/auth/me` | 当前用户信息(`user_id` / `username` / `is_admin` / `device_id`) |
```bash
curl -s https://footpr.example.com/auth/login \
-H 'content-type: application/json' \
-d '{"username":"demo","password":"demo1234","device_name":"cli"}'
# => {"ok":true,"data":{"token":"...","user_id":"...","device_id":"...","expires_at":"..."}}
```
## 管理(仅 is_admin 会话)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/admin/users` | 用户列表(设备数/记录数/附件用量) |
| GET | `/admin/invites` | 邀请码列表(含 remark、创建者、使用者) |
| POST | `/admin/invites/generate` | 批量生成随机码 `{"count": 5}`(1-50) |
| POST | `/admin/invites/create` | 新建自定义码 `{"code":"XIONG-2025","remark":"备注"}`(4-32 位字母数字-\_) |
| POST | `/admin/invites/update` | 改备注 / 启停 `{"id":1,"remark":"...","valid":false}`(已使用的码不可再启用) |
| POST | `/admin/invites/delete` | 删除 `{"id":1}` |
| POST | `/admin/invites/revoke` | 作废(等价 update valid=false,兼容保留) |
| POST | `/admin/demo-seed` | 给 demo 账号播种演示数据(幂等;档案库非空时返回 seeded:false) |
## 同步
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/sync/status` | 当前用户同步账本统计 |
| POST | `/sync/push` | 推送 wire 变更(LWW 上/删除,立即生效) |
| GET | `/sync/pull?cursor=...&limit=...` | 按 seq 游标增量拉取 |
| POST | `/sync/attachment` | 上传附件 `{"sha256","size","mime","file_name","data_b64"}`(内容寻址去重) |
| GET | `/sync/attachment/<sha256>` | 下载附件 |
## 业务命令层 `POST /api/<command>`
与桌面端共用同一命令层(`app/server/db.mjs`),按用户隔离。37 个命令:
| 分组 | 命令 |
|---|---|
| 应用 | `app_info` · `sync_status` |
| 记录类型 | `type_list` · `type_upsert` · `type_delete` |
| 字典/维度 | `taxonomy_list` · `taxonomy_upsert` · `taxonomy_delete` · `dimension_list` · `dimension_upsert` · `dimension_delete` |
| 足迹 | `footprint_list` · `footprint_get` · `footprint_upsert` · `footprint_delete` · `timeline` |
| 统计 | `stats_overview` · `metric_list` · `metric_upsert` · `metric_delete` |
| 成绩 | `grades_overview` · `semester_upsert` · `semester_delete` · `course_upsert` · `course_delete` · `band_upsert` · `band_delete` |
| 附件 | `attachment_add` · `attachment_list` · `attachment_delete` · `attachment_read` |
| 保存视图 | `view_list` · `view_upsert` · `view_delete` |
| 播种/同步内核 | `demo_seed` · `sync_apply_wire` · `sync_pull_wire` |
示例(检索记录):
```bash
curl -s https://footpr.example.com/api/footprint_list \
-H 'content-type: application/json' -H "authorization: Bearer $TOKEN" \
-d '{"query":{"page":1,"page_size":20,"year":2025}}'
```
`footprint_list` 的 `query` 支持:`year`、`type_id`、`level`、`status`、`q`(全文检索)、
`dimension_id`、`sort`(date_desc/date_asc/updated_desc/weight_desc)、分页 `page`/`page_size`。
-1
View File
@@ -21,7 +21,6 @@ services:
- REGISTER_ENABLED=true - REGISTER_ENABLED=true
- INVITE_CODE=please-change-me # 多用户:注册须携带此邀请码(留空则不校验) - INVITE_CODE=please-change-me # 多用户:注册须携带此邀请码(留空则不校验)
- MAX_USERS=20 # 用户数上限,0=不限 - MAX_USERS=20 # 用户数上限,0=不限
# - ADMIN_TOKEN=please-change-me # 设置后可用 GET /admin/users(请求头 X-Admin-Token)
restart: unless-stopped restart: unless-stopped
networks: networks:
- proxy - proxy