Add migration ci check (#8867)
Fixes #8865 --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
21
.github/workflows/ci-server.yaml
vendored
21
.github/workflows/ci-server.yaml
vendored
@ -75,9 +75,30 @@ jobs:
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
npx nx run twenty-server:database:init:prod
|
||||
npx nx run twenty-server:database:migrate:prod
|
||||
- name: Worker / Run
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: npx nx run twenty-server:worker:ci
|
||||
- name: Server / Check for Pending Migrations
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
METADATA_MIGRATION_OUTPUT=$(npx nx run twenty-server:typeorm migration:generate metadata-migration-check -d src/database/typeorm/metadata/metadata.datasource.ts || true)
|
||||
|
||||
CORE_MIGRATION_OUTPUT=$(npx nx run twenty-server:typeorm migration:generate core-migration-check -d src/database/typeorm/core/core.datasource.ts || true)
|
||||
|
||||
METADATA_MIGRATION_FILE=$(ls packages/twenty-server/*metadata-migration-check.ts 2>/dev/null || echo "")
|
||||
CORE_MIGRATION_FILE=$(ls packages/twenty-server/*core-migration-check.ts 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$METADATA_MIGRATION_FILE" ] || [ -n "$CORE_MIGRATION_FILE" ]; then
|
||||
echo "::error::Unexpected migration files were generated. Please create a proper migration manually."
|
||||
echo "$METADATA_MIGRATION_OUTPUT"
|
||||
echo "$CORE_MIGRATION_OUTPUT"
|
||||
|
||||
rm -f packages/twenty-server/*metadata-migration-check.ts packages/twenty-server/*core-migration-check.ts
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
server-test:
|
||||
timeout-minutes: 30
|
||||
|
||||
@ -11,9 +11,12 @@ export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
|
||||
type: 'postgres',
|
||||
logging: ['error'],
|
||||
schema: 'core',
|
||||
entities: [
|
||||
`${isJest ? '' : 'dist/'}src/engine/core-modules/**/*.entity{.ts,.js}`,
|
||||
],
|
||||
entities:
|
||||
process.env.IS_BILLING_ENABLED === 'true'
|
||||
? [`${isJest ? '' : 'dist/'}src/engine/core-modules/**/*.entity{.ts,.js}`]
|
||||
: [
|
||||
`${isJest ? '' : 'dist/'}src/engine/core-modules/**/!(billing-*).entity{.ts,.js}`,
|
||||
],
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
migrationsTableName: '_typeorm_migrations',
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveBillingFKWithCore1733753649142
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveBillingFKWithCore1733753649142';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_599121a93d8177b5d713b941982"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingSubscription" DROP CONSTRAINT "FK_4abfb70314c18da69e1bee1954d"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingCustomer" DROP CONSTRAINT "FK_53c2ef50e9611082f83d760897d"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingCustomer" ADD CONSTRAINT "FK_53c2ef50e9611082f83d760897d" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingSubscription" ADD CONSTRAINT "FK_4abfb70314c18da69e1bee1954d" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_599121a93d8177b5d713b941982" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,171 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class SsoMissingMigration1733318043626 implements MigrationInterface {
|
||||
name = 'SsoMissingMigration1733318043626';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP CONSTRAINT "FK_workspaceId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."apptoken_unique_invitation_by_user_workspace"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."workspace_subdomain_unique_index"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."appToken" DROP CONSTRAINT "userIdIsNullWhenTypeIsInvitation"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."appToken" DROP CONSTRAINT "userIdNotNullWhenTypeIsNotInvitation"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP CONSTRAINT "CHK_OIDC"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP CONSTRAINT "CHK_SAML"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "name" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP COLUMN "status"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."workspaceSSOIdentityProvider_status_enum" AS ENUM('Active', 'Inactive', 'Error')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD "status" "core"."workspaceSSOIdentityProvider_status_enum" NOT NULL DEFAULT 'Active'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."idp_type_enum" RENAME TO "idp_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."workspaceSSOIdentityProvider_type_enum" AS ENUM('OIDC', 'SAML')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" TYPE "core"."workspaceSSOIdentityProvider_type_enum" USING "type"::"text"::"core"."workspaceSSOIdentityProvider_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" SET DEFAULT 'OIDC'`,
|
||||
);
|
||||
await queryRunner.query(`DROP TYPE "core"."idp_type_enum_old"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."workspace_activationStatus_enum" RENAME TO "workspace_activationStatus_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."workspace_activationstatus_enum" AS ENUM('ONGOING_CREATION', 'PENDING_CREATION', 'ACTIVE', 'INACTIVE')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" TYPE "core"."workspace_activationstatus_enum" USING "activationStatus"::"text"::"core"."workspace_activationstatus_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."workspace_activationStatus_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isGoogleAuthEnabled" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isPasswordAuthEnabled" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isMicrosoftAuthEnabled" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "FK_bc8d8855198de1fbc32fba8df93" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP CONSTRAINT "FK_bc8d8855198de1fbc32fba8df93"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isMicrosoftAuthEnabled" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isPasswordAuthEnabled" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "isGoogleAuthEnabled" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."workspace_activationStatus_enum_old" AS ENUM('PENDING_CREATION', 'ONGOING_CREATION', 'ACTIVE', 'INACTIVE')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" TYPE "core"."workspace_activationStatus_enum_old" USING "activationStatus"::"text"::"core"."workspace_activationStatus_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."workspace_activationstatus_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."workspace_activationStatus_enum_old" RENAME TO "workspace_activationStatus_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."idp_type_enum_old" AS ENUM('OIDC', 'SAML')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" TYPE "core"."idp_type_enum_old" USING "type"::"text"::"core"."idp_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "type" SET DEFAULT 'OIDC'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."workspaceSSOIdentityProvider_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."idp_type_enum_old" RENAME TO "idp_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" DROP COLUMN "status"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."workspaceSSOIdentityProvider_status_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD "status" character varying NOT NULL DEFAULT 'Active'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ALTER COLUMN "name" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "CHK_SAML" CHECK ((((type = 'SAML'::core.idp_type_enum) AND ("ssoURL" IS NOT NULL) AND (certificate IS NOT NULL)) OR (type = 'OIDC'::core.idp_type_enum)))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "CHK_OIDC" CHECK ((((type = 'OIDC'::core.idp_type_enum) AND ("clientID" IS NOT NULL) AND ("clientSecret" IS NOT NULL)) OR (type = 'SAML'::core.idp_type_enum)))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."appToken" ADD CONSTRAINT "userIdNotNullWhenTypeIsNotInvitation" CHECK (((type = 'INVITATION_TOKEN'::text) OR ("userId" IS NOT NULL)))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."appToken" ADD CONSTRAINT "userIdIsNullWhenTypeIsInvitation" CHECK (((type <> 'INVITATION_TOKEN'::text) OR ("userId" IS NULL)))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "workspace_subdomain_unique_index" ON "core"."workspace" ("subdomain") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "apptoken_unique_invitation_by_user_workspace" ON "core"."appToken" ("workspaceId") WHERE (type = 'INVITATION_TOKEN'::text)`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspaceSSOIdentityProvider" ADD CONSTRAINT "FK_workspaceId" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class MissingShortcutMigration1733318004066
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'MissingShortcutMigration1733318004066';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "metadata"."IDX_objectMetadata_shortcut_upper_workspace"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_objectMetadata_shortcut_upper_workspace" ON "metadata"."objectMetadata" ("workspaceId") `,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,6 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
@ -17,7 +15,6 @@ import {
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Entity({ name: 'billingCustomer', schema: 'core' })
|
||||
@ObjectType('billingCustomer')
|
||||
@ -39,12 +36,6 @@ export class BillingCustomer {
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.billingCustomers, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
workspace: Relation<Workspace>;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ import {
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@Entity({ name: 'billingEntitlement', schema: 'core' })
|
||||
@ObjectType('billingEntitlement')
|
||||
@Unique('IndexOnFeatureKeyAndWorkspaceIdUnique', ['key', 'workspaceId'])
|
||||
@ -33,12 +32,6 @@ export class BillingEntitlement {
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.billingEntitlements, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
workspace: Relation<Workspace>;
|
||||
|
||||
@Column({ nullable: false })
|
||||
stripeCustomerId: string;
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entitie
|
||||
import { BillingSubscriptionCollectionMethod } from 'src/engine/core-modules/billing/enums/billing-subscription-collection-method.enum';
|
||||
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
|
||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
registerEnumType(SubscriptionStatus, { name: 'SubscriptionStatus' });
|
||||
registerEnumType(SubscriptionInterval, { name: 'SubscriptionInterval' });
|
||||
@ -41,12 +40,6 @@ export class BillingSubscription {
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.billingSubscriptions, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
workspace: Relation<Workspace>;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -67,8 +67,8 @@ export class KeyValuePair {
|
||||
value: JSON;
|
||||
|
||||
@Field(() => String)
|
||||
@Column({ nullable: false, type: 'text' })
|
||||
textValueDeprecated: string;
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
textValueDeprecated: string | null;
|
||||
|
||||
@Field(() => KeyValuePairType)
|
||||
@Column({
|
||||
|
||||
@ -119,24 +119,6 @@ export class Workspace {
|
||||
})
|
||||
activationStatus: WorkspaceActivationStatus;
|
||||
|
||||
@OneToMany(
|
||||
() => BillingSubscription,
|
||||
(billingSubscription) => billingSubscription.workspace,
|
||||
)
|
||||
billingSubscriptions: Relation<BillingSubscription[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => BillingCustomer,
|
||||
(billingCustomer) => billingCustomer.workspace,
|
||||
)
|
||||
billingCustomers: Relation<BillingCustomer[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => BillingEntitlement,
|
||||
(billingEntitlement) => billingEntitlement.workspace,
|
||||
)
|
||||
billingEntitlements: Relation<BillingEntitlement[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => PostgresCredentials,
|
||||
(postgresCredentials) => postgresCredentials.workspace,
|
||||
|
||||
Reference in New Issue
Block a user