From ba44eb5fc643f388fcad6be1b4bdab9b31fa4ae9 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Thu, 19 Jan 2023 20:27:01 +0800 Subject: [PATCH] refactor(schemas): init tenant sqls --- .../cli/src/commands/database/seed/index.ts | 45 +++++- .../next-1674032099-multi-tenancy.ts | 140 ++++++++++++++++++ packages/schemas/package.json | 2 + packages/schemas/src/gen/index.ts | 4 +- packages/schemas/src/models/tenants.ts | 1 + packages/schemas/src/seeds/application.ts | 2 + packages/schemas/src/seeds/resource.ts | 2 + packages/schemas/src/seeds/roles.ts | 3 + packages/schemas/src/seeds/scope.ts | 2 + packages/schemas/src/seeds/setting.ts | 2 + .../schemas/src/seeds/sign-in-experience.ts | 3 + packages/schemas/src/seeds/tenant.ts | 2 + .../{logto_configs.sql => _logto_configs.sql} | 0 packages/schemas/tables/applications.sql | 7 + .../schemas/tables/applications_roles.sql | 15 ++ packages/schemas/tables/connectors.sql | 5 + packages/schemas/tables/custom_phrases.sql | 10 +- packages/schemas/tables/logs.sql | 20 ++- .../schemas/tables/oidc_model_instances.sql | 34 +++-- packages/schemas/tables/passcodes.sql | 20 +-- packages/schemas/tables/resources.sql | 20 +-- packages/schemas/tables/roles.sql | 14 +- packages/schemas/tables/roles_scopes.sql | 15 ++ packages/schemas/tables/rolesapplications.sql | 5 - packages/schemas/tables/scopes.sql | 16 +- packages/schemas/tables/scopesroles.sql | 5 - packages/schemas/tables/settings.sql | 11 +- .../schemas/tables/sign_in_experiences.sql | 5 + packages/schemas/tables/users.sql | 11 +- packages/schemas/tables/users_roles.sql | 14 ++ packages/schemas/tables/usersroles.sql | 5 - pnpm-lock.yaml | 15 +- 32 files changed, 362 insertions(+), 93 deletions(-) create mode 100644 packages/schemas/alterations/next-1674032099-multi-tenancy.ts create mode 100644 packages/schemas/src/seeds/tenant.ts rename packages/schemas/tables/{logto_configs.sql => _logto_configs.sql} (100%) create mode 100644 packages/schemas/tables/applications_roles.sql create mode 100644 packages/schemas/tables/roles_scopes.sql delete mode 100644 packages/schemas/tables/rolesapplications.sql delete mode 100644 packages/schemas/tables/scopesroles.sql create mode 100644 packages/schemas/tables/users_roles.sql delete mode 100644 packages/schemas/tables/usersroles.sql diff --git a/packages/cli/src/commands/database/seed/index.ts b/packages/cli/src/commands/database/seed/index.ts index 2ba55e6b3..35243adfe 100644 --- a/packages/cli/src/commands/database/seed/index.ts +++ b/packages/cli/src/commands/database/seed/index.ts @@ -13,7 +13,7 @@ import { managementResourceScope, defaultRoleScopeRelation, } from '@logto/schemas'; -import { Hooks } from '@logto/schemas/models'; +import { Hooks, Tenants } from '@logto/schemas/models'; import chalk from 'chalk'; import type { DatabasePool, DatabaseTransactionConnection } from 'slonik'; import { sql } from 'slonik'; @@ -33,6 +33,31 @@ import { getLatestAlterationTimestamp } from '../alteration/index.js'; import { getAlterationDirectory } from '../alteration/utils.js'; import { oidcConfigReaders } from './oidc-config.js'; +const getExplicitOrder = (query: string) => { + const matched = /\/\*\s*init_order\s*=\s*(\d+)\s*\*\//.exec(query)?.[1]; + + return matched ? Number(matched) : undefined; +}; + +const compareQuery = ([t1, q1]: [string, string], [t2, q2]: [string, string]) => { + const o1 = getExplicitOrder(q1); + const o2 = getExplicitOrder(q2); + + if (o1 === undefined && o2 === undefined) { + return t1.localeCompare(t2); + } + + if (o1 === undefined) { + return 1; + } + + if (o2 === undefined) { + return -1; + } + + return o1 - o2; +}; + const createTables = async (connection: DatabaseTransactionConnection) => { const tableDirectory = getPathInModule('@logto/schemas', 'tables'); const directoryFiles = await readdir(tableDirectory); @@ -44,16 +69,19 @@ const createTables = async (connection: DatabaseTransactionConnection) => { ]) ); - // Await in loop is intended for better error handling - for (const [, query] of queries) { + console.log(Tenants.raw, getExplicitOrder(Tenants.raw)); + + const allQueries: Array<[string, string]> = [ + [Hooks.tableName, Hooks.raw], + [Tenants.tableName, Tenants.raw], + ...queries, + ]; + const sorted = allQueries.slice().sort(compareQuery); + + for (const [, query] of sorted) { // eslint-disable-next-line no-await-in-loop await connection.query(sql`${raw(query)}`); } - - for (const table of [Hooks]) { - // eslint-disable-next-line no-await-in-loop - await connection.query(sql`${raw(table.raw)}`); - } }; const seedTables = async (connection: DatabaseTransactionConnection, latestTimestamp: number) => { @@ -133,6 +161,7 @@ export const seedByPool = async (pool: DatabasePool, type: SeedChoice) => { ); } + await createTables(connection); await oraPromise(createTables(connection), { text: 'Create tables', prefixText: chalk.blue('[info]'), diff --git a/packages/schemas/alterations/next-1674032099-multi-tenancy.ts b/packages/schemas/alterations/next-1674032099-multi-tenancy.ts new file mode 100644 index 000000000..a2e8d17f5 --- /dev/null +++ b/packages/schemas/alterations/next-1674032099-multi-tenancy.ts @@ -0,0 +1,140 @@ +import { conditionalString } from '@silverhand/essentials'; +import { sql } from 'slonik'; +import { raw } from 'slonik-sql-tag-raw'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const getId = (value: string) => sql.identifier([value]); +const tenantId = sql.identifier(['tenant_id']); +const defaultTenantId = 'default'; + +// [table name, primary key array] +type TableInfo = [string, string[]]; + +const tables: TableInfo[] = [ + ['applications', ['id']], + ['connectors', ['id']], + ['custom_phrases', ['language_tag']], + ['logs', ['id']], + ['oidc_model_instances', ['model_name', 'id']], + ['passcodes', ['id']], + ['resources', ['id']], + ['roles', ['id']], + ['roles_scopes', ['role_id', 'scope_id']], + ['scopes', ['id']], + ['settings', ['id']], + ['sign_in_experiences', ['id']], + ['users_roles', ['user_id', 'role_id']], + ['users', ['id']], +]; + +type IndexInfo = { + table: string; + indexes: Array<{ name?: string; type?: 'unique'; columns: string[]; strategy?: 'drop-only' }>; +}; + +const indexes: IndexInfo[] = [ + { + table: 'logs', + indexes: [ + { columns: ['key'] }, + { columns: ['created_at'], strategy: 'drop-only' }, + { name: 'user_id', columns: ["(payload->>'user_id') nulls last"] }, + { name: 'application_id', columns: ["(payload->>'application_id') nulls last"] }, + ], + }, + { + table: 'oidc_model_instances', + indexes: [ + { name: 'model_name_payload_user_code', columns: ['model_name', "(payload->>'userCode')"] }, + { name: 'model_name_payload_uid', columns: ['model_name', "(payload->>'uid')"] }, + { name: 'model_name_payload_grant_id', columns: ['model_name', "(payload->>'grantId')"] }, + ], + }, + { + table: 'passcodes', + indexes: [ + { columns: ['interaction_jti', 'type'] }, + { columns: ['email', 'type'] }, + { columns: ['phone', 'type'] }, + ], + }, + { table: 'resources', indexes: [{ type: 'unique', columns: ['indicator'] }] }, + { table: 'roles', indexes: [{ type: 'unique', columns: ['name'] }] }, + { table: 'scopes', indexes: [{ type: 'unique', columns: ['resource_id', 'name'] }] }, + { + table: 'users', + indexes: [{ columns: ['name'] }, { columns: ['created_at'], strategy: 'drop-only' }], + }, +]; + +const alteration: AlterationScript = { + up: async (pool) => { + await pool.query(sql` + create table tenants ( + id varchar(21) not null, + db_user_password varchar(128), + primary key (id) + ); + `); + + await pool.query(sql` + insert into tenants (${getId('id')}, ${getId('db_user_password')}) + values (${defaultTenantId}, null); + `); + + // Update primary keys + await Promise.all( + tables.map(async ([tableName, primaryKeys]) => { + // Add `tenant_id` column and set existing data to a default tenant + await pool.query(sql` + alter table ${sql.identifier([tableName])} + add column ${tenantId} varchar(21) not null default 'default' + references tenants (id) on update cascade on delete cascade, + drop constraint ${sql.identifier([tableName + '_pkey'])} cascade, + add primary key (${sql.join( + ['tenant_id', ...primaryKeys].map((key) => sql.identifier([key])), + sql`, ` + )}); + `); + + // Column should not have a default tenant ID, it should be always manually assigned + await pool.query(sql` + alter table ${sql.identifier([tableName])} + alter column ${tenantId} drop default; + `); + }) + ); + + // Update indexes + await Promise.all( + indexes.flatMap(({ table, indexes }) => + indexes.map(async ({ name, type, columns, strategy }) => { + const indexName = getId(`${table}__${name ?? columns.join('_')}`); + await pool.query(sql`drop index ${indexName}`); + + if (strategy !== 'drop-only') { + await pool.query( + sql` + create ${raw(conditionalString(type))} index ${indexName} + on ${getId(table)} + ( + ${tenantId}, + ${sql.join( + columns.map((column) => raw(column)), + sql`, ` + )} + ); + ` + ); + } + }) + ) + ); + }, + down: async (pool) => { + throw new Error('Not implemented'); + }, +}; + +export default alteration; diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 8082e719b..863cb7f7b 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -52,7 +52,9 @@ "lint-staged": "^13.0.0", "pluralize": "^8.0.0", "prettier": "^2.8.1", + "roarr": "^7.11.0", "slonik": "^30.0.0", + "slonik-sql-tag-raw": "^1.1.4", "typescript": "^4.9.4" }, "eslintConfig": { diff --git a/packages/schemas/src/gen/index.ts b/packages/schemas/src/gen/index.ts index 566ba98d0..dace4a1b1 100644 --- a/packages/schemas/src/gen/index.ts +++ b/packages/schemas/src/gen/index.ts @@ -44,8 +44,8 @@ const generate = async () => { // Get statements const statements = paragraph .split(';') - .map((value) => normalizeWhitespaces(value)) - .map((value) => removeUnrecognizedComments(value)); + .map((value) => removeUnrecognizedComments(value)) + .map((value) => normalizeWhitespaces(value)); // Parse Table statements const tables = statements diff --git a/packages/schemas/src/models/tenants.ts b/packages/schemas/src/models/tenants.ts index abc8353ca..e40051f66 100644 --- a/packages/schemas/src/models/tenants.ts +++ b/packages/schemas/src/models/tenants.ts @@ -1,6 +1,7 @@ import { createModel } from '@withtyped/server'; export const Tenants = createModel(/* sql */ ` + /* init_order = 0 */ create table tenants ( id varchar(32) not null, db_user_password varchar(128) not null, diff --git a/packages/schemas/src/seeds/application.ts b/packages/schemas/src/seeds/application.ts index c3214b82b..e76cd5332 100644 --- a/packages/schemas/src/seeds/application.ts +++ b/packages/schemas/src/seeds/application.ts @@ -1,5 +1,6 @@ import type { CreateApplication } from '../db-entries/index.js'; import { ApplicationType } from '../db-entries/index.js'; +import { defaultTenantId } from './tenant.js'; /** * The fixed application ID for Admin Console. @@ -11,6 +12,7 @@ export const adminConsoleApplicationId = 'admin-console'; export const demoAppApplicationId = 'demo-app'; export const createDemoAppApplication = (secret: string): Readonly => ({ + tenantId: defaultTenantId, id: demoAppApplicationId, secret, name: 'Demo App', diff --git a/packages/schemas/src/seeds/resource.ts b/packages/schemas/src/seeds/resource.ts index 34bb5c676..d1f4bfff6 100644 --- a/packages/schemas/src/seeds/resource.ts +++ b/packages/schemas/src/seeds/resource.ts @@ -1,8 +1,10 @@ import type { CreateResource } from '../db-entries/index.js'; +import { defaultTenantId } from './tenant.js'; export const managementResourceId = 'management-api'; export const managementResource: Readonly = Object.freeze({ + tenantId: defaultTenantId, id: managementResourceId, /** * The fixed resource indicator for Management APIs. diff --git a/packages/schemas/src/seeds/roles.ts b/packages/schemas/src/seeds/roles.ts index d67d2eb86..f60489c64 100644 --- a/packages/schemas/src/seeds/roles.ts +++ b/packages/schemas/src/seeds/roles.ts @@ -1,6 +1,7 @@ import type { CreateRole, CreateRolesScope } from '../db-entries/index.js'; import { UserRole } from '../types/index.js'; import { managementResourceScopeId } from './scope.js'; +import { defaultTenantId } from './tenant.js'; export const adminConsoleAdminRoleId = 'ac-admin-id'; @@ -8,12 +9,14 @@ export const adminConsoleAdminRoleId = 'ac-admin-id'; * Default Admin Role for Admin Console. */ export const defaultRole: Readonly = { + tenantId: defaultTenantId, id: adminConsoleAdminRoleId, name: UserRole.Admin, description: 'Admin role for Logto.', }; export const defaultRoleScopeRelation: Readonly = { + tenantId: defaultTenantId, roleId: adminConsoleAdminRoleId, scopeId: managementResourceScopeId, }; diff --git a/packages/schemas/src/seeds/scope.ts b/packages/schemas/src/seeds/scope.ts index 7eb942d37..8b3258b2d 100644 --- a/packages/schemas/src/seeds/scope.ts +++ b/packages/schemas/src/seeds/scope.ts @@ -1,9 +1,11 @@ import type { CreateScope } from '../db-entries/index.js'; import { managementResourceId } from './resource.js'; +import { defaultTenantId } from './tenant.js'; export const managementResourceScopeId = 'management-api-scope'; export const managementResourceScope: Readonly = Object.freeze({ + tenantId: defaultTenantId, id: managementResourceScopeId, name: 'management-api:default', description: 'Default scope for management API', diff --git a/packages/schemas/src/seeds/setting.ts b/packages/schemas/src/seeds/setting.ts index 10a321e86..24d543b49 100644 --- a/packages/schemas/src/seeds/setting.ts +++ b/packages/schemas/src/seeds/setting.ts @@ -1,10 +1,12 @@ import type { CreateSetting } from '../db-entries/index.js'; import { AppearanceMode } from '../foundations/index.js'; +import { defaultTenantId } from './tenant.js'; export const defaultSettingId = 'default'; export const createDefaultSetting = (): Readonly => Object.freeze({ + tenantId: defaultTenantId, id: defaultSettingId, adminConsole: { language: 'en', diff --git a/packages/schemas/src/seeds/sign-in-experience.ts b/packages/schemas/src/seeds/sign-in-experience.ts index 4ac4a7345..1deac85e1 100644 --- a/packages/schemas/src/seeds/sign-in-experience.ts +++ b/packages/schemas/src/seeds/sign-in-experience.ts @@ -3,10 +3,12 @@ import { generateDarkColor } from '@logto/core-kit'; import type { CreateSignInExperience } from '../db-entries/index.js'; import { SignInMode } from '../db-entries/index.js'; import { BrandingStyle, SignInIdentifier } from '../foundations/index.js'; +import { defaultTenantId } from './tenant.js'; const defaultPrimaryColor = '#6139F6'; export const defaultSignInExperience: Readonly = { + tenantId: defaultTenantId, id: 'default', color: { primaryColor: defaultPrimaryColor, @@ -52,5 +54,6 @@ export const adminConsoleSignInExperience: CreateSignInExperience = { style: BrandingStyle.Logo_Slogan, logoUrl: 'https://logto.io/logo.svg', darkLogoUrl: 'https://logto.io/logo-dark.svg', + slogan: 'admin_console.welcome.title', // TODO: @simeng should we programmatically support an i18n key for slogan? }, }; diff --git a/packages/schemas/src/seeds/tenant.ts b/packages/schemas/src/seeds/tenant.ts new file mode 100644 index 000000000..884073d89 --- /dev/null +++ b/packages/schemas/src/seeds/tenant.ts @@ -0,0 +1,2 @@ +export const defaultTenantId = 'default'; +export const adminTenantId = 'admin'; diff --git a/packages/schemas/tables/logto_configs.sql b/packages/schemas/tables/_logto_configs.sql similarity index 100% rename from packages/schemas/tables/logto_configs.sql rename to packages/schemas/tables/_logto_configs.sql diff --git a/packages/schemas/tables/applications.sql b/packages/schemas/tables/applications.sql index 507bc8013..9fc34254f 100644 --- a/packages/schemas/tables/applications.sql +++ b/packages/schemas/tables/applications.sql @@ -1,6 +1,10 @@ +/* init_order = 1 */ + create type application_type as enum ('Native', 'SPA', 'Traditional', 'MachineToMachine'); create table applications ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, name varchar(256) not null, secret varchar(64) not null, @@ -11,3 +15,6 @@ create table applications ( created_at timestamptz not null default(now()), primary key (id) ); + +create index applications__id + on applications (tenant_id, id) diff --git a/packages/schemas/tables/applications_roles.sql b/packages/schemas/tables/applications_roles.sql new file mode 100644 index 000000000..99faf8860 --- /dev/null +++ b/packages/schemas/tables/applications_roles.sql @@ -0,0 +1,15 @@ +create table applications_roles ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + application_id varchar(21) not null + references applications (id) on update cascade on delete cascade, + role_id varchar(21) not null + references roles (id) on update cascade on delete cascade, + primary key (id), + constraint applications_roles__application_id_role_id + unique (tenant_id, application_id, role_id) +); + +create index applications_roles__id + on applications_roles (tenant_id, id); diff --git a/packages/schemas/tables/connectors.sql b/packages/schemas/tables/connectors.sql index d8d5396f4..03f304768 100644 --- a/packages/schemas/tables/connectors.sql +++ b/packages/schemas/tables/connectors.sql @@ -1,4 +1,6 @@ create table connectors ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(128) not null, sync_profile boolean not null default FALSE, connector_id varchar(128) not null, @@ -7,3 +9,6 @@ create table connectors ( created_at timestamptz not null default(now()), primary key (id) ); + +create index connectors__id + on connectors (tenant_id, id); diff --git a/packages/schemas/tables/custom_phrases.sql b/packages/schemas/tables/custom_phrases.sql index 43a00c1ac..3fd6c1ca5 100644 --- a/packages/schemas/tables/custom_phrases.sql +++ b/packages/schemas/tables/custom_phrases.sql @@ -1,5 +1,13 @@ create table custom_phrases ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, language_tag varchar(16) not null, translation jsonb /* @use Translation */ not null, - primary key(language_tag) + primary key (id), + constraint custom_phrases__language_tag + unique (tenant_id, language_tag) ); + +create index custom_phrases__id + on custom_phrases (tenant_id, id); diff --git a/packages/schemas/tables/logs.sql b/packages/schemas/tables/logs.sql index 2f82b5648..6eb38f606 100644 --- a/packages/schemas/tables/logs.sql +++ b/packages/schemas/tables/logs.sql @@ -1,5 +1,6 @@ -create table logs -( +create table logs ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, key varchar(128) not null, payload jsonb /* @use LogContextPayload */ not null default '{}'::jsonb, @@ -7,7 +8,14 @@ create table logs primary key (id) ); -create index logs__key on logs (key); -create index logs__created_at on logs (created_at); -create index logs__user_id on logs ((payload->>'user_id') nulls last); -create index logs__application_id on logs ((payload->>'application_id') nulls last); +create index logs__id + on logs (tenant_id, id); + +create index logs__key + on logs (tenant_id, key); + +create index logs__user_id + on logs (tenant_id, (payload->>'user_id') nulls last); + +create index logs__application_id + on logs (tenant_id, (payload->>'application_id') nulls last); diff --git a/packages/schemas/tables/oidc_model_instances.sql b/packages/schemas/tables/oidc_model_instances.sql index 6f198918c..ee5e080ea 100644 --- a/packages/schemas/tables/oidc_model_instances.sql +++ b/packages/schemas/tables/oidc_model_instances.sql @@ -1,26 +1,34 @@ create table oidc_model_instances ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, model_name varchar(64) not null, id varchar(128) not null, payload jsonb /* @use OidcModelInstancePayload */ not null, expires_at timestamptz not null, consumed_at timestamptz, - primary key (model_name, id) + primary key (id) ); +create index oidc_model_instances__model_name_id + on oidc_model_instances (tenant_id, model_name, id); + create index oidc_model_instances__model_name_payload_user_code -on oidc_model_instances ( - model_name, - (payload->>'userCode') -); + on oidc_model_instances ( + tenant_id, + model_name, + (payload->>'userCode') + ); create index oidc_model_instances__model_name_payload_uid -on oidc_model_instances ( - model_name, - (payload->>'uid') -); + on oidc_model_instances ( + tenant_id, + model_name, + (payload->>'uid') + ); create index oidc_model_instances__model_name_payload_grant_id -on oidc_model_instances ( - model_name, - (payload->>'grantId') -); + on oidc_model_instances ( + tenant_id, + model_name, + (payload->>'grantId') + ); diff --git a/packages/schemas/tables/passcodes.sql b/packages/schemas/tables/passcodes.sql index b5f48c3b3..a8639b10b 100644 --- a/packages/schemas/tables/passcodes.sql +++ b/packages/schemas/tables/passcodes.sql @@ -1,4 +1,6 @@ create table passcodes ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, interaction_jti varchar(128), phone varchar(32), @@ -11,20 +13,14 @@ create table passcodes ( primary key (id) ); +create index passcodes__id + on passcodes (tenant_id, id); + create index passcodes__interaction_jti_type -on passcodes ( - interaction_jti, - type -); + on passcodes (tenant_id, interaction_jti, type); create index passcodes__email_type -on passcodes ( - email, - type -); + on passcodes (tenant_id, email, type); create index passcodes__phone_type -on passcodes ( - phone, - type -); + on passcodes (tenant_id, phone, type); diff --git a/packages/schemas/tables/resources.sql b/packages/schemas/tables/resources.sql index 9c73a0cbf..9b5a3ed9a 100644 --- a/packages/schemas/tables/resources.sql +++ b/packages/schemas/tables/resources.sql @@ -1,12 +1,14 @@ create table resources ( - id varchar(21) not null, - name text not null, - indicator text not null unique, /* resource indicator also used as audience */ - access_token_ttl bigint not null default(3600), /* expiration value in seconds, default is 1h */ - primary key (id) + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + name text not null, + indicator text not null unique, /* resource indicator also used as audience */ + access_token_ttl bigint not null default(3600), /* expiration value in seconds, default is 1h */ + primary key (id), + constraint resources__indicator + unique (tenant_id, indicator) ); -create unique index resources__indicator -on resources ( - indicator -); +create index resources__id + on resources (tenant_id, indicator); diff --git a/packages/schemas/tables/roles.sql b/packages/schemas/tables/roles.sql index f08ba85d1..e606b2677 100644 --- a/packages/schemas/tables/roles.sql +++ b/packages/schemas/tables/roles.sql @@ -1,11 +1,15 @@ +/* init_order = 1 */ + create table roles ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, name varchar(128) not null, description varchar(128) not null, - primary key (id) + primary key (id), + constraint roles__name + unique (tenant_id, name) ); -create unique index roles__name -on roles ( - name -); +create index roles__id + on roles (tenant_id, id); diff --git a/packages/schemas/tables/roles_scopes.sql b/packages/schemas/tables/roles_scopes.sql new file mode 100644 index 000000000..2b61b7612 --- /dev/null +++ b/packages/schemas/tables/roles_scopes.sql @@ -0,0 +1,15 @@ +create table roles_scopes ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + role_id varchar(21) not null + references roles (id) on update cascade on delete cascade, + scope_id varchar(21) not null + references scopes (id) on update cascade on delete cascade, + primary key (id), + constraint roles_scopes__role_id_scope_id + unique (tenant_id, role_id, scope_id) +); + +create index roles_scopes__id + on roles_scopes (tenant_id, id); diff --git a/packages/schemas/tables/rolesapplications.sql b/packages/schemas/tables/rolesapplications.sql deleted file mode 100644 index 88728f9e0..000000000 --- a/packages/schemas/tables/rolesapplications.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table applications_roles ( - application_id varchar(21) not null references applications (id) on update cascade on delete cascade, - role_id varchar(21) not null references roles (id) on update cascade on delete cascade, - primary key (application_id, role_id) -); diff --git a/packages/schemas/tables/scopes.sql b/packages/schemas/tables/scopes.sql index f8f68a586..86e8a631a 100644 --- a/packages/schemas/tables/scopes.sql +++ b/packages/schemas/tables/scopes.sql @@ -1,14 +1,16 @@ create table scopes ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, - resource_id varchar(21) not null references resources (id) on update cascade on delete cascade, + resource_id varchar(21) not null + references resources (id) on update cascade on delete cascade, name varchar(256) not null, description text not null, created_at timestamptz not null default(now()), - primary key (id) + primary key (id), + constraint scopes__resource_id_name + unique (tenant_id, resource_id, name) ); -create index scopes__resource_id_name -on scopes ( - resource_id, - name -); +create index scopes__id + on scopes (tenant_id, id); diff --git a/packages/schemas/tables/scopesroles.sql b/packages/schemas/tables/scopesroles.sql deleted file mode 100644 index 227f7271b..000000000 --- a/packages/schemas/tables/scopesroles.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table roles_scopes ( - role_id varchar(21) not null references roles (id) on update cascade on delete cascade, - scope_id varchar(21) not null references scopes (id) on update cascade on delete cascade, - primary key (role_id, scope_id) -); diff --git a/packages/schemas/tables/settings.sql b/packages/schemas/tables/settings.sql index 329956779..c8e9bbbdf 100644 --- a/packages/schemas/tables/settings.sql +++ b/packages/schemas/tables/settings.sql @@ -1,5 +1,10 @@ create table settings ( - id varchar(21) not null, - admin_console jsonb /* @use AdminConsoleConfig */ not null, - primary key (id) + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + id varchar(21) not null, + admin_console jsonb /* @use AdminConsoleConfig */ not null, + primary key (id) ); + +create index settings__id + on settings (tenant_id, id); diff --git a/packages/schemas/tables/sign_in_experiences.sql b/packages/schemas/tables/sign_in_experiences.sql index c0f7ce154..5802766fd 100644 --- a/packages/schemas/tables/sign_in_experiences.sql +++ b/packages/schemas/tables/sign_in_experiences.sql @@ -1,6 +1,8 @@ create type sign_in_mode as enum ('SignIn', 'Register', 'SignInAndRegister'); create table sign_in_experiences ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(21) not null, color jsonb /* @use Color */ not null, branding jsonb /* @use Branding */ not null, @@ -12,3 +14,6 @@ create table sign_in_experiences ( sign_in_mode sign_in_mode not null default 'SignInAndRegister', primary key (id) ); + +create index sign_in_experiences__id + on sign_in_experiences (tenant_id, id); diff --git a/packages/schemas/tables/users.sql b/packages/schemas/tables/users.sql index 0d01e28e0..37db1f92f 100644 --- a/packages/schemas/tables/users.sql +++ b/packages/schemas/tables/users.sql @@ -1,6 +1,10 @@ +/* init_order = 1 */ + create type users_password_encryption_method as enum ('Argon2i'); create table users ( + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, id varchar(12) not null, username varchar(128) unique, primary_email varchar(128) unique, @@ -18,5 +22,8 @@ create table users ( primary key (id) ); -create index users__created_at on users (created_at); -create index users__name on users (name); +create index users__id + on users (tenant_id, id); + +create index users__name + on users (tenant_id, name); diff --git a/packages/schemas/tables/users_roles.sql b/packages/schemas/tables/users_roles.sql new file mode 100644 index 000000000..38a5f3411 --- /dev/null +++ b/packages/schemas/tables/users_roles.sql @@ -0,0 +1,14 @@ +create table users_roles ( + id varchar(21) not null, + tenant_id varchar(21) not null + references tenants (id) on update cascade on delete cascade, + user_id varchar(21) not null + references users (id) on update cascade on delete cascade, + role_id varchar(21) not null + references roles (id) on update cascade on delete cascade, + primary key (id), + constraint users_roles__user_id_role_id unique (tenant_id, user_id, role_id) +); + +create index users_roles__id + on users_roles (tenant_id, id); diff --git a/packages/schemas/tables/usersroles.sql b/packages/schemas/tables/usersroles.sql deleted file mode 100644 index 7cb88d9cb..000000000 --- a/packages/schemas/tables/usersroles.sql +++ /dev/null @@ -1,5 +0,0 @@ -create table users_roles ( - user_id varchar(21) not null references users (id) on update cascade on delete cascade, - role_id varchar(21) not null references roles (id) on update cascade on delete cascade, - primary key (user_id, role_id) -); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69cbc48bc..3fff578da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -590,7 +590,9 @@ importers: lint-staged: ^13.0.0 pluralize: ^8.0.0 prettier: ^2.8.1 + roarr: ^7.11.0 slonik: ^30.0.0 + slonik-sql-tag-raw: ^1.1.4 typescript: ^4.9.4 zod: ^3.20.2 dependencies: @@ -614,7 +616,9 @@ importers: lint-staged: 13.0.0 pluralize: 8.0.0 prettier: 2.8.1 + roarr: 7.11.0 slonik: 30.1.2 + slonik-sql-tag-raw: 1.1.4_roarr@7.11.0+slonik@30.1.2 typescript: 4.9.4 packages/shared: @@ -5893,19 +5897,12 @@ packages: engines: {node: '>=8'} dev: true - /define-properties/1.1.3: - resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} - engines: {node: '>= 0.4'} - dependencies: - object-keys: 1.1.1 - /define-properties/1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} engines: {node: '>= 0.4'} dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 - dev: true /delay/4.4.1: resolution: {integrity: sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==} @@ -7388,7 +7385,7 @@ packages: resolution: {integrity: sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==} engines: {node: '>= 0.4'} dependencies: - define-properties: 1.1.3 + define-properties: 1.1.4 /globalyzer/0.1.0: resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} @@ -7486,7 +7483,6 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.1.3 - dev: true /has-symbols/1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} @@ -13177,7 +13173,6 @@ packages: roarr: 7.11.0 serialize-error: 8.1.0 slonik: 30.1.2 - dev: false /slonik/22.7.1: resolution: {integrity: sha512-88GidNOWv4Bg0CqYLXajqcD0bbLip2soY6B4JzHP7EGDrWUb1WSlu7mIppTJVfcK99mx+jnX3xQq3FJ0DoOXag==}