Skip to content

feat(backend)!: Cloud Database Migration Script - #185

Closed
Alexander-Noah wants to merge 7 commits into
1024XEngineer:mainfrom
Alexander-Noah:feature/backend-cloud-schedule-schema
Closed

Alexander-Noah wants to merge 7 commits into
1024XEngineer:mainfrom
Alexander-Noah:feature/backend-cloud-schedule-schema

Conversation

@Alexander-Noah

Copy link
Copy Markdown
Contributor

关联 Issue

Closes #<feat(backend): 建立云端日程 PostgreSQL 三表迁移与 ORM 契约>

变更概述

  • 新增 accounts、schedules、schedule_occurrence_overrides 三张业务表。
  • 将数据库迁移拆分为三个线性 Alembic 版本:
    • 20260810_0003_create_accounts_table.py
    • 20260810_0004_create_schedules_table.py
    • 20260810_0005_create_schedule_occurrence_overrides_table.py
  • 保留已有的 20260728_0001 和 20260729_0002 迁移历史。
  • 新增对应的 SQLAlchemy ORM 模型。
  • 补充字段、外键、唯一约束、检查约束、默认值和索引。
  • 增加 ORM、迁移链和真实 PostgreSQL 数据库结构测试。

变更原因

已有的 20260729_0002 属于 Alembic 迁移历史,不能直接修改同一个版本号,否则可能导致相同迁移版本对应不同的数据库结构。

本 PR 通过新增正向迁移完成三张业务表的结构升级,并在替换旧版 schedules 表前检查历史数据:

  • 旧表为空:允许升级并创建新版结构。
  • 旧表存在数据:终止升级并回滚,防止历史数据被意外删除。

破坏性变更

数据库升级时,空的旧版 schedules 表将被替换为新版云端日程表结构。

如果旧表中存在数据,迁移会主动失败。旧数据转换不在本 PR 范围内,需要另行设计迁移方案。

验证结果

  • Ruff 代码检查通过
  • Ruff 格式检查通过
  • MyPy 类型检查通过
  • 后端测试通过
  • 测试覆盖率达到要求
  • Alembic 只有一个最新版本:20260810_0005
  • alembic check 检查通过
  • 空 PostgreSQL 数据库可以执行 upgrade head
  • 非空旧表的升级保护验证通过
  • 升级 → 降级 → 再次升级 迁移路径验证通过
  • PostgreSQL 字段、约束、默认值和索引验证通过

本次不包含

  • 非空旧版 schedules 表的数据转换
  • 注册、登录和 JWT 接口
  • 云端同步接口
  • 日程增删改查和数据仓库层
  • 客户端 SQLite 数据库结构
  • 种子数据和示例数据
  • 全天日程默认提前 900 分钟提醒
  • 修改日程或周期例外时自动递增 revision

Replace the existing service, migration, and container scaffolding with the local layered packages and minimal FastAPI entry point.

BREAKING CHANGE: the previous timeapp package, health API, Alembic migrations, and backend container setup are removed.
Package the service under src/timeflow, align the versioned API, and add reproducible local and Docker validation.

BREAKING CHANGE: backend imports and startup commands now use the timeflow package and timeflow.main:app.
Removed instructions for cloning and starting the project.
Add forward Alembic migrations for accounts, schedules, and occurrence overrides while preserving existing revision history. Reject upgrades when the legacy schedules table contains data.

BREAKING CHANGE: the empty legacy schedules table is replaced by the cloud schedule schema during migration.
@vercel

vercel Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timeflow Ready Ready Preview Aug 10, 2026 7:34am

@fennoai fennoai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one migration safety issue in the legacy-table replacement path. Local tests were not run because uv is unavailable in the review environment.

"""Replace the legacy table only when it contains no data."""

connection = op.get_bind()
legacy_row = connection.execute(sa.text("SELECT 1 FROM schedules LIMIT 1")).first()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Lock the legacy table before checking whether it is empty. This SELECT only takes an ACCESS SHARE lock, so another transaction can insert and commit after the check while this migration waits to acquire ACCESS EXCLUSIVE for DROP TABLE. The migration would then silently drop that newly inserted row, defeating the data-loss guard. Acquire an exclusive table lock before the emptiness check (or otherwise quiesce writes), and cover the concurrent-writer case with a regression test.

This branch was successfully deployed

1 active deployment
Preview — 82c0df40 Deployed Aug 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant