feat: strengthen backup and mobile workflows
This commit is contained in:
+55
-39
@@ -1,59 +1,75 @@
|
||||
# API
|
||||
|
||||
Base path: `/api/v1`. 除初始化、登录和健康检查外均需 `dodo_session` Cookie。
|
||||
Base path: `/api/v1`。除初始化、登录和健康检查外,接口均要求 `dodo_session` Cookie;同源写请求使用双提交 CSRF 校验。登录后可访问 `/api/docs` 和 `/api/openapi.json`。
|
||||
|
||||
## Setup / Auth
|
||||
## 账户与会话
|
||||
|
||||
- `GET /setup/status`
|
||||
- `POST /setup/initialize`
|
||||
- `POST /auth/login`
|
||||
- `POST /auth/logout`
|
||||
- `GET /me`
|
||||
- `GET /setup/status`、`POST /setup/initialize`
|
||||
- `POST /auth/login`、`POST /auth/logout`、`POST /auth/change-password`
|
||||
- `GET/PATCH /me`
|
||||
- `GET /sessions`、`DELETE /sessions/{session_id}`、`DELETE /sessions/others`
|
||||
- `GET /audit-logs`
|
||||
|
||||
## Folders
|
||||
## 文件夹、清单与任务
|
||||
|
||||
- `GET /folders` — 仅返回未删除文件夹
|
||||
- `POST /folders` — 创建文件夹
|
||||
- `PATCH /folders/{folder_id}` — 重命名
|
||||
- `DELETE /folders/{folder_id}` — 软删除;其清单移到根层级
|
||||
- 文件夹:`GET/POST /folders`,`PATCH/DELETE /folders/{id}`,以及排序接口
|
||||
- 清单:`GET/POST /lists`,`PATCH/DELETE /lists/{id}`,归档恢复、永久删除、移动与排序接口
|
||||
- 归档清单只标记 `deleted_at`,不会把任务移到收集箱;清单归档期间,其任务在普通列表和详情中不可见,恢复清单后重新出现
|
||||
- 任务:`GET/POST /tasks`、`GET/PATCH/DELETE /tasks/{id}`、`POST /tasks/{id}/restore`、`DELETE /trash/{id}`
|
||||
- `GET /tasks` 和 `GET /trash` 使用不透明游标;`limit` 为 1–100
|
||||
- `POST /tasks/batch` 支持批量完成、移动、截止时间与软删除;任务写入使用 `version` 乐观锁
|
||||
- 一层子任务必须与父任务同清单;任务可带日期型或具体时间型截止时间
|
||||
|
||||
## Lists
|
||||
## 重复任务
|
||||
|
||||
- `GET /lists` — 仅返回未删除清单,系统收集箱排首位
|
||||
- `POST /lists` — 创建清单,可指定 `folder_id`
|
||||
- `PATCH /lists/{list_id}` — 重命名;系统收集箱返回 409
|
||||
- `DELETE /lists/{list_id}` — 软删除并将任务移入系统收集箱;系统收集箱返回 409
|
||||
- `GET /tasks/{task_id}/recurrence`
|
||||
- `POST /recurrences`、`PATCH/DELETE /recurrences/{id}`
|
||||
- `POST /recurrences/{id}/complete`
|
||||
- 支持 RRULE 计划重复与按用户本地完成日期计算的“完成后重复”
|
||||
|
||||
## Tasks
|
||||
## 习惯、倒数日与备忘录
|
||||
|
||||
- `GET /tasks?q=&limit=&cursor=` — 顶层未删除任务的游标分页;`q` 匹配标题、描述和清单名
|
||||
- `POST /tasks` — 创建任务;`parent_id` 只允许指向同清单顶层任务
|
||||
- `GET /tasks/{task_id}` — 返回任务和一层子任务
|
||||
- `PATCH /tasks/{task_id}` — 必须携带当前 `version`,原子比较更新;版本冲突返回 409
|
||||
- `DELETE /tasks/{task_id}` — 软删除任务及其直接子任务
|
||||
- `POST /tasks/{task_id}/restore` — 恢复任务及其直接子任务
|
||||
- `POST /tasks/batch` — 原子批量完成、移动、设置截止时间或软删除
|
||||
- 习惯:创建、列表/周网格、部分更新、排序、归档/恢复/永久删除、日志、暂停和统计
|
||||
- 倒数日:创建、列表、编辑、置顶、归档/恢复/永久删除;支持公历/农历及周/月/年重复
|
||||
- 备忘录:游标列表、创建、读取、乐观锁更新、软删除、恢复和归档后永久删除
|
||||
|
||||
批量请求字段:`task_ids`、`completed`、`list_id`、`due_at`、`soft_delete`。所有任务和目标清单在写入前完成归属校验;任一不存在则整批不修改。
|
||||
## 附件
|
||||
|
||||
## Countdowns
|
||||
- `GET/POST /tasks/{task_id}/attachments`
|
||||
- `GET/DELETE /attachments/{attachment_id}`
|
||||
- 文件保存在服务端附件目录;上传受大小与类型限制,访问始终校验当前用户归属
|
||||
|
||||
- `GET /countdowns?archived=false` — 查询倒数日;置顶项优先,其余按下一次发生日期排序
|
||||
- `POST /countdowns` — 创建倒数日、纪念日或生日;支持 `none/weekly/monthly/yearly` 重复
|
||||
- `PATCH /countdowns/{countdown_id}` — 编辑名称、日期、类型、重复与图标
|
||||
- `POST /countdowns/{countdown_id}/pin` — 单一置顶,自动取消其他置顶项
|
||||
- `DELETE /countdowns/{countdown_id}` — 归档
|
||||
- `POST /countdowns/{countdown_id}/restore` — 恢复归档项
|
||||
- `DELETE /countdowns/{countdown_id}/purge` — 永久删除已归档项
|
||||
## 完整备份 ZIP v2
|
||||
|
||||
## Recycle bin
|
||||
### `GET /backup/export.zip`
|
||||
|
||||
- `GET /trash?limit=&cursor=` — 已删除顶层任务的游标分页
|
||||
- `DELETE /trash/{task_id}` — 永久删除任务及其子任务
|
||||
导出 `dodo-backup` version 2 ZIP。归档包含 `manifest.json`、每类实体的 `data/*.json`、附件元数据和附件原始字节。manifest 声明实体数量及每个条目的 SHA-256。
|
||||
|
||||
游标是不透明字符串。无效游标返回 422;`limit` 范围为 1–100。
|
||||
实体范围:`folders`、`lists`、`tasks`、`recurrences`、`recurrence_exceptions`、`habits`、`habit_logs`、`habit_pauses`、`countdowns`、`memos`、`attachments`。会话、密码散列、审计日志及备份内部账本不导出。
|
||||
|
||||
## Health
|
||||
### `POST /backup/preflight?mode=merge|replace`
|
||||
|
||||
以 multipart 字段 `file` 上传 ZIP。服务端流式暂存,并在返回令牌前验证:ZIP 路径与条目、压缩比/容量、manifest 版本与计数、全部校验和、字段与业务约束、关系拓扑、一层任务树、用户隔离以及附件元数据/字节一致性。
|
||||
|
||||
成功返回 `valid`、短期 `preflight_token`、`backup_id`、归档摘要和各实体数量。预检有每用户待处理数量/容量配额和过期时间;令牌绑定用户、文件摘要和恢复模式。
|
||||
|
||||
### `POST /backup/restore`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{"preflight_token":"...","mode":"merge"}
|
||||
```
|
||||
|
||||
`merge` 使用持久化 source→target ID/内容摘要账本实现可重试合并;跨用户或同 ID 不同内容冲突会拒绝。`replace` 在事务内替换当前用户业务实体,并通过同文件系统隔离区协调附件删除和失败补偿。令牌单次消费;若数据库已提交但隔离区清理失败,同一令牌仅重试清理,不重复导入。
|
||||
|
||||
## 旧格式兼容
|
||||
|
||||
- `GET /export`、`GET /export.csv`:旧版 JSON/UTF-8-BOM CSV v1 轻量导出,不是完整备份
|
||||
- `POST /restore?mode=merge|replace`、`POST /restore.csv?mode=merge|replace`:兼容旧 JSON/CSV v1
|
||||
- 旧格式只覆盖文件夹、清单、任务、重复模板、习惯、倒数日和备忘录;不包含日志、暂停、重复例外和附件字节
|
||||
|
||||
## 健康检查
|
||||
|
||||
- `GET /health/live`
|
||||
- `GET /health/ready`
|
||||
|
||||
+29
-80
@@ -1,97 +1,46 @@
|
||||
# dodo 数据模型
|
||||
|
||||
所有业务实体使用 UUIDv7 主键并按 `user_id` 隔离。生产使用 PostgreSQL,测试使用 SQLite;模型保持两者兼容。
|
||||
业务主键使用 UUIDv7;带 `user_id` 的实体按用户隔离。时间点按 UTC 保存,用户时区用于日期语义与展示。生产使用 PostgreSQL,测试使用 SQLite。
|
||||
|
||||
## app_state
|
||||
## 账户
|
||||
|
||||
- `key` 主键
|
||||
- `created_at`
|
||||
- `app_state`:初始化状态
|
||||
- `users`:用户名、密码散列、时区
|
||||
- `sessions`:会话令牌散列、过期/最近访问时间、IP 与 User-Agent
|
||||
- `audit_logs`:用户、动作、实体、非敏感摘要和时间
|
||||
|
||||
## users
|
||||
## 任务域
|
||||
|
||||
- `id`
|
||||
- `username` 唯一
|
||||
- `password_hash`
|
||||
- `timezone`
|
||||
- `created_at`
|
||||
- `folders`:名称、位置、软删除时间;删除文件夹只解除清单分组
|
||||
- `task_lists`:文件夹、名称、收集箱标记、位置、软删除时间
|
||||
- `tasks`:清单、可空父任务、标题、Markdown 描述、优先级、完成/完成时间、截止时间、`due_has_time`、位置、版本、软删除与外部 ID
|
||||
- `recurrence_templates`:任务的一对一重复规则、开始/结束、计划或完成后触发模式、完成后间隔与最近完成时间
|
||||
- `recurrence_exceptions`:模板发生时间及标题/截止/完成/删除覆盖
|
||||
- `attachments`:任务、原始文件名、服务端存储名、MIME、大小和创建时间
|
||||
- `purge_operations`:清单永久删除时附件隔离区清理的补偿状态
|
||||
|
||||
## sessions
|
||||
任务树只允许一层,父子任务属于同一清单。清单归档仅设置清单 `deleted_at`,保留所有任务的 `list_id`;查询隐藏归档清单内任务,恢复清单后原任务和完成状态重新可见。任务与子任务软删除/恢复按生命周期规则处理,永久删除会清理关联重复数据和附件。
|
||||
|
||||
- `id`
|
||||
- `token_hash` 唯一
|
||||
- `user_id` → users,级联删除
|
||||
- `expires_at`
|
||||
- `created_at`
|
||||
## 习惯、倒数日与备忘录
|
||||
|
||||
## folders
|
||||
- `habits`:完成型/数值型、目标/上限、日/周/月/间隔计划、开始日、排序和归档
|
||||
- `habit_logs`:习惯与日期唯一的数值记录
|
||||
- `habit_pauses`:习惯暂停区间
|
||||
- `countdowns`:标题、日期、公历/农历字段、类型、重复、兼容保留的图标字段、置顶与归档
|
||||
- `memos`:标题、Markdown 内容、乐观锁版本、创建/更新时间和软删除时间
|
||||
|
||||
- `id`
|
||||
- `user_id` → users
|
||||
- `name`
|
||||
- `position`
|
||||
- `created_at`
|
||||
- `deleted_at`,非空表示软删除
|
||||
## 完整备份 v2 内部状态
|
||||
|
||||
删除文件夹不会删除清单;清单的 `folder_id` 被置空。
|
||||
- `backup_preflights`:令牌散列、用户、备份 ID/摘要/大小、暂存路径、模式、状态、过期/消费时间和待清理路径
|
||||
- `backup_imports`:每用户已导入备份 ID、归档摘要、模式和时间;用于幂等判断
|
||||
- `backup_import_entities`:源实体 ID 到目标 ID 的映射及内容摘要;用于 merge 冲突检测与可重试导入
|
||||
|
||||
## task_lists
|
||||
这些表和账户/会话/审计信息不属于用户可迁移业务实体。ZIP v2 只导出:
|
||||
|
||||
- `id`
|
||||
- `user_id` → users
|
||||
- `folder_id` → folders,可空
|
||||
- `name`
|
||||
- `is_inbox`,每个用户初始化时创建一个受保护的系统收集箱
|
||||
- `position`
|
||||
- `created_at`
|
||||
- `deleted_at`,非空表示软删除
|
||||
`folders`、`lists`、`tasks`、`recurrences`、`recurrence_exceptions`、`habits`、`habit_logs`、`habit_pauses`、`countdowns`、`memos`、`attachments`。
|
||||
|
||||
删除普通清单时,其未删除任务原子移动到系统收集箱。系统收集箱不可重命名或删除。
|
||||
|
||||
## tasks
|
||||
|
||||
- `id`
|
||||
- `user_id` → users
|
||||
- `list_id` → task_lists
|
||||
- `parent_id` → tasks,可空;仅允许一层子任务且必须与父任务同清单
|
||||
- `title`
|
||||
- `description`
|
||||
- `priority`(0–3)
|
||||
- `completed`
|
||||
- `due_at`,可空
|
||||
- `version`,乐观锁版本;单任务更新用 `id + user_id + version` 原子比较更新
|
||||
- `position`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
- `deleted_at`,非空表示进入回收站
|
||||
|
||||
顶层任务软删除、恢复或永久删除时同步处理直接子任务。列表与回收站使用 `(created_at, id)` 作为稳定游标排序键。
|
||||
|
||||
## countdowns
|
||||
|
||||
- `id`
|
||||
- `user_id` → users,级联删除
|
||||
- `title`
|
||||
- `event_date`,仅日期
|
||||
- `kind`:`countdown` / `anniversary` / `birthday`
|
||||
- `repeat_rule`:`none` / `weekly` / `monthly` / `yearly`
|
||||
- `icon`
|
||||
- `pinned`,每个用户仅保留一个置顶项
|
||||
- `archived_at`,非空表示归档
|
||||
- `created_at` / `updated_at`
|
||||
附件导出时去掉内部 `storage_name`,改用归档内安全路径并携带真实字节;恢复时生成目标存储名。每个实体文件和附件字节均由 manifest SHA-256 覆盖。
|
||||
|
||||
## 迁移
|
||||
|
||||
- `0001_initial.py`:已部署的初始模式,不修改
|
||||
- `0002_task_management.py`:新增文件夹/清单软删除列、历史标签表及游标/回收站索引
|
||||
- `0008_remove_calendar_subscriptions.py`:移除日历订阅表
|
||||
- `0009_remove_tags.py`:移除历史标签表及任务标签关联表
|
||||
- `0010_countdowns.py`:新增倒数日、纪念日与生日表
|
||||
|
||||
## 后续阶段预留
|
||||
|
||||
- task_reminders
|
||||
- task_recurrence_templates
|
||||
- task_recurrence_exceptions
|
||||
- habits / habit_logs / habit_reminders
|
||||
- attachments
|
||||
- audit_logs
|
||||
迁移按 `0001` 至 `0019` 顺序应用;当前最新 `0019_backup_imports.py` 增加完整备份预检、导入及实体映射账本。历史迁移还覆盖任务管理、会话元数据、查询索引、习惯排序、倒数日/农历、日期型截止语义、重复触发模式、备忘录与 `completed_at` 等演进。
|
||||
|
||||
+31
-102
@@ -1,117 +1,46 @@
|
||||
# dodo 产品与技术决策记录
|
||||
|
||||
## 定位
|
||||
## 定位与当前范围
|
||||
|
||||
dodo 是一个纯自托管的 TickTick-like 任务与习惯管理工具。目标不是一比一复刻 TickTick,而是做一个数据归自己、界面温暖紧凑、适合个人长期使用的任务系统。
|
||||
dodo 是纯自托管、面向个人长期使用的任务与生活管理 PWA。当前包含任务/子任务、文件夹与清单、今日视图、重复任务、习惯、倒数日、Markdown 备忘录、附件、会话管理、审计和数据备份;不提供番茄钟、自然语言建任务或外部通知渠道。
|
||||
|
||||
## 当前边界
|
||||
## 技术与数据
|
||||
|
||||
- 首版只做本地开发验证,不部署。
|
||||
- 首版不做通知渠道:Web Push、Telegram、SMTP 暂不实现。
|
||||
- 首版附件只做本地存储,不实现 S3。
|
||||
- 首版不做番茄钟。
|
||||
- 首版不做自然语言创建任务。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Monorepo:`frontend/`、`backend/`
|
||||
- 前端:Vue 3 + TypeScript + Vite + Tailwind CSS + Shadcn-vue/Reka UI
|
||||
- 后端:FastAPI + Pydantic v2 + SQLAlchemy 2 Async + Alembic
|
||||
- 数据库:PostgreSQL,主键 UUIDv7,时间统一 UTC,用户配置时区
|
||||
- 包管理:uv + pnpm
|
||||
- 交付:单 Docker 镜像,外部 PostgreSQL
|
||||
- 许可证:AGPL-3.0
|
||||
|
||||
## 数据库
|
||||
|
||||
本地开发数据库:
|
||||
|
||||
```text
|
||||
postgresql+asyncpg://postgres:***@10.10.100.99:5433/dodo
|
||||
```
|
||||
|
||||
已从默认 `postgres` 库迁移到独立 `dodo` 库。误建在 `postgres.public` 的 dodo 表已清理。
|
||||
- Monorepo:Vue 3 + TypeScript + Vite 前端,FastAPI + Pydantic v2 + SQLAlchemy 2 Async + Alembic 后端
|
||||
- PostgreSQL 生产、SQLite 测试;UUIDv7 主键,时间点使用 UTC,日历语义使用用户时区
|
||||
- 单 Docker 镜像,外部 PostgreSQL;AGPL-3.0
|
||||
- 用户业务读写必须按归属过滤;更新任务/备忘录使用乐观锁
|
||||
|
||||
## 产品模型
|
||||
|
||||
### 任务
|
||||
- 文件夹 → 清单 → 一层任务树;系统收集箱受保护
|
||||
- 清单删除定义为归档:保留任务成员关系,归档期间隐藏,恢复后原样出现;永久删除仅允许作用于已归档清单
|
||||
- 截止日期区分全天日期和具体时间;重复任务支持 RFC 5545 计划重复及“完成后重复”
|
||||
- 习惯支持完成型/数值型、日/周/月/间隔计划、暂停、历史和归档
|
||||
- 倒数日支持公历/农历、生日/纪念日、重复、置顶与归档
|
||||
- 备忘录使用 Markdown,支持软删除、恢复及归档后永久删除
|
||||
|
||||
- 文件夹 → 清单 → 任务
|
||||
- 系统内置收集箱,不允许删除
|
||||
- 任务支持一层子任务
|
||||
- 状态:未完成 / 已完成
|
||||
- 优先级:无 / 低 / 中 / 高
|
||||
- Markdown 描述
|
||||
- 截止日期 + 具体时间
|
||||
- 多提醒设计预留
|
||||
- 删除为软删除,回收站手动清空
|
||||
- 支持创建、修改、完成、恢复、删除操作历史
|
||||
- 并发编辑使用原子乐观锁
|
||||
## UI 决策
|
||||
|
||||
### 重复任务
|
||||
- 桌面保留左导航/内容/可选详情三栏;移动端使用底部导航
|
||||
- 手机底栏固定为“今天、习惯、倒数日、设置”,精确匹配当前页面;不使用“更多”中转
|
||||
- 新建入口使用同一个普通圆形 Plus FAB,禁止装饰性光环或吉祥物
|
||||
- 设置页使用连续分组:数据、账户与安全、登录设备、活动、危险操作
|
||||
- 任务、习惯、倒数日、备忘录、操作菜单和确认框统一走 `AppSheet` / `AppDialog` 覆盖层栈;共享背景 inert、焦点陷阱、Escape、忙碌态和嵌套焦点恢复
|
||||
- 桌面任务/备忘录详情可保持非模态,移动端由同一组件切为底部模态弹层
|
||||
|
||||
- RFC 5545 RRULE
|
||||
- 模板 + 实例
|
||||
- 修改范围:仅本次 / 本次及以后 / 全部
|
||||
- 删除单次保存为例外
|
||||
- 每月 31 日在无 31 日月份跳过
|
||||
- 逾期完成不影响下次计划日期
|
||||
## 备份决策
|
||||
|
||||
### 习惯
|
||||
|
||||
- 完成型 + 数值型
|
||||
- 每天 / 每周 / 每月 / 间隔天数
|
||||
- 数值型当日累计,达标后封顶
|
||||
- 允许补打和修改历史
|
||||
- 支持暂停区间,暂停期不破坏连续记录
|
||||
- 归档后保留历史统计
|
||||
|
||||
### 倒数纪念日
|
||||
|
||||
- 支持倒数日、纪念日、生日
|
||||
- 支持不重复、每周、每月、每年重复
|
||||
- 未来显示“还有 N 天”,当天显示“就是今天”,过去显示“已经 N 天”
|
||||
- 支持单一置顶、归档恢复、编辑和删除
|
||||
|
||||
## UI 方向
|
||||
|
||||
- 手账生活感
|
||||
- 中高信息密度
|
||||
- 10–12px 中等圆角
|
||||
- 细分割线为主,少量浅底色
|
||||
- 强调色:`#F15A29`
|
||||
- 只做浅色模式
|
||||
- 系统字体栈
|
||||
- 不使用猫猫元素
|
||||
- 轻微动效
|
||||
|
||||
## 页面结构
|
||||
|
||||
- 桌面三栏:左导航 / 中任务列表 / 右任务详情
|
||||
- 手机底部导航
|
||||
- 顶部快速输入,手机悬浮新增按钮
|
||||
- 桌面右侧详情栏,手机底部弹层
|
||||
- 习惯首页:今日习惯列表 + 一周打卡格
|
||||
- 搜索:顶部搜索框 + 全局搜索快捷键
|
||||
- “完整备份”专指 `dodo-backup` ZIP version 2,而不是旧 JSON/CSV
|
||||
- v2 覆盖全部用户业务实体、历史/例外、附件元数据与附件字节;manifest 记录实体数量和每个条目的 SHA-256
|
||||
- 恢复必须先预检,再用绑定用户、文件摘要和模式的短期单次令牌执行
|
||||
- 预检拒绝未知/缺失实体、不安全 ZIP 路径、重复条目、异常压缩比/容量、校验和错误、非法字段、破坏关系拓扑或一层任务树的数据
|
||||
- `merge` 通过持久化 ID/摘要账本保证幂等与冲突可见;`replace` 仅替换当前用户业务数据
|
||||
- 附件恢复采用同文件系统暂存/隔离与补偿;数据库提交后的清理失败可用同一令牌重试清理,不会再次导入
|
||||
- 保留 JSON/CSV v1 恢复兼容,但明确其不包含日志、暂停、重复例外和附件字节,仅用于旧数据迁移
|
||||
|
||||
## 工程质量
|
||||
|
||||
- `/api/v1` API 路径
|
||||
- 统一错误码、可读提示和字段详情
|
||||
- 页面内诊断信息 + Toast
|
||||
- readiness 检查数据库
|
||||
- 首版不提供 Prometheus metrics
|
||||
- 审计日志记录操作人、实体、动作、时间和变更摘要
|
||||
- 手动 JSON 全量导出
|
||||
- 附件默认 20MB,可用环境变量调整
|
||||
- 默认允许常用文档与图片,拒绝危险文件类型
|
||||
|
||||
## 第一阶段验收
|
||||
|
||||
- 可初始化管理员
|
||||
- 可登录
|
||||
- 可创建清单和任务
|
||||
- 后端测试通过
|
||||
- 前端生产构建通过
|
||||
- PostgreSQL 迁移成功
|
||||
- 本地应用可以启动并访问
|
||||
- API 基路径 `/api/v1`;Cookie Session、同源 CSRF、安全响应头、登录限流
|
||||
- 附件与备份均有容量限制、路径包含检查和用户归属校验
|
||||
- 后端使用 pytest + ruff,前端使用 Vitest + vue-tsc/Vite;变更结束运行全量测试、构建和 `git diff --check`
|
||||
|
||||
Reference in New Issue
Block a user