From 6771f9ed6f72726d645fb97aff12d36ae39ce976 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 15 Aug 2023 16:54:53 +0800 Subject: [PATCH] feat(cli): add rollback-to db action (#4329) * feat(cli): add rollback-to db action add rollback-to db action * chore: bump alteration timestamp bump alteration timestamp * chore: rename the alteration action name rename the alteration action name --- .../src/commands/database/alteration/index.ts | 111 +++++++++++------- .../src/commands/database/alteration/utils.ts | 11 ++ ...add-is-suspend-column-to-tenants-table.ts} | 0 3 files changed, 80 insertions(+), 42 deletions(-) rename packages/schemas/alterations/{next-1691979708-add-is-suspend-column-to-tenants-table.ts => next-1692088012-add-is-suspend-column-to-tenants-table.ts} (100%) diff --git a/packages/cli/src/commands/database/alteration/index.ts b/packages/cli/src/commands/database/alteration/index.ts index 8db472bfb..03a103243 100644 --- a/packages/cli/src/commands/database/alteration/index.ts +++ b/packages/cli/src/commands/database/alteration/index.ts @@ -12,7 +12,11 @@ import { import { consoleLog } from '../../../utils.js'; import type { AlterationFile } from './type.js'; -import { getAlterationFiles, getTimestampFromFilename } from './utils.js'; +import { + getAlterationFiles, + getTimestampFromFilename, + chooseRevertAlterationsByTimestamp, +} from './utils.js'; import { chooseAlterationsByVersion, chooseRevertAlterationsByVersion } from './version.js'; const importAlterationScript = async (filePath: string): Promise => { @@ -87,6 +91,21 @@ const deployAlteration = async ( consoleLog.info(`Run alteration ${filename} \`${action}()\` function succeeded`); }; +const revertAlterations = async (alterations: AlterationFile[], pool: DatabasePool) => { + consoleLog.info( + `Found ${alterations.length} alteration${conditionalString( + alterations.length > 1 && 's' + )} to revert` + ); + + // The await inside the loop is intended, alterations should run in order + // eslint-disable-next-line @silverhand/fp/no-mutating-methods + for (const alteration of alterations.slice().reverse()) { + // eslint-disable-next-line no-await-in-loop + await deployAlteration(pool, alteration, 'down'); + } +}; + const alteration: CommandModule = { command: ['alteration [target]', 'alt', 'alter'], describe: 'Perform database alteration', @@ -101,56 +120,64 @@ const alteration: CommandModule = describe: 'The target Logto version for alteration', type: 'string', }), + handler: async ({ action, target }) => { - if (action === 'list') { - const files = await getAlterationFiles(); + switch (action) { + case 'list': { + const files = await getAlterationFiles(); - for (const file of files) { - consoleLog.plain(file.filename); + for (const file of files) { + consoleLog.plain(file.filename); + } + + break; } - } else if (action === 'deploy') { - const pool = await createPoolFromConfig(); - const alterations = await chooseAlterationsByVersion( - await getAvailableAlterations(pool), - target - ); + case 'deploy': { + const pool = await createPoolFromConfig(); + const alterations = await chooseAlterationsByVersion( + await getAvailableAlterations(pool), + target + ); - consoleLog.info( - `Found ${alterations.length} alteration${conditionalString( - alterations.length > 1 && 's' - )} to deploy` - ); + consoleLog.info( + `Found ${alterations.length} alteration${conditionalString( + alterations.length > 1 && 's' + )} to deploy` + ); - // The await inside the loop is intended, alterations should run in order - for (const alteration of alterations) { - // eslint-disable-next-line no-await-in-loop - await deployAlteration(pool, alteration); + // The await inside the loop is intended, alterations should run in order + for (const alteration of alterations) { + // eslint-disable-next-line no-await-in-loop + await deployAlteration(pool, alteration); + } + + await pool.end(); + + break; } + case 'rollback': + case 'r': { + const pool = await createPoolFromConfig(); + const alterations = await chooseRevertAlterationsByVersion( + await getAvailableAlterations(pool, 'lte'), + target ?? '' + ); - await pool.end(); - } else if (['rollback', 'r'].includes(action)) { - const pool = await createPoolFromConfig(); - const alterations = await chooseRevertAlterationsByVersion( - await getAvailableAlterations(pool, 'lte'), - target ?? '' - ); - - consoleLog.info( - `Found ${alterations.length} alteration${conditionalString( - alterations.length > 1 && 's' - )} to revert` - ); - - // The await inside the loop is intended, alterations should run in order - // eslint-disable-next-line @silverhand/fp/no-mutating-methods - for (const alteration of alterations.slice().reverse()) { - // eslint-disable-next-line no-await-in-loop - await deployAlteration(pool, alteration, 'down'); + await revertAlterations(alterations, pool); + await pool.end(); + break; } + case 'rollback-to-timestamp': { + const pool = await createPoolFromConfig(); + const alterations = await chooseRevertAlterationsByTimestamp(target ?? ''); - await pool.end(); - } else { - consoleLog.fatal('Unsupported action'); + await revertAlterations(alterations, pool); + await pool.end(); + break; + } + default: { + consoleLog.fatal('Unsupported action'); + } } }, }; diff --git a/packages/cli/src/commands/database/alteration/utils.ts b/packages/cli/src/commands/database/alteration/utils.ts index b5faea162..09242b7dc 100644 --- a/packages/cli/src/commands/database/alteration/utils.ts +++ b/packages/cli/src/commands/database/alteration/utils.ts @@ -55,3 +55,14 @@ export const getAlterationFiles = async (): Promise => { .sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2)) .map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename })); }; + +export const chooseRevertAlterationsByTimestamp = async (target: string) => { + const files = await getAlterationFiles(); + const targetTimestamp = Number(target); + + if (Number.isNaN(targetTimestamp)) { + return []; + } + + return files.filter(({ filename }) => getTimestampFromFilename(filename) > targetTimestamp); +}; diff --git a/packages/schemas/alterations/next-1691979708-add-is-suspend-column-to-tenants-table.ts b/packages/schemas/alterations/next-1692088012-add-is-suspend-column-to-tenants-table.ts similarity index 100% rename from packages/schemas/alterations/next-1691979708-add-is-suspend-column-to-tenants-table.ts rename to packages/schemas/alterations/next-1692088012-add-is-suspend-column-to-tenants-table.ts