Guard yarn database:migrate:prod (#19008)
## Motivations A lot of self hosters hands up using the `yarn database:migrated:prod` either manually or through AI assisted debug while they try to upgrade an instance while their workspace is still blocked in a previous one Leading to their whole database permanent corruption ## What happened Replaced the direct call the the typeorm cli to a command calling it programmatically, adding a layer of security in case a workspace seems to be blocked in a previous version than the one just before the one being installed ( e.g 1.0 when you try to upgrade from 1.1 to 1.2 ) For our cloud we still need a way to bypass this security explaining the -f flag ## Remark Centralized this logic and refactored creating new services `WorkspaceVersionService` and `CoreEngineVersionService` that will become useful for the upcoming upgrade refactor Related to https://github.com/twentyhq/twenty-infra/pull/529
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
|
||||
|
||||
export const camelToSnakeCase = (str: string) =>
|
||||
str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||
|
||||
export const performQuery = async <T = unknown>(
|
||||
query: string,
|
||||
consoleDescription: string,
|
||||
withLog = true,
|
||||
ignoreAlreadyExistsError = false,
|
||||
) => {
|
||||
try {
|
||||
const result = await rawDataSource.query<T>(query);
|
||||
|
||||
if (withLog) {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log(`Performed '${consoleDescription}' successfully`);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
let message = '';
|
||||
|
||||
if (ignoreAlreadyExistsError && `${err}`.includes('already exists')) {
|
||||
message = `Performed '${consoleDescription}' successfully`;
|
||||
} else {
|
||||
message = `Failed to perform '${consoleDescription}': ${err}`;
|
||||
}
|
||||
if (withLog) {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
|
||||
|
||||
import { camelToSnakeCase, performQuery } from './setup-db-utils';
|
||||
|
||||
rawDataSource
|
||||
.initialize()
|
||||
.then(async () => {
|
||||
await performQuery(
|
||||
'CREATE SCHEMA IF NOT EXISTS "public"',
|
||||
'create schema "public"',
|
||||
);
|
||||
await performQuery(
|
||||
'CREATE SCHEMA IF NOT EXISTS "core"',
|
||||
'create schema "core"',
|
||||
);
|
||||
|
||||
await performQuery(
|
||||
'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"',
|
||||
'create extension "uuid-ossp"',
|
||||
);
|
||||
|
||||
await performQuery(
|
||||
'CREATE EXTENSION IF NOT EXISTS "unaccent"',
|
||||
'create extension "unaccent"',
|
||||
);
|
||||
|
||||
await performQuery(
|
||||
`CREATE OR REPLACE FUNCTION public.unaccent_immutable(input text)
|
||||
RETURNS text
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
AS $$
|
||||
SELECT public.unaccent('public.unaccent'::regdictionary, input)
|
||||
$$;`,
|
||||
'create immutable unaccent wrapper function',
|
||||
);
|
||||
|
||||
// We paused the work on FDW
|
||||
if (process.env.IS_FDW_ENABLED !== 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
await performQuery(
|
||||
'CREATE EXTENSION IF NOT EXISTS "postgres_fdw"',
|
||||
'create extension "postgres_fdw"',
|
||||
);
|
||||
|
||||
await performQuery(
|
||||
'CREATE EXTENSION IF NOT EXISTS "wrappers"',
|
||||
'create extension "wrappers"',
|
||||
);
|
||||
|
||||
await performQuery(
|
||||
'CREATE EXTENSION IF NOT EXISTS "mysql_fdw"',
|
||||
'create extension "mysql_fdw"',
|
||||
);
|
||||
|
||||
const supabaseWrappers = [
|
||||
'airtable',
|
||||
'bigQuery',
|
||||
'clickHouse',
|
||||
'firebase',
|
||||
'logflare',
|
||||
's3',
|
||||
'stripe',
|
||||
]; // See https://supabase.github.io/wrappers/
|
||||
|
||||
for (const wrapper of supabaseWrappers) {
|
||||
if (await checkForeignDataWrapperExists(`${wrapper.toLowerCase()}_fdw`)) {
|
||||
continue;
|
||||
}
|
||||
await performQuery(
|
||||
`
|
||||
CREATE FOREIGN DATA WRAPPER "${wrapper.toLowerCase()}_fdw"
|
||||
HANDLER "${camelToSnakeCase(wrapper)}_fdw_handler"
|
||||
VALIDATOR "${camelToSnakeCase(wrapper)}_fdw_validator";
|
||||
`,
|
||||
`create ${wrapper} "wrappers"`,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Error during Data Source initialization:', err);
|
||||
});
|
||||
|
||||
async function checkForeignDataWrapperExists(
|
||||
wrapperName: string,
|
||||
): Promise<boolean> {
|
||||
const result = await rawDataSource.query(
|
||||
`SELECT 1 FROM pg_foreign_data_wrapper WHERE fdwname = $1`,
|
||||
[wrapperName],
|
||||
);
|
||||
|
||||
return result.length > 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
|
||||
|
||||
import { performQuery } from './setup-db-utils';
|
||||
|
||||
async function dropSchemasSequentially() {
|
||||
try {
|
||||
await rawDataSource.initialize();
|
||||
|
||||
// Fetch all schemas excluding the ones we want to keep
|
||||
const schemas =
|
||||
(await performQuery<{ schema_name: string }[]>(
|
||||
`
|
||||
SELECT n.nspname AS "schema_name"
|
||||
FROM pg_catalog.pg_namespace n
|
||||
WHERE n.nspname !~ '^pg_'
|
||||
AND n.nspname <> 'information_schema'
|
||||
AND n.nspname NOT IN ('metric_helpers', 'user_management', 'public')
|
||||
`,
|
||||
'Fetching schemas...',
|
||||
)) ?? [];
|
||||
|
||||
const batchSize = 10;
|
||||
|
||||
for (let i = 0; i < schemas.length; i += batchSize) {
|
||||
const batch = schemas.slice(i, i + batchSize);
|
||||
|
||||
await Promise.all(
|
||||
batch.map((schema) =>
|
||||
performQuery(
|
||||
`DROP SCHEMA IF EXISTS "${schema.schema_name}" CASCADE;`,
|
||||
`Dropping schema ${schema.schema_name}...`,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('All schemas dropped successfully.');
|
||||
} catch (err) {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error('Error during schema dropping:', err);
|
||||
}
|
||||
}
|
||||
|
||||
dropSchemasSequentially();
|
||||
Reference in New Issue
Block a user