mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(schemas,cloud): align tenant types (#4004)
This commit is contained in:
parent
d2f7c94167
commit
e96e92b2df
24 changed files with 79 additions and 109 deletions
|
@ -1,11 +1,5 @@
|
|||
import { createTenantMetadata } from '@logto/core-kit';
|
||||
import type {
|
||||
CreateTenant,
|
||||
AdminData,
|
||||
UpdateAdminData,
|
||||
CreateScope,
|
||||
CreateRolesScope,
|
||||
} from '@logto/schemas';
|
||||
import type { AdminData, UpdateAdminData, CreateScope, CreateRolesScope } from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import type { CommonQueryMethods } from 'slonik';
|
||||
|
@ -18,7 +12,11 @@ import { getDatabaseName } from '../../../queries/database.js';
|
|||
export const createTenant = async (pool: CommonQueryMethods, tenantId: string) => {
|
||||
const database = await getDatabaseName(pool, true);
|
||||
const { parentRole, role, password } = createTenantMetadata(database, tenantId);
|
||||
const createTenant: CreateTenant = { id: tenantId, dbUser: role, dbUserPassword: password };
|
||||
const createTenant = {
|
||||
id: tenantId,
|
||||
dbUser: role,
|
||||
dbUserPassword: password,
|
||||
};
|
||||
|
||||
await pool.query(insertInto(createTenant, 'tenants'));
|
||||
await pool.query(sql`
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@silverhand/essentials": "^2.5.0",
|
||||
"@withtyped/postgres": "^0.11.0",
|
||||
"@withtyped/server": "^0.11.0",
|
||||
"@withtyped/server": "^0.11.1",
|
||||
"accepts": "^1.3.8",
|
||||
"chalk": "^5.0.0",
|
||||
"decamelize": "^6.0.0",
|
||||
|
|
|
@ -6,8 +6,6 @@ import { DemoConnector } from '@logto/connector-kit';
|
|||
import { createTenantMetadata } from '@logto/core-kit';
|
||||
import {
|
||||
type LogtoOidcConfigType,
|
||||
type TenantInfo,
|
||||
type CreateTenant,
|
||||
createAdminTenantApplicationRole,
|
||||
AdminTenantRole,
|
||||
createTenantMachineToMachineApplication,
|
||||
|
@ -20,8 +18,9 @@ import {
|
|||
createAdminData,
|
||||
createAdminDataInAdminTenant,
|
||||
getManagementApiResourceIndicator,
|
||||
type PatchTenant,
|
||||
type TenantModel,
|
||||
} from '@logto/schemas';
|
||||
import type { TenantInfo } from '@logto/schemas/models';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { appendPath } from '@silverhand/essentials';
|
||||
|
||||
|
@ -71,7 +70,10 @@ export class TenantsLibrary {
|
|||
}));
|
||||
}
|
||||
|
||||
async updateTenantById(tenantId: string, payload: PatchTenant): Promise<TenantInfo> {
|
||||
async updateTenantById(
|
||||
tenantId: string,
|
||||
payload: Partial<Pick<TenantModel, 'name' | 'tag'>>
|
||||
): Promise<TenantInfo> {
|
||||
const { id, name, tag } = await this.queries.tenants.updateTenantById(tenantId, payload);
|
||||
|
||||
return { id, name, tag, indicator: getManagementApiResourceIndicator(id) };
|
||||
|
@ -119,13 +121,13 @@ export class TenantsLibrary {
|
|||
|
||||
async createNewTenant(
|
||||
forUserId: string,
|
||||
payload: Pick<CreateTenant, 'name' | 'tag'>
|
||||
payload: Partial<Pick<TenantModel, 'name' | 'tag'>>
|
||||
): Promise<TenantInfo> {
|
||||
const databaseName = await getDatabaseName(this.queries.client);
|
||||
const { id: tenantId, parentRole, role, password } = createTenantMetadata(databaseName);
|
||||
|
||||
// Init tenant
|
||||
const createTenant: CreateTenant = {
|
||||
const createTenant = {
|
||||
id: tenantId,
|
||||
dbUser: role,
|
||||
dbUserPassword: password,
|
||||
|
|
|
@ -8,11 +8,9 @@ import {
|
|||
PredefinedScope,
|
||||
ApplicationType,
|
||||
type AdminData,
|
||||
type CreateTenant,
|
||||
type PatchTenant,
|
||||
type CreateRolesScope,
|
||||
type TenantModel,
|
||||
} from '@logto/schemas';
|
||||
import type { TenantModel } from '@logto/schemas/models';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import {
|
||||
type PostgreSql,
|
||||
|
@ -47,20 +45,15 @@ export const createTenantsQueries = (client: Queryable<PostgreSql>) => {
|
|||
where roles.tenant_id = ${adminTenantId};
|
||||
`);
|
||||
|
||||
const insertTenant = async (tenant: CreateTenant) =>
|
||||
client.query(
|
||||
insertInto(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Object.fromEntries(Object.entries(tenant).filter(([_, value]) => value !== undefined)),
|
||||
'tenants'
|
||||
)
|
||||
);
|
||||
const insertTenant = async (
|
||||
tenant: Pick<TenantModel, 'id' | 'dbUser' | 'dbUserPassword'> &
|
||||
Partial<Pick<TenantModel, 'name' | 'tag'>>
|
||||
) => client.query(insertInto(tenant, 'tenants'));
|
||||
|
||||
const updateTenantById = async (tenantId: string, rawPayload: PatchTenant) => {
|
||||
const payload: Record<string, string> = Object.fromEntries(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Object.entries(rawPayload).filter(([_, value]) => value !== undefined)
|
||||
);
|
||||
const updateTenantById = async (
|
||||
tenantId: string,
|
||||
payload: Partial<Pick<TenantModel, 'name' | 'tag'>>
|
||||
) => {
|
||||
const tenant = await client.maybeOne<TenantModel>(sql`
|
||||
update tenants
|
||||
set ${Object.entries(payload).map(([key, value]) => sql`${id(key)}=${jsonIfNeeded(value)}`)}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { TenantInfo } from '@logto/schemas';
|
||||
import { CloudScope, TenantTag } from '@logto/schemas';
|
||||
import { CloudScope } from '@logto/schemas';
|
||||
import { type TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
|
||||
import { buildRequestAuthContext, createHttpContext } from '#src/test-utils/context.js';
|
||||
import { noop } from '#src/test-utils/function.js';
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import {
|
||||
CloudScope,
|
||||
tenantInfoGuard,
|
||||
createTenantGuard,
|
||||
adminTenantId,
|
||||
defaultTenantId,
|
||||
} from '@logto/schemas';
|
||||
import { CloudScope, adminTenantId, defaultTenantId } from '@logto/schemas';
|
||||
import { Tenants, tenantInfoGuard } from '@logto/schemas/models';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
import { createRouter, RequestError } from '@withtyped/server';
|
||||
|
||||
|
@ -23,7 +18,7 @@ export const tenantsRoutes = (library: TenantsLibrary) =>
|
|||
.patch(
|
||||
'/:tenantId',
|
||||
{
|
||||
body: createTenantGuard.pick({ name: true, tag: true }).partial(),
|
||||
body: Tenants.guard('patch').pick({ name: true, tag: true }),
|
||||
response: tenantInfoGuard,
|
||||
},
|
||||
async (context, next) => {
|
||||
|
@ -61,7 +56,7 @@ export const tenantsRoutes = (library: TenantsLibrary) =>
|
|||
.post(
|
||||
'/',
|
||||
{
|
||||
body: createTenantGuard.pick({ name: true, tag: true }).required(),
|
||||
body: Tenants.guard('create').pick({ name: true, tag: true }),
|
||||
response: tenantInfoGuard,
|
||||
},
|
||||
async (context, next) => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { ServiceLogType, TenantInfo, TenantTag } from '@logto/schemas';
|
||||
import type { ServiceLogType } from '@logto/schemas';
|
||||
import type { TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
|
||||
import type { ServicesLibrary } from '#src/libraries/services.js';
|
||||
import type { TenantsLibrary } from '#src/libraries/tenants.js';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import type { TenantInfo } from '@logto/schemas';
|
||||
import type { TenantInfo } from '@logto/schemas/models';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useHref } from 'react-router-dom';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type TenantInfo, TenantTag } from '@logto/schemas';
|
||||
import { type TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
import { useCallback, useContext, useEffect } from 'react';
|
||||
|
||||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import type { TenantInfo } from '@logto/schemas';
|
||||
import type { TenantInfo } from '@logto/schemas/models';
|
||||
import { conditional, yes } from '@silverhand/essentials';
|
||||
import { HTTPError } from 'ky';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type TenantInfo, TenantTag, defaultManagementApi } from '@logto/schemas';
|
||||
import { defaultManagementApi } from '@logto/schemas';
|
||||
import { type TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
import { conditional, noop } from '@silverhand/essentials';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, createContext, useState } from 'react';
|
||||
|
@ -24,7 +25,7 @@ type Tenants = {
|
|||
const { tenantId, indicator } = defaultManagementApi.resource;
|
||||
const initialTenants = conditional(
|
||||
!isCloud && [
|
||||
{ id: tenantId, name: `tenant_${tenantId}`, tag: `${TenantTag.Development}`, indicator }, // Make `tag` value to be string type.
|
||||
{ id: tenantId, name: `tenant_${tenantId}`, tag: TenantTag.Development, indicator }, // Make `tag` value to be string type.
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import { type TenantInfo } from '@logto/schemas';
|
||||
import { type TenantInfo } from '@logto/schemas/models';
|
||||
import { type Optional, trySafe } from '@silverhand/essentials';
|
||||
import type ky from 'ky';
|
||||
import { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type TenantInfo } from '@logto/schemas';
|
||||
import { type TenantInfo } from '@logto/schemas/models';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { AdminConsoleKey } from '@logto/phrases';
|
||||
import { TenantTag } from '@logto/schemas';
|
||||
import { TenantTag } from '@logto/schemas/models';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type PatchTenant, type TenantInfo, TenantTag } from '@logto/schemas';
|
||||
import { type TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
@ -49,7 +49,7 @@ function TenantBasicSettings() {
|
|||
reset({ profile: { name, tag } });
|
||||
}, [currentTenant, reset]);
|
||||
|
||||
const saveData = async (data: PatchTenant) => {
|
||||
const saveData = async (data: { name?: string; tag?: TenantTag }) => {
|
||||
try {
|
||||
const { name, tag } = await cloudApi
|
||||
.patch(`/api/tenants/${currentTenantId}`, {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { type TenantInfo } from '@logto/schemas';
|
||||
import { type TenantModel } from '@logto/schemas/models';
|
||||
|
||||
export type TenantSettingsForm = {
|
||||
profile: Pick<TenantInfo, 'name' | 'tag'>;
|
||||
profile: Pick<TenantModel, 'name' | 'tag'>;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { CreateTenant, TenantInfo, TenantTag } from '@logto/schemas';
|
||||
import type { TenantInfo, TenantTag } from '@logto/schemas/models';
|
||||
|
||||
import { cloudApi } from './api.js';
|
||||
|
||||
export const createTenant = async (
|
||||
accessToken: string,
|
||||
payload: Required<Pick<CreateTenant, 'name' | 'tag'>>
|
||||
payload: { name: string; tag: TenantTag }
|
||||
) => {
|
||||
return cloudApi
|
||||
.extend({
|
||||
|
|
|
@ -11,11 +11,9 @@ import {
|
|||
type Resource,
|
||||
type Scope,
|
||||
type Role,
|
||||
TenantTag,
|
||||
type TenantInfo,
|
||||
type CreateTenant,
|
||||
defaultTenantId,
|
||||
} from '@logto/schemas';
|
||||
import { TenantTag, type TenantInfo } from '@logto/schemas/models';
|
||||
import { GlobalValues } from '@logto/shared';
|
||||
import { appendPath } from '@silverhand/essentials';
|
||||
|
||||
|
@ -40,7 +38,7 @@ describe('Tenant APIs', () => {
|
|||
for (const [payload, tenant] of [
|
||||
[payload1, tenant1],
|
||||
[payload2, tenant2],
|
||||
] as Array<[Required<Pick<CreateTenant, 'name' | 'tag'>>, TenantInfo]>) {
|
||||
] as Array<[{ name: string; tag: TenantTag }, TenantInfo]>) {
|
||||
expect(tenant).toHaveProperty('id');
|
||||
expect(tenant).toHaveProperty('tag', payload.tag);
|
||||
expect(tenant).toHaveProperty('name', payload.name);
|
||||
|
@ -82,7 +80,7 @@ describe('Tenant APIs', () => {
|
|||
[payload1, tenant1],
|
||||
[payload2, tenant2],
|
||||
[payload3, tenant3],
|
||||
] as Array<[Required<Pick<CreateTenant, 'name' | 'tag'>>, TenantInfo]>) {
|
||||
] as Array<[{ name: string; tag: TenantTag }, TenantInfo]>) {
|
||||
expect(tenant).toHaveProperty('id');
|
||||
expect(tenant).toHaveProperty('tag', payload.tag);
|
||||
expect(tenant).toHaveProperty('name', payload.name);
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
"@logto/phrases": "workspace:^1.4.0",
|
||||
"@logto/phrases-ui": "workspace:^1.2.0",
|
||||
"@logto/shared": "workspace:^2.0.0",
|
||||
"@withtyped/server": "^0.11.0",
|
||||
"@withtyped/server": "^0.11.1",
|
||||
"zod": "^3.20.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { createModel } from '@withtyped/server/model';
|
||||
import type { InferModelType } from '@withtyped/server/model';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TenantTag } from '../index.js';
|
||||
export enum TenantTag {
|
||||
Development = 'development',
|
||||
Staging = 'staging',
|
||||
Production = 'production',
|
||||
}
|
||||
|
||||
export const Tenants = createModel(/* sql */ `
|
||||
/* init_order = 0 */
|
||||
|
@ -16,4 +22,14 @@ export const Tenants = createModel(/* sql */ `
|
|||
unique (db_user)
|
||||
);
|
||||
/* no_after_each */
|
||||
`);
|
||||
`)
|
||||
.extend('tag', z.nativeEnum(TenantTag))
|
||||
.extend('createdAt', { readonly: true });
|
||||
|
||||
export type TenantModel = InferModelType<typeof Tenants>;
|
||||
|
||||
export type TenantInfo = Pick<TenantModel, 'id' | 'name' | 'tag'> & { indicator: string };
|
||||
|
||||
export const tenantInfoGuard: z.ZodType<TenantInfo> = Tenants.guard('model')
|
||||
.pick({ id: true, name: true, tag: true })
|
||||
.extend({ indicator: z.string() });
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import type { InferModelType } from '@withtyped/server/model';
|
||||
|
||||
import type { Tenants } from '../models/tenants.js';
|
||||
export type { TenantModel } from '../models/tenants.js';
|
||||
|
||||
export const defaultTenantId = 'default';
|
||||
export const adminTenantId = 'admin';
|
||||
|
||||
export type TenantModel = InferModelType<typeof Tenants>;
|
||||
|
|
|
@ -11,7 +11,6 @@ export * from './role.js';
|
|||
export * from './verification-code.js';
|
||||
export * from './application.js';
|
||||
export * from './system.js';
|
||||
export * from './tenant.js';
|
||||
export * from './user-assets.js';
|
||||
export * from './hook.js';
|
||||
export * from './service-log.js';
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { type TenantModel } from '../seeds/tenant.js';
|
||||
|
||||
export enum TenantTag {
|
||||
Development = 'development',
|
||||
Staging = 'staging',
|
||||
Production = 'production',
|
||||
}
|
||||
|
||||
export type PatchTenant = Partial<Pick<TenantModel, 'name' | 'tag'>>;
|
||||
export type CreateTenant = Pick<TenantModel, 'id' | 'dbUser' | 'dbUserPassword'> &
|
||||
PatchTenant & { createdAt?: number };
|
||||
|
||||
export const createTenantGuard = z.object({
|
||||
id: z.string(),
|
||||
dbUser: z.string(),
|
||||
dbUserPassword: z.string(),
|
||||
name: z.string().optional(),
|
||||
tag: z.nativeEnum(TenantTag).optional(),
|
||||
createdAt: z.number().optional(),
|
||||
});
|
||||
|
||||
export type TenantInfo = Pick<TenantModel, 'id' | 'name' | 'tag'> & { indicator: string };
|
||||
|
||||
export const tenantInfoGuard: z.ZodType<TenantInfo> = createTenantGuard
|
||||
.pick({ id: true, name: true, tag: true })
|
||||
.extend({ indicator: z.string() })
|
||||
.required();
|
|
@ -253,10 +253,10 @@ importers:
|
|||
version: 2.5.0
|
||||
'@withtyped/postgres':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(@withtyped/server@0.11.0)
|
||||
version: 0.11.0(@withtyped/server@0.11.1)
|
||||
'@withtyped/server':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(zod@3.20.2)
|
||||
specifier: ^0.11.1
|
||||
version: 0.11.1(zod@3.20.2)
|
||||
accepts:
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8
|
||||
|
@ -3556,8 +3556,8 @@ importers:
|
|||
specifier: workspace:^2.0.0
|
||||
version: link:../shared
|
||||
'@withtyped/server':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(zod@3.20.2)
|
||||
specifier: ^0.11.1
|
||||
version: 0.11.1(zod@3.20.2)
|
||||
zod:
|
||||
specifier: ^3.20.2
|
||||
version: 3.20.2
|
||||
|
@ -9808,21 +9808,21 @@ packages:
|
|||
eslint-visitor-keys: 3.3.0
|
||||
dev: true
|
||||
|
||||
/@withtyped/postgres@0.11.0(@withtyped/server@0.11.0):
|
||||
/@withtyped/postgres@0.11.0(@withtyped/server@0.11.1):
|
||||
resolution: {integrity: sha512-PHnx6ake/MDdyy4sZXS/7l5XNBtjqlPSqSHrlmCYUXYxUV0sHSrXECKxX7deAvWZtcHVh9VaWEpiQBhFS06Vig==}
|
||||
peerDependencies:
|
||||
'@withtyped/server': ^0.11.0
|
||||
dependencies:
|
||||
'@types/pg': 8.6.6
|
||||
'@withtyped/server': 0.11.0(zod@3.20.2)
|
||||
'@withtyped/server': 0.11.1(zod@3.20.2)
|
||||
'@withtyped/shared': 0.2.0
|
||||
pg: 8.8.0
|
||||
transitivePeerDependencies:
|
||||
- pg-native
|
||||
dev: false
|
||||
|
||||
/@withtyped/server@0.11.0(zod@3.20.2):
|
||||
resolution: {integrity: sha512-InF3g9uv63ImK/sTIywcwX9tg2KnyFOlYsqeVI1WDjV1eTdzVxRJInD8iA08+U10S/dhBimukSx2Bfjwt8MWiw==}
|
||||
/@withtyped/server@0.11.1(zod@3.20.2):
|
||||
resolution: {integrity: sha512-9b/rH+6S8rKlgVTuDfDOSXiVjOBz5yd4EIP6qswncIJvA+i9j1ykR0GIBGuJSQAr7SWrSNCILUVK+oDTiWzJ6g==}
|
||||
peerDependencies:
|
||||
zod: ^3.19.1
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in a new issue