Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add migration ci check #8867

Merged
merged 14 commits into from
Dec 9, 2024
19 changes: 19 additions & 0 deletions .github/workflows/ci-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ jobs:
- 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ export const seedWorkspaces = async (
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
activationStatus: WorkspaceActivationStatus.ACTIVE,
},
[SEED_ACME_WORKSPACE_ID]: {
id: workspaceId,
displayName: 'Acme',
domainName: 'acme.dev',
subdomain: 'acme',
inviteHash: 'acme.dev-invite-hash',
logo: 'https://logos-world.net/wp-content/uploads/2022/05/Acme-Logo-700x394.png',
activationStatus: WorkspaceActivationStatus.ACTIVE,
},
// [SEED_ACME_WORKSPACE_ID]: {
AMoreaux marked this conversation as resolved.
Show resolved Hide resolved
// id: workspaceId,
// displayName: 'Acme',
// domainName: 'acme.dev',
// subdomain: 'acme',
// inviteHash: 'acme.dev-invite-hash',
// logo: 'https://logos-world.net/wp-content/uploads/2022/05/Acme-Logo-700x394.png',
// activationStatus: WorkspaceActivationStatus.ACTIVE,
// },
};

await workspaceDataSource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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"."keyValuePair" ALTER COLUMN "textValueDeprecated" SET NOT NULL`,
);
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`,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two constraints are usefull and shouldn't be dropped:

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)))`,
    );
    ```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those ones too:

   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)))`,
    );

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion offline, let's remove those for now since typeorm does not handle Check annotation with expressions and take time to find a better implementation later

}

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"."keyValuePair" ALTER COLUMN "textValueDeprecated" 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`,
);
}
}
Original file line number Diff line number Diff line change
@@ -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") `,
);
}
}
Loading