From 08cf18537ba0aa445d88ed54955fd10026a9d94e Mon Sep 17 00:00:00 2001 From: Deep Koluguri Date: Sun, 5 Apr 2026 15:58:40 -0400 Subject: [PATCH] fixed --- backend/scripts/migrate-pg-to-sqlite.js | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/backend/scripts/migrate-pg-to-sqlite.js b/backend/scripts/migrate-pg-to-sqlite.js index 88059d8..ff029f3 100644 --- a/backend/scripts/migrate-pg-to-sqlite.js +++ b/backend/scripts/migrate-pg-to-sqlite.js @@ -100,6 +100,63 @@ async function tableExists(sqlite, name) { return Array.isArray(rows) && rows.length > 0; } +function colSetKey(cols) { + return [...cols].sort().join(','); +} + +/** Detect old/broken SQLite schemas (e.g. UNIQUE(group_id) only → one row per group). */ +async function groupMembersSqliteNeedsRepair(sqlite) { + const list = await sqlite.query(`PRAGMA index_list('group_members')`, { type: QueryTypes.SELECT }); + let hasGroupUser = false; + let hasGroupMemberNum = false; + let badGroupIdOnly = false; + + for (const row of list) { + const unique = row.unique === 1 || row.unique === true; + if (!unique) continue; + const info = await sqlite.query(`PRAGMA index_info('${String(row.name).replace(/'/g, "''")}')`, { + type: QueryTypes.SELECT, + }); + const cols = info + .slice() + .sort((a, b) => (a.seqno ?? 0) - (b.seqno ?? 0)) + .map((i) => i.name) + .filter(Boolean); + if (cols.length === 1 && cols[0] === 'group_id') { + badGroupIdOnly = true; + } + if (colSetKey(cols) === 'group_id,user_id') hasGroupUser = true; + if (colSetKey(cols) === 'group_id,member_number') hasGroupMemberNum = true; + } + + return badGroupIdOnly || !hasGroupUser || !hasGroupMemberNum; +} + +/** Must match `src/models/GroupMember.js` + Sequelize SQLite DDL (table is empty). */ +async function recreateGroupMembersTable(sqlite) { + await sqlite.query('DROP TABLE IF EXISTS `group_members`'); + await sqlite.query(` + CREATE TABLE \`group_members\` ( + \`id\` UUID PRIMARY KEY, + \`group_id\` UUID NOT NULL REFERENCES \`chit_groups\` (\`id\`) ON DELETE CASCADE ON UPDATE CASCADE, + \`user_id\` UUID NOT NULL REFERENCES \`users\` (\`id\`) ON DELETE CASCADE ON UPDATE CASCADE, + \`member_number\` INTEGER NOT NULL, + \`joined_date\` DATETIME NOT NULL, + \`status\` TEXT DEFAULT 'active', + \`total_paid\` DECIMAL(15,2) DEFAULT 0, + \`total_won\` DECIMAL(15,2) DEFAULT 0, + \`created_at\` DATETIME NOT NULL, + \`updated_at\` DATETIME NOT NULL + ) + `); + await sqlite.query( + 'CREATE UNIQUE INDEX `group_members_group_id_user_id` ON `group_members` (`group_id`, `user_id`)' + ); + await sqlite.query( + 'CREATE UNIQUE INDEX `group_members_group_id_member_number` ON `group_members` (`group_id`, `member_number`)' + ); +} + async function fetchPostgresTable(pg, table) { try { return await pg.query(`SELECT * FROM "${table}"`, { type: QueryTypes.SELECT }); @@ -187,6 +244,15 @@ async function main() { } console.log(' Done.\n'); + if (await groupMembersSqliteNeedsRepair(sqlite)) { + console.log( + '🔧 Fixing `group_members` SQLite schema (wrong UNIQUE on `group_id` or missing composite indexes).\n' + + ' Recreating empty table to match current Sequelize model…\n' + ); + await recreateGroupMembersTable(sqlite); + console.log(' Done.\n'); + } + console.log('📥 Copying from PostgreSQL → SQLite…\n'); for (const table of TABLES_ORDER) { const rows = await fetchPostgresTable(pg, table);