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:
@@ -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;
|
||||
Reference in New Issue
Block a user