fix(db): enforce global unique project names across creators

Replace per-creator name uniqueness with a global index among
non-retired projects, and reject cross-user duplicate creates with 409.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-20 09:10:55 +00:00
co-authored by Cursor
parent 638083cc74
commit 28478bdca5
6 changed files with 116 additions and 8 deletions
@@ -0,0 +1,38 @@
-- +goose Up
-- 全局项目名唯一:任意活跃项目不得重名(跨创建人)。
-- 替换 00056 的 per-creator 唯一索引。
-- Deduplicate globally by lower(trim(name)) among non-retired rows (keep lowest id).
WITH dups AS (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY lower(trim(name))
ORDER BY id ASC
) AS rn
FROM projects
WHERE length(trim(name)) > 0
AND COALESCE(status, 'active') <> 'demo_retired'
)
UPDATE projects p
SET name = p.name || ' (dup-' || p.id || ')',
updated_at = now()
FROM dups d
WHERE p.id = d.id AND d.rn > 1;
DROP INDEX IF EXISTS uq_projects_creator_name;
CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_name_global
ON projects (lower(trim(name)))
WHERE length(trim(name)) > 0
AND COALESCE(status, 'active') <> 'demo_retired';
COMMENT ON INDEX uq_projects_name_global IS
'Global unique project name among non-retired projects (case-insensitive, trimmed).';
-- +goose Down
DROP INDEX IF EXISTS uq_projects_name_global;
CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_creator_name
ON projects (creator_user_id, lower(trim(name)))
WHERE creator_user_id IS NOT NULL AND creator_user_id > 0
AND length(trim(name)) > 0;