9c0474ef9c
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 42s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m50s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m53s
CI/CD Pipeline / Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m43s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Style (push) Successful in 3m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m7s
CI/CD Pipeline / Validate - Security (push) Successful in 5m45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m6s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m13s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Unit Tests (push) Failing after 7m58s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Failing after 198h32m29s
CI/CD Pipeline / CI Gate (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production Web Image (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production API Image (push) Failing after 198h32m31s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 198h36m16s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 198h37m22s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 198h37m23s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 198h39m3s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 198h37m23s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 198h39m11s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 198h39m14s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 198h39m20s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 198h39m40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 198h39m27s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 198h39m33s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 198h39m42s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 198h39m45s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 198h39m39s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 198h40m27s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 198h40m28s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 198h40m30s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 198h40m30s
CI/CD Pipeline / Frontend Lint (push) Failing after 198h40m30s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 198h40m37s
CI/CD Pipeline / PR Build API Image (push) Failing after 198h40m37s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 198h40m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 198h40m39s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 199h14m21s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 199h14m26s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 199h15m10s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 199h15m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 199h15m11s
CI/CD Pipeline / PR Build Web Image (push) Failing after 199h15m19s
Squash merge PR #1783 into develop
73 lines
2.4 KiB
Python
73 lines
2.4 KiB
Python
"""Projects is_default + partial unique index for idempotent default project (Issue #1775)
|
||
|
||
Revision ID: 069_project_is_default
|
||
Revises: 068_user_profile_completed
|
||
Create Date: 2026-09-08
|
||
|
||
背景:
|
||
小程序端 getOrCreateDefaultProject 在重试/并发/前端重复调用下,
|
||
仅靠应用层"先查再插"不保证幂等,会给同一用户重复创建默认项目。
|
||
|
||
改动:
|
||
1. projects 表新增 is_default 布尔列(默认 false)
|
||
2. 部分唯一索引 uq_projects_owner_default:(owner_user_id) WHERE is_default = true
|
||
—— 保证每个用户至多一个默认项目
|
||
3. 存量数据回填:把名为"默认项目"的存量项目按创建时间最早者标记为 is_default=true
|
||
(只标记不删除;存量重复项目的清理另行确认后单独执行)
|
||
|
||
注意:部分唯一索引依赖 PostgreSQL,不支持 downgrade 到其他方言。
|
||
"""
|
||
|
||
import sqlalchemy as sa
|
||
|
||
from alembic import op
|
||
|
||
revision = "069_project_is_default"
|
||
down_revision = "068_user_profile_completed"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def upgrade() -> None:
|
||
# 1. 新增 is_default 列
|
||
op.add_column(
|
||
"projects",
|
||
sa.Column(
|
||
"is_default",
|
||
sa.Boolean(),
|
||
nullable=False,
|
||
server_default=sa.text("false"),
|
||
),
|
||
)
|
||
|
||
# 2. 存量回填:每个拥有"默认项目"的用户,只把最早创建的那一个标记为默认。
|
||
# 用 ROW_NUMBER() 取每组第一条;非"默认项目"命名的项目不标记(保守,不动用户自建项目)。
|
||
op.execute("""
|
||
UPDATE projects p
|
||
SET is_default = true
|
||
WHERE p.id IN (
|
||
SELECT id FROM (
|
||
SELECT id,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY owner_user_id
|
||
ORDER BY created_at ASC, id ASC
|
||
) AS rn
|
||
FROM projects
|
||
WHERE name = '默认项目'
|
||
) t
|
||
WHERE t.rn = 1
|
||
)
|
||
""")
|
||
|
||
# 3. 部分唯一索引:每用户至多一个默认项目(只约束 is_default = true 的行)
|
||
op.execute("""
|
||
CREATE UNIQUE INDEX uq_projects_owner_default
|
||
ON projects (owner_user_id)
|
||
WHERE is_default = true
|
||
""")
|
||
|
||
|
||
def downgrade() -> None:
|
||
op.execute("DROP INDEX IF EXISTS uq_projects_owner_default")
|
||
op.drop_column("projects", "is_default")
|