diff --git a/migrations/20260725010000_actors_socials_handle_lower_unique.js b/migrations/20260725010000_actors_socials_handle_lower_unique.js new file mode 100644 index 00000000..92d20eda --- /dev/null +++ b/migrations/20260725010000_actors_socials_handle_lower_unique.js @@ -0,0 +1,40 @@ +exports.up = async function(knex) { + const duplicates = await knex.raw(` + SELECT actor_id, platform, lower(handle) AS handle_lower, COUNT(*) AS count + FROM actors_socials + WHERE handle IS NOT NULL + GROUP BY actor_id, platform, lower(handle) + HAVING COUNT(*) > 1 + `); + + if (duplicates.rows.length > 0) { + console.log(`Deduping ${duplicates.rows.length} actors_socials handle group(s)`, duplicates.rows.map((row) => `${row.actor_id}/${row.platform}/${row.handle_lower} (${row.count})`).join(', ')); + } + + await knex.raw(` + DELETE FROM actors_socials + WHERE id IN ( + SELECT id FROM ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY actor_id, platform, lower(handle) + ORDER BY (handle = lower(handle)) ASC, created_at ASC + ) AS rn + FROM actors_socials + WHERE handle IS NOT NULL + ) ranked + WHERE rn > 1 + ); + + ALTER TABLE actors_socials DROP CONSTRAINT actors_social_actor_id_platform_handle_unique; + CREATE UNIQUE INDEX actors_socials_actor_id_platform_handle_lower_unique ON actors_socials (actor_id, platform, lower(handle)); + `); +}; + +exports.down = async function(knex) { + await knex.raw(` + DROP INDEX actors_socials_actor_id_platform_handle_lower_unique; + ALTER TABLE actors_socials ADD CONSTRAINT actors_social_actor_id_platform_handle_unique UNIQUE (actor_id, platform, handle); + `); +};