diff --git a/scripts/package.json b/scripts/package.json index 0c563c71..f6894bfe 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -6,6 +6,7 @@ "scripts": { "hello": "tsx ./src/hello.ts", "seed": "tsx ./src/seed.ts", + "remove-chat": "tsx ./src/remove-chat.ts", "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { diff --git a/scripts/post-merge.sh b/scripts/post-merge.sh index 4f0889d6..396547d3 100644 --- a/scripts/post-merge.sh +++ b/scripts/post-merge.sh @@ -1,6 +1,11 @@ #!/bin/bash set -e pnpm install --frozen-lockfile +# Task #511: idempotent destructive cleanup of the removed Chat feature +# (drops chat tables, deletes chat-related notifications, removes the chat +# app catalog row + link rows). Runs before push so push won't see chat +# tables in the live DB that aren't in the schema. Safe to run repeatedly. +pnpm --filter scripts run remove-chat pnpm --filter db run push-force # Idempotent: ensure the default groups exist and existing users are mapped # (Admins, Tx, Everyone). Safe to run repeatedly. diff --git a/scripts/src/remove-chat.ts b/scripts/src/remove-chat.ts new file mode 100644 index 00000000..e1c7af03 --- /dev/null +++ b/scripts/src/remove-chat.ts @@ -0,0 +1,80 @@ +import { pool } from "@workspace/db"; + +async function tableExists(table: string): Promise { + const result = await pool.query( + `SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = $1`, + [table], + ); + return (result.rowCount ?? 0) > 0; +} + +async function main(): Promise { + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + for (const table of [ + "message_reads", + "messages", + "conversation_participants", + "conversations", + ]) { + if (await tableExists(table)) { + await client.query(`DROP TABLE IF EXISTS "${table}" CASCADE`); + console.log(`[remove-chat] Dropped table ${table}`); + } + } + + if (await tableExists("notifications")) { + const r = await client.query( + `DELETE FROM notifications + WHERE related_type = 'conversation' OR type = 'chat'`, + ); + if ((r.rowCount ?? 0) > 0) { + console.log( + `[remove-chat] Removed ${r.rowCount} chat-related notification row(s)`, + ); + } + } + + if (await tableExists("apps")) { + const idRes = await client.query( + `SELECT id FROM apps WHERE slug = 'chat'`, + ); + const chatAppId = idRes.rows[0]?.id as number | undefined; + if (chatAppId !== undefined) { + for (const link of [ + "user_app_orders", + "app_opens", + "app_permissions", + "group_apps", + ]) { + if (await tableExists(link)) { + await client.query(`DELETE FROM "${link}" WHERE app_id = $1`, [ + chatAppId, + ]); + } + } + await client.query(`DELETE FROM apps WHERE id = $1`, [chatAppId]); + console.log( + `[remove-chat] Removed chat app (id=${chatAppId}) and link rows`, + ); + } + } + + await client.query("COMMIT"); + } catch (err) { + await client.query("ROLLBACK"); + throw err; + } finally { + client.release(); + } +} + +main() + .then(() => pool.end()) + .catch(async (err) => { + console.error("[remove-chat] failed:", err); + await pool.end(); + process.exit(1); + });