0
Fork 0
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:
Darcy Ye 2023-06-12 18:36:15 +08:00 committed by GitHub
parent d2f7c94167
commit e96e92b2df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 79 additions and 109 deletions

View file

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

View file

@ -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",

View file

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

View file

@ -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)}`)}

View file

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

View file

@ -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) => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}`, {

View file

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

View file

@ -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({

View file

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

View file

@ -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"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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