diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 9a5607741..806580597 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -78,7 +78,7 @@ jobs: - name: Extract working-directory: tests run: | - npm run cli init -- -p ../logto --db postgres://postgres:postgres@localhost:5432/postgres --no-oc --du ../logto.tar.gz + npm run cli init -- -p ../logto --db postgres://postgres:postgres@localhost:5432/postgres --du ../logto.tar.gz - name: Check and add mock connectors working-directory: tests diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb39e9971..95bb4c588 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -85,19 +85,73 @@ jobs: with: context: . - main-alteration-sequence: + main-alteration: runs-on: ubuntu-latest steps: + # ** Checkout fresh and alteration ref ** - uses: actions/checkout@v3 with: fetch-depth: 2 + path: ./fresh + + - uses: actions/checkout@v3 + with: + ref: v1.0.0-beta.11 + path: ./alteration + # ** End ** + + - name: Copy lockfile # Make setup workflow happy + run: cp ./fresh/pnpm-lock.yaml ./ - name: Setup Node and pnpm uses: silverhand-io/actions-node-pnpm-run-steps@v2 + with: + run-install: false + + # ** Prepack packages ** + - name: Prepack fresh + working-directory: ./fresh + run: pnpm i && pnpm prepack + + - name: Prepack alteration + working-directory: ./alteration + run: pnpm i && pnpm prepack + # ** End ** - - name: Build - run: pnpm prepack - - name: Check alteration sequence + working-directory: ./fresh run: node .scripts/check-alterations-sequence.js + + - name: Setup Postgres + uses: ikalnytskyi/action-setup-postgres@v4 + + # ** Setup up-to-date databases and compare (test `up`) ** + - name: Setup fresh database + working-directory: ./fresh + run: pnpm cli db seed + env: + DB_URL: postgres://postgres:postgres@localhost:5432/fresh + + - name: Setup alteration database + working-directory: ./alteration + run: | + cd packages/cli + pnpm start db seed + env: + DB_URL: postgres://postgres:postgres@localhost:5432/alteration + + - name: Run alteration scripts + working-directory: ./fresh + run: pnpm cli db alt deploy next + env: + DB_URL: postgres://postgres:postgres@localhost:5432/alteration + + - name: Compare manifests + working-directory: ./fresh + run: node .scripts/compare-database.js fresh alteration + # ** End ** + + # ** Setup old databases and compare (test `down`) ** + # TBD + # ** End ** diff --git a/.scripts/check-alterations-sequence.js b/.scripts/check-alterations-sequence.js index 5db435174..0e9d5ed08 100644 --- a/.scripts/check-alterations-sequence.js +++ b/.scripts/check-alterations-sequence.js @@ -1,7 +1,6 @@ /** - * This script runs a task to check alteration files seqence: - * new files should come last - * + * This script runs a task to check alteration files sequence: + * Newest files should come last. */ import { execSync } from "child_process"; diff --git a/.scripts/compare-database.js b/.scripts/compare-database.js new file mode 100644 index 000000000..b8e9386c1 --- /dev/null +++ b/.scripts/compare-database.js @@ -0,0 +1,84 @@ +import pg from 'pg'; +import assert from 'node:assert'; + +const omit = (object, ...keys) => Object.fromEntries(Object.entries(object).filter(([key]) => !keys.includes(key))); +const omitArray = (arrayOfObjects, ...keys) => arrayOfObjects.map((value) => omit(value, ...keys)); + +const schema = 'public'; + +const queryDatabaseManifest = async (database) => { + const pool = new pg.Pool({ database, user: 'postgres', password: 'postgres' }); + + const { rows: tables } = await pool.query(/* sql */` + select * + from information_schema.tables + where table_schema = '${schema}' + order by table_name asc; + `); + + const { rows: columns } = await pool.query(/* sql */` + select * + from information_schema.columns + where table_schema = '${schema}' + order by table_name, column_name asc; + `); + + const { rows: enums } = await pool.query(/* sql */` + select pg_type.typname, pg_enum.enumlabel + from pg_type + join pg_enum + on pg_enum.enumtypid = pg_type.oid + order by pg_type.typname, pg_enum.enumlabel asc; + `); + + const { rows: constraints } = await pool.query(/* sql */` + select conrelid::regclass AS table, con.*, pg_get_constraintdef(con.oid) + from pg_catalog.pg_constraint con + inner join pg_catalog.pg_class rel + on rel.oid = con.conrelid + inner join pg_catalog.pg_namespace nsp + on nsp.oid = connamespace + where nsp.nspname = 'public' + order by conname asc; + `); + + const { rows: indexes } = await pool.query(/* sql */` + select * + from pg_indexes + where schemaname='${schema}' + order by tablename, indexname asc; + `); + + return { + tables: omitArray(tables, 'table_catalog'), + columns: omitArray(columns, 'table_catalog', 'udt_catalog', 'ordinal_position', 'dtd_identifier'), + enums, + constraints: omitArray( + constraints, + 'oid', + 'connamespace', + 'conrelid', + 'contypid', + 'conindid', + 'conparentid', + 'confrelid', + 'conkey', + 'confkey', + 'conpfeqop', + 'conppeqop', + 'conffeqop', + 'confdelsetcols', + 'conexclop', + ), + indexes, + }; +}; + +const [,, database1, database2] = process.argv; + +console.log('Compare database manifest between', database1, 'and', database2) + +assert.deepStrictEqual( + await queryDatabaseManifest(database1), + await queryDatabaseManifest(database2), +); diff --git a/package.json b/package.json index 09c797854..31f8f2935 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "@commitlint/cli": "^17.0.0", "@commitlint/config-conventional": "^17.0.0", "@commitlint/types": "^17.0.0", + "@types/pg": "^8.6.6", "husky": "^8.0.0", + "pg": "^8.8.0", "typescript": "^4.9.4" }, "engines": { diff --git a/packages/schemas/alterations/next-1673882867-fix-alteration-issues.ts b/packages/schemas/alterations/next-1673882867-fix-alteration-issues.ts new file mode 100644 index 000000000..d9080a190 --- /dev/null +++ b/packages/schemas/alterations/next-1673882867-fix-alteration-issues.ts @@ -0,0 +1,41 @@ +import { sql } from 'slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + alter table roles_scopes + drop constraint roles_scopes_role_id_fkey; + `); + await pool.query(sql` + alter table users_roles + drop constraint users_roles_role_id_fkey; + `); + await pool.query(sql`drop index roles_pkey;`); + await pool.query(sql` + alter table roles + add primary key (id); + `); + + await pool.query(sql` + alter table roles_scopes + drop constraint roles_permissison_pkey, + add primary key (role_id, scope_id); + `); + + await pool.query(sql` + alter table users_roles + add foreign key (role_id) references roles (id) on update cascade on delete cascade; + `); + await pool.query(sql` + alter table roles_scopes + add foreign key (role_id) references roles (id) on update cascade on delete cascade; + `); + }, + down: async (pool) => { + throw new Error('Not implemented'); + }, +}; + +export default alteration; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c6aa9a2d..089418edb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,9 @@ importers: '@commitlint/config-conventional': ^17.0.0 '@commitlint/types': ^17.0.0 '@logto/cli': ^1.0.0-beta.10 + '@types/pg': ^8.6.6 husky: ^8.0.0 + pg: ^8.8.0 typescript: ^4.9.4 dependencies: '@logto/cli': link:packages/cli @@ -18,7 +20,9 @@ importers: '@commitlint/cli': 17.0.0 '@commitlint/config-conventional': 17.0.0 '@commitlint/types': 17.0.0 + '@types/pg': 8.6.6 husky: 8.0.1 + pg: 8.8.0 typescript: 4.9.4 packages/cli: @@ -4084,6 +4088,14 @@ packages: pg-types: 2.2.0 dev: false + /@types/pg/8.6.6: + resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + dependencies: + '@types/node': 18.11.18 + pg-protocol: 1.5.0 + pg-types: 2.2.0 + dev: true + /@types/pluralize/0.0.29: resolution: {integrity: sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==} dev: true @@ -11558,7 +11570,6 @@ packages: pg: '>=8.0' dependencies: pg: 8.8.0 - dev: false /pg-protocol/1.5.0: resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==} @@ -11630,7 +11641,6 @@ packages: pg-protocol: 1.5.0 pg-types: 2.2.0 pgpass: 1.0.4 - dev: false /pgpass/1.0.4: resolution: {integrity: sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==}