0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(schemas): init tenant sqls

This commit is contained in:
Gao Sun 2023-01-19 20:27:01 +08:00
parent 53f49df621
commit ba44eb5fc6
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
32 changed files with 362 additions and 93 deletions

View file

@ -13,7 +13,7 @@ import {
managementResourceScope, managementResourceScope,
defaultRoleScopeRelation, defaultRoleScopeRelation,
} from '@logto/schemas'; } from '@logto/schemas';
import { Hooks } from '@logto/schemas/models'; import { Hooks, Tenants } from '@logto/schemas/models';
import chalk from 'chalk'; import chalk from 'chalk';
import type { DatabasePool, DatabaseTransactionConnection } from 'slonik'; import type { DatabasePool, DatabaseTransactionConnection } from 'slonik';
import { sql } from 'slonik'; import { sql } from 'slonik';
@ -33,6 +33,31 @@ import { getLatestAlterationTimestamp } from '../alteration/index.js';
import { getAlterationDirectory } from '../alteration/utils.js'; import { getAlterationDirectory } from '../alteration/utils.js';
import { oidcConfigReaders } from './oidc-config.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 createTables = async (connection: DatabaseTransactionConnection) => {
const tableDirectory = getPathInModule('@logto/schemas', 'tables'); const tableDirectory = getPathInModule('@logto/schemas', 'tables');
const directoryFiles = await readdir(tableDirectory); const directoryFiles = await readdir(tableDirectory);
@ -44,16 +69,19 @@ const createTables = async (connection: DatabaseTransactionConnection) => {
]) ])
); );
// Await in loop is intended for better error handling console.log(Tenants.raw, getExplicitOrder(Tenants.raw));
for (const [, query] of queries) {
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 // eslint-disable-next-line no-await-in-loop
await connection.query(sql`${raw(query)}`); 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) => { 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), { await oraPromise(createTables(connection), {
text: 'Create tables', text: 'Create tables',
prefixText: chalk.blue('[info]'), prefixText: chalk.blue('[info]'),

View file

@ -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;

View file

@ -52,7 +52,9 @@
"lint-staged": "^13.0.0", "lint-staged": "^13.0.0",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"prettier": "^2.8.1", "prettier": "^2.8.1",
"roarr": "^7.11.0",
"slonik": "^30.0.0", "slonik": "^30.0.0",
"slonik-sql-tag-raw": "^1.1.4",
"typescript": "^4.9.4" "typescript": "^4.9.4"
}, },
"eslintConfig": { "eslintConfig": {

View file

@ -44,8 +44,8 @@ const generate = async () => {
// Get statements // Get statements
const statements = paragraph const statements = paragraph
.split(';') .split(';')
.map((value) => normalizeWhitespaces(value)) .map((value) => removeUnrecognizedComments(value))
.map((value) => removeUnrecognizedComments(value)); .map((value) => normalizeWhitespaces(value));
// Parse Table statements // Parse Table statements
const tables = statements const tables = statements

View file

@ -1,6 +1,7 @@
import { createModel } from '@withtyped/server'; import { createModel } from '@withtyped/server';
export const Tenants = createModel(/* sql */ ` export const Tenants = createModel(/* sql */ `
/* init_order = 0 */
create table tenants ( create table tenants (
id varchar(32) not null, id varchar(32) not null,
db_user_password varchar(128) not null, db_user_password varchar(128) not null,

View file

@ -1,5 +1,6 @@
import type { CreateApplication } from '../db-entries/index.js'; import type { CreateApplication } from '../db-entries/index.js';
import { ApplicationType } from '../db-entries/index.js'; import { ApplicationType } from '../db-entries/index.js';
import { defaultTenantId } from './tenant.js';
/** /**
* The fixed application ID for Admin Console. * The fixed application ID for Admin Console.
@ -11,6 +12,7 @@ export const adminConsoleApplicationId = 'admin-console';
export const demoAppApplicationId = 'demo-app'; export const demoAppApplicationId = 'demo-app';
export const createDemoAppApplication = (secret: string): Readonly<CreateApplication> => ({ export const createDemoAppApplication = (secret: string): Readonly<CreateApplication> => ({
tenantId: defaultTenantId,
id: demoAppApplicationId, id: demoAppApplicationId,
secret, secret,
name: 'Demo App', name: 'Demo App',

View file

@ -1,8 +1,10 @@
import type { CreateResource } from '../db-entries/index.js'; import type { CreateResource } from '../db-entries/index.js';
import { defaultTenantId } from './tenant.js';
export const managementResourceId = 'management-api'; export const managementResourceId = 'management-api';
export const managementResource: Readonly<CreateResource> = Object.freeze({ export const managementResource: Readonly<CreateResource> = Object.freeze({
tenantId: defaultTenantId,
id: managementResourceId, id: managementResourceId,
/** /**
* The fixed resource indicator for Management APIs. * The fixed resource indicator for Management APIs.

View file

@ -1,6 +1,7 @@
import type { CreateRole, CreateRolesScope } from '../db-entries/index.js'; import type { CreateRole, CreateRolesScope } from '../db-entries/index.js';
import { UserRole } from '../types/index.js'; import { UserRole } from '../types/index.js';
import { managementResourceScopeId } from './scope.js'; import { managementResourceScopeId } from './scope.js';
import { defaultTenantId } from './tenant.js';
export const adminConsoleAdminRoleId = 'ac-admin-id'; export const adminConsoleAdminRoleId = 'ac-admin-id';
@ -8,12 +9,14 @@ export const adminConsoleAdminRoleId = 'ac-admin-id';
* Default Admin Role for Admin Console. * Default Admin Role for Admin Console.
*/ */
export const defaultRole: Readonly<CreateRole> = { export const defaultRole: Readonly<CreateRole> = {
tenantId: defaultTenantId,
id: adminConsoleAdminRoleId, id: adminConsoleAdminRoleId,
name: UserRole.Admin, name: UserRole.Admin,
description: 'Admin role for Logto.', description: 'Admin role for Logto.',
}; };
export const defaultRoleScopeRelation: Readonly<CreateRolesScope> = { export const defaultRoleScopeRelation: Readonly<CreateRolesScope> = {
tenantId: defaultTenantId,
roleId: adminConsoleAdminRoleId, roleId: adminConsoleAdminRoleId,
scopeId: managementResourceScopeId, scopeId: managementResourceScopeId,
}; };

View file

@ -1,9 +1,11 @@
import type { CreateScope } from '../db-entries/index.js'; import type { CreateScope } from '../db-entries/index.js';
import { managementResourceId } from './resource.js'; import { managementResourceId } from './resource.js';
import { defaultTenantId } from './tenant.js';
export const managementResourceScopeId = 'management-api-scope'; export const managementResourceScopeId = 'management-api-scope';
export const managementResourceScope: Readonly<CreateScope> = Object.freeze({ export const managementResourceScope: Readonly<CreateScope> = Object.freeze({
tenantId: defaultTenantId,
id: managementResourceScopeId, id: managementResourceScopeId,
name: 'management-api:default', name: 'management-api:default',
description: 'Default scope for management API', description: 'Default scope for management API',

View file

@ -1,10 +1,12 @@
import type { CreateSetting } from '../db-entries/index.js'; import type { CreateSetting } from '../db-entries/index.js';
import { AppearanceMode } from '../foundations/index.js'; import { AppearanceMode } from '../foundations/index.js';
import { defaultTenantId } from './tenant.js';
export const defaultSettingId = 'default'; export const defaultSettingId = 'default';
export const createDefaultSetting = (): Readonly<CreateSetting> => export const createDefaultSetting = (): Readonly<CreateSetting> =>
Object.freeze({ Object.freeze({
tenantId: defaultTenantId,
id: defaultSettingId, id: defaultSettingId,
adminConsole: { adminConsole: {
language: 'en', language: 'en',

View file

@ -3,10 +3,12 @@ import { generateDarkColor } from '@logto/core-kit';
import type { CreateSignInExperience } from '../db-entries/index.js'; import type { CreateSignInExperience } from '../db-entries/index.js';
import { SignInMode } from '../db-entries/index.js'; import { SignInMode } from '../db-entries/index.js';
import { BrandingStyle, SignInIdentifier } from '../foundations/index.js'; import { BrandingStyle, SignInIdentifier } from '../foundations/index.js';
import { defaultTenantId } from './tenant.js';
const defaultPrimaryColor = '#6139F6'; const defaultPrimaryColor = '#6139F6';
export const defaultSignInExperience: Readonly<CreateSignInExperience> = { export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
tenantId: defaultTenantId,
id: 'default', id: 'default',
color: { color: {
primaryColor: defaultPrimaryColor, primaryColor: defaultPrimaryColor,
@ -52,5 +54,6 @@ export const adminConsoleSignInExperience: CreateSignInExperience = {
style: BrandingStyle.Logo_Slogan, style: BrandingStyle.Logo_Slogan,
logoUrl: 'https://logto.io/logo.svg', logoUrl: 'https://logto.io/logo.svg',
darkLogoUrl: 'https://logto.io/logo-dark.svg', darkLogoUrl: 'https://logto.io/logo-dark.svg',
slogan: 'admin_console.welcome.title', // TODO: @simeng should we programmatically support an i18n key for slogan?
}, },
}; };

View file

@ -0,0 +1,2 @@
export const defaultTenantId = 'default';
export const adminTenantId = 'admin';

View file

@ -1,6 +1,10 @@
/* init_order = 1 */
create type application_type as enum ('Native', 'SPA', 'Traditional', 'MachineToMachine'); create type application_type as enum ('Native', 'SPA', 'Traditional', 'MachineToMachine');
create table applications ( create table applications (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(21) not null, id varchar(21) not null,
name varchar(256) not null, name varchar(256) not null,
secret varchar(64) not null, secret varchar(64) not null,
@ -11,3 +15,6 @@ create table applications (
created_at timestamptz not null default(now()), created_at timestamptz not null default(now()),
primary key (id) primary key (id)
); );
create index applications__id
on applications (tenant_id, id)

View file

@ -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);

View file

@ -1,4 +1,6 @@
create table connectors ( create table connectors (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(128) not null, id varchar(128) not null,
sync_profile boolean not null default FALSE, sync_profile boolean not null default FALSE,
connector_id varchar(128) not null, connector_id varchar(128) not null,
@ -7,3 +9,6 @@ create table connectors (
created_at timestamptz not null default(now()), created_at timestamptz not null default(now()),
primary key (id) primary key (id)
); );
create index connectors__id
on connectors (tenant_id, id);

View file

@ -1,5 +1,13 @@
create table custom_phrases ( 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, language_tag varchar(16) not null,
translation jsonb /* @use Translation */ 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);

View file

@ -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, id varchar(21) not null,
key varchar(128) not null, key varchar(128) not null,
payload jsonb /* @use LogContextPayload */ not null default '{}'::jsonb, payload jsonb /* @use LogContextPayload */ not null default '{}'::jsonb,
@ -7,7 +8,14 @@ create table logs
primary key (id) primary key (id)
); );
create index logs__key on logs (key); create index logs__id
create index logs__created_at on logs (created_at); on logs (tenant_id, id);
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__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);

View file

@ -1,26 +1,34 @@
create table oidc_model_instances ( 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, model_name varchar(64) not null,
id varchar(128) not null, id varchar(128) not null,
payload jsonb /* @use OidcModelInstancePayload */ not null, payload jsonb /* @use OidcModelInstancePayload */ not null,
expires_at timestamptz not null, expires_at timestamptz not null,
consumed_at timestamptz, 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 create index oidc_model_instances__model_name_payload_user_code
on oidc_model_instances ( on oidc_model_instances (
model_name, tenant_id,
(payload->>'userCode') model_name,
); (payload->>'userCode')
);
create index oidc_model_instances__model_name_payload_uid create index oidc_model_instances__model_name_payload_uid
on oidc_model_instances ( on oidc_model_instances (
model_name, tenant_id,
(payload->>'uid') model_name,
); (payload->>'uid')
);
create index oidc_model_instances__model_name_payload_grant_id create index oidc_model_instances__model_name_payload_grant_id
on oidc_model_instances ( on oidc_model_instances (
model_name, tenant_id,
(payload->>'grantId') model_name,
); (payload->>'grantId')
);

View file

@ -1,4 +1,6 @@
create table passcodes ( create table passcodes (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(21) not null, id varchar(21) not null,
interaction_jti varchar(128), interaction_jti varchar(128),
phone varchar(32), phone varchar(32),
@ -11,20 +13,14 @@ create table passcodes (
primary key (id) primary key (id)
); );
create index passcodes__id
on passcodes (tenant_id, id);
create index passcodes__interaction_jti_type create index passcodes__interaction_jti_type
on passcodes ( on passcodes (tenant_id, interaction_jti, type);
interaction_jti,
type
);
create index passcodes__email_type create index passcodes__email_type
on passcodes ( on passcodes (tenant_id, email, type);
email,
type
);
create index passcodes__phone_type create index passcodes__phone_type
on passcodes ( on passcodes (tenant_id, phone, type);
phone,
type
);

View file

@ -1,12 +1,14 @@
create table resources ( create table resources (
id varchar(21) not null, tenant_id varchar(21) not null
name text not null, references tenants (id) on update cascade on delete cascade,
indicator text not null unique, /* resource indicator also used as audience */ id varchar(21) not null,
access_token_ttl bigint not null default(3600), /* expiration value in seconds, default is 1h */ name text not null,
primary key (id) 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 create index resources__id
on resources ( on resources (tenant_id, indicator);
indicator
);

View file

@ -1,11 +1,15 @@
/* init_order = 1 */
create table roles ( create table roles (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(21) not null, id varchar(21) not null,
name varchar(128) not null, name varchar(128) not null,
description 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 create index roles__id
on roles ( on roles (tenant_id, id);
name
);

View file

@ -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);

View file

@ -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)
);

View file

@ -1,14 +1,16 @@
create table scopes ( create table scopes (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(21) not null, 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, name varchar(256) not null,
description text not null, description text not null,
created_at timestamptz not null default(now()), 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 create index scopes__id
on scopes ( on scopes (tenant_id, id);
resource_id,
name
);

View file

@ -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)
);

View file

@ -1,5 +1,10 @@
create table settings ( create table settings (
id varchar(21) not null, tenant_id varchar(21) not null
admin_console jsonb /* @use AdminConsoleConfig */ not null, references tenants (id) on update cascade on delete cascade,
primary key (id) id varchar(21) not null,
admin_console jsonb /* @use AdminConsoleConfig */ not null,
primary key (id)
); );
create index settings__id
on settings (tenant_id, id);

View file

@ -1,6 +1,8 @@
create type sign_in_mode as enum ('SignIn', 'Register', 'SignInAndRegister'); create type sign_in_mode as enum ('SignIn', 'Register', 'SignInAndRegister');
create table sign_in_experiences ( 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, id varchar(21) not null,
color jsonb /* @use Color */ not null, color jsonb /* @use Color */ not null,
branding jsonb /* @use Branding */ 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', sign_in_mode sign_in_mode not null default 'SignInAndRegister',
primary key (id) primary key (id)
); );
create index sign_in_experiences__id
on sign_in_experiences (tenant_id, id);

View file

@ -1,6 +1,10 @@
/* init_order = 1 */
create type users_password_encryption_method as enum ('Argon2i'); create type users_password_encryption_method as enum ('Argon2i');
create table users ( create table users (
tenant_id varchar(21) not null
references tenants (id) on update cascade on delete cascade,
id varchar(12) not null, id varchar(12) not null,
username varchar(128) unique, username varchar(128) unique,
primary_email varchar(128) unique, primary_email varchar(128) unique,
@ -18,5 +22,8 @@ create table users (
primary key (id) primary key (id)
); );
create index users__created_at on users (created_at); create index users__id
create index users__name on users (name); on users (tenant_id, id);
create index users__name
on users (tenant_id, name);

View file

@ -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);

View file

@ -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)
);

View file

@ -590,7 +590,9 @@ importers:
lint-staged: ^13.0.0 lint-staged: ^13.0.0
pluralize: ^8.0.0 pluralize: ^8.0.0
prettier: ^2.8.1 prettier: ^2.8.1
roarr: ^7.11.0
slonik: ^30.0.0 slonik: ^30.0.0
slonik-sql-tag-raw: ^1.1.4
typescript: ^4.9.4 typescript: ^4.9.4
zod: ^3.20.2 zod: ^3.20.2
dependencies: dependencies:
@ -614,7 +616,9 @@ importers:
lint-staged: 13.0.0 lint-staged: 13.0.0
pluralize: 8.0.0 pluralize: 8.0.0
prettier: 2.8.1 prettier: 2.8.1
roarr: 7.11.0
slonik: 30.1.2 slonik: 30.1.2
slonik-sql-tag-raw: 1.1.4_roarr@7.11.0+slonik@30.1.2
typescript: 4.9.4 typescript: 4.9.4
packages/shared: packages/shared:
@ -5893,19 +5897,12 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true 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: /define-properties/1.1.4:
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
has-property-descriptors: 1.0.0 has-property-descriptors: 1.0.0
object-keys: 1.1.1 object-keys: 1.1.1
dev: true
/delay/4.4.1: /delay/4.4.1:
resolution: {integrity: sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==} resolution: {integrity: sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==}
@ -7388,7 +7385,7 @@ packages:
resolution: {integrity: sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==} resolution: {integrity: sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
define-properties: 1.1.3 define-properties: 1.1.4
/globalyzer/0.1.0: /globalyzer/0.1.0:
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
@ -7486,7 +7483,6 @@ packages:
resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
dependencies: dependencies:
get-intrinsic: 1.1.3 get-intrinsic: 1.1.3
dev: true
/has-symbols/1.0.3: /has-symbols/1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
@ -13177,7 +13173,6 @@ packages:
roarr: 7.11.0 roarr: 7.11.0
serialize-error: 8.1.0 serialize-error: 8.1.0
slonik: 30.1.2 slonik: 30.1.2
dev: false
/slonik/22.7.1: /slonik/22.7.1:
resolution: {integrity: sha512-88GidNOWv4Bg0CqYLXajqcD0bbLip2soY6B4JzHP7EGDrWUb1WSlu7mIppTJVfcK99mx+jnX3xQq3FJ0DoOXag==} resolution: {integrity: sha512-88GidNOWv4Bg0CqYLXajqcD0bbLip2soY6B4JzHP7EGDrWUb1WSlu7mIppTJVfcK99mx+jnX3xQq3FJ0DoOXag==}