0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor: use orgsLimit instead of orgsEnabled as org quota key (#6570)

* refactor: use orgsLimit instead of orgsEnabled as org quota key

* refactor: implement getUsageByKey method

* chore: undo logto email connector dependency update
This commit is contained in:
Darcy Ye 2024-09-12 11:17:43 +08:00 committed by GitHub
parent a6178f45e2
commit 343602027d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 112 additions and 54 deletions

View file

@ -27,7 +27,7 @@
"devDependencies": { "devDependencies": {
"@fontsource/roboto-mono": "^5.0.0", "@fontsource/roboto-mono": "^5.0.0",
"@jest/types": "^29.5.0", "@jest/types": "^29.5.0",
"@logto/cloud": "0.2.5-20fd0a2", "@logto/cloud": "0.2.5-91ab76c",
"@logto/connector-kit": "workspace:^4.0.0", "@logto/connector-kit": "workspace:^4.0.0",
"@logto/core-kit": "workspace:^2.5.0", "@logto/core-kit": "workspace:^2.5.0",
"@logto/elements": "workspace:^0.0.0", "@logto/elements": "workspace:^0.0.0",

View file

@ -20,11 +20,23 @@ export type NewSubscriptionUsageResponse = GuardedResponse<
GetRoutes['/api/tenants/:tenantId/subscription-usage'] GetRoutes['/api/tenants/:tenantId/subscription-usage']
>; >;
/** The response of `GET /api/tenants/my/subscription/quota` has the same response type. */ /** The response of `GET /api/tenants/my/subscription/quota` has the same response type. */
export type NewSubscriptionQuota = NewSubscriptionUsageResponse['quota']; export type NewSubscriptionQuota = Omit<
NewSubscriptionUsageResponse['quota'],
// Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the quota keys for now to avoid confusion.
'organizationsEnabled'
>;
/** The response of `GET /api/tenants/my/subscription/usage` has the same response type. */ /** The response of `GET /api/tenants/my/subscription/usage` has the same response type. */
export type NewSubscriptionCountBasedUsage = NewSubscriptionUsageResponse['usage']; export type NewSubscriptionCountBasedUsage = Omit<
NewSubscriptionUsageResponse['usage'],
// Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the usage keys for now to avoid confusion.
'organizationsEnabled'
>;
export type NewSubscriptionResourceScopeUsage = NewSubscriptionUsageResponse['resources']; export type NewSubscriptionResourceScopeUsage = NewSubscriptionUsageResponse['resources'];
export type NewSubscriptionRoleScopeUsage = NewSubscriptionUsageResponse['roles']; export type NewSubscriptionRoleScopeUsage = Omit<
NewSubscriptionUsageResponse['roles'],
// Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the quota keys for now to avoid confusion.
'organizationsEnabled'
>;
export type NewSubscriptionPeriodicUsage = GuardedResponse< export type NewSubscriptionPeriodicUsage = GuardedResponse<
GetRoutes['/api/tenants/:tenantId/subscription/periodic-usage'] GetRoutes['/api/tenants/:tenantId/subscription/periodic-usage']

View file

@ -4,7 +4,10 @@ import classNames from 'classnames';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useContext, useMemo } from 'react'; import { useContext, useMemo } from 'react';
import { type NewSubscriptionPeriodicUsage } from '@/cloud/types/router'; import {
type NewSubscriptionPeriodicUsage,
type NewSubscriptionCountBasedUsage,
} from '@/cloud/types/router';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import { TenantsContext } from '@/contexts/TenantsProvider'; import { TenantsContext } from '@/contexts/TenantsProvider';
import DynamicT from '@/ds-components/DynamicT'; import DynamicT from '@/ds-components/DynamicT';
@ -12,12 +15,41 @@ import { formatPeriod } from '@/utils/subscription';
import PlanUsageCard, { type Props as PlanUsageCardProps } from './PlanUsageCard'; import PlanUsageCard, { type Props as PlanUsageCardProps } from './PlanUsageCard';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { usageKeys, usageKeyPriceMap, usageKeyMap, titleKeyMap, tooltipKeyMap } from './utils'; import {
type UsageKey,
usageKeys,
usageKeyPriceMap,
usageKeyMap,
titleKeyMap,
tooltipKeyMap,
} from './utils';
type Props = { type Props = {
readonly periodicUsage?: NewSubscriptionPeriodicUsage; readonly periodicUsage?: NewSubscriptionPeriodicUsage;
}; };
const getUsageByKey = (
key: keyof UsageKey,
{
periodicUsage,
countBasedUsage,
}: {
periodicUsage: NewSubscriptionPeriodicUsage;
countBasedUsage: NewSubscriptionCountBasedUsage;
}
) => {
if (key === 'mauLimit' || key === 'tokenLimit') {
return periodicUsage[key];
}
// Show enabled status for organization feature.
if (key === 'organizationsLimit') {
return countBasedUsage[key] > 0;
}
return countBasedUsage[key];
};
function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) { function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) {
const { const {
currentSubscriptionQuota, currentSubscriptionQuota,
@ -59,10 +91,7 @@ function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) {
isAddOnAvailable || (onlyShowPeriodicUsage && (key === 'mauLimit' || key === 'tokenLimit')) isAddOnAvailable || (onlyShowPeriodicUsage && (key === 'mauLimit' || key === 'tokenLimit'))
) )
.map((key) => ({ .map((key) => ({
usage: usage: getUsageByKey(key, { periodicUsage, countBasedUsage: currentSubscriptionUsage }),
key === 'mauLimit' || key === 'tokenLimit'
? periodicUsage[key]
: currentSubscriptionUsage[key],
usageKey: `subscription.usage.${usageKeyMap[key]}`, usageKey: `subscription.usage.${usageKeyMap[key]}`,
titleKey: `subscription.usage.${titleKeyMap[key]}`, titleKey: `subscription.usage.${titleKeyMap[key]}`,
unitPrice: usageKeyPriceMap[key], unitPrice: usageKeyPriceMap[key],

View file

@ -12,10 +12,10 @@ import {
hooksAddOnUnitPrice, hooksAddOnUnitPrice,
} from '@/consts/subscriptions'; } from '@/consts/subscriptions';
type UsageKey = Pick< export type UsageKey = Pick<
NewSubscriptionQuota, NewSubscriptionQuota,
| 'mauLimit' | 'mauLimit'
| 'organizationsEnabled' | 'organizationsLimit'
| 'mfaEnabled' | 'mfaEnabled'
| 'enterpriseSsoLimit' | 'enterpriseSsoLimit'
| 'resourcesLimit' | 'resourcesLimit'
@ -28,7 +28,7 @@ type UsageKey = Pick<
// We decide not to show `hooksLimit` usage in console for now. // We decide not to show `hooksLimit` usage in console for now.
export const usageKeys: Array<keyof UsageKey> = [ export const usageKeys: Array<keyof UsageKey> = [
'mauLimit', 'mauLimit',
'organizationsEnabled', 'organizationsLimit',
'mfaEnabled', 'mfaEnabled',
'enterpriseSsoLimit', 'enterpriseSsoLimit',
'resourcesLimit', 'resourcesLimit',
@ -39,7 +39,7 @@ export const usageKeys: Array<keyof UsageKey> = [
export const usageKeyPriceMap: Record<keyof UsageKey, number> = { export const usageKeyPriceMap: Record<keyof UsageKey, number> = {
mauLimit: 0, mauLimit: 0,
organizationsEnabled: organizationAddOnUnitPrice, organizationsLimit: organizationAddOnUnitPrice,
mfaEnabled: mfaAddOnUnitPrice, mfaEnabled: mfaAddOnUnitPrice,
enterpriseSsoLimit: enterpriseSsoAddOnUnitPrice, enterpriseSsoLimit: enterpriseSsoAddOnUnitPrice,
resourcesLimit: resourceAddOnUnitPrice, resourcesLimit: resourceAddOnUnitPrice,
@ -54,7 +54,7 @@ export const usageKeyMap: Record<
TFuncKey<'translation', 'admin_console.subscription.usage'> TFuncKey<'translation', 'admin_console.subscription.usage'>
> = { > = {
mauLimit: 'mau.description', mauLimit: 'mau.description',
organizationsEnabled: 'organizations.description', organizationsLimit: 'organizations.description',
mfaEnabled: 'mfa.description', mfaEnabled: 'mfa.description',
enterpriseSsoLimit: 'enterprise_sso.description', enterpriseSsoLimit: 'enterprise_sso.description',
resourcesLimit: 'api_resources.description', resourcesLimit: 'api_resources.description',
@ -69,7 +69,7 @@ export const titleKeyMap: Record<
TFuncKey<'translation', 'admin_console.subscription.usage'> TFuncKey<'translation', 'admin_console.subscription.usage'>
> = { > = {
mauLimit: 'mau.title', mauLimit: 'mau.title',
organizationsEnabled: 'organizations.title', organizationsLimit: 'organizations.title',
mfaEnabled: 'mfa.title', mfaEnabled: 'mfa.title',
enterpriseSsoLimit: 'enterprise_sso.title', enterpriseSsoLimit: 'enterprise_sso.title',
resourcesLimit: 'api_resources.title', resourcesLimit: 'api_resources.title',
@ -84,7 +84,7 @@ export const tooltipKeyMap: Record<
TFuncKey<'translation', 'admin_console.subscription.usage'> TFuncKey<'translation', 'admin_console.subscription.usage'>
> = { > = {
mauLimit: 'mau.tooltip', mauLimit: 'mau.tooltip',
organizationsEnabled: 'organizations.tooltip', organizationsLimit: 'organizations.tooltip',
mfaEnabled: 'mfa.tooltip', mfaEnabled: 'mfa.tooltip',
enterpriseSsoLimit: 'enterprise_sso.tooltip', enterpriseSsoLimit: 'enterprise_sso.tooltip',
resourcesLimit: 'api_resources.tooltip', resourcesLimit: 'api_resources.tooltip',

View file

@ -27,7 +27,7 @@ export const skuQuotaItemOrder: Array<keyof LogtoSkuQuota> = [
'userRolesLimit', 'userRolesLimit',
'machineToMachineRolesLimit', 'machineToMachineRolesLimit',
'scopesPerRoleLimit', 'scopesPerRoleLimit',
'organizationsEnabled', 'organizationsLimit',
'auditLogsRetentionDays', 'auditLogsRetentionDays',
'hooksLimit', 'hooksLimit',
'customJwtEnabled', 'customJwtEnabled',

View file

@ -22,7 +22,7 @@ export const skuQuotaItemPhrasesMap: Record<
auditLogsRetentionDays: 'audit_logs_retention_days.name', auditLogsRetentionDays: 'audit_logs_retention_days.name',
ticketSupportResponseTime: 'email_ticket_support.name', ticketSupportResponseTime: 'email_ticket_support.name',
mfaEnabled: 'mfa_enabled.name', mfaEnabled: 'mfa_enabled.name',
organizationsEnabled: 'organizations_enabled.name', organizationsLimit: 'organizations_enabled.name',
enterpriseSsoLimit: 'sso_enabled.name', enterpriseSsoLimit: 'sso_enabled.name',
tenantMembersLimit: 'tenant_members_limit.name', tenantMembersLimit: 'tenant_members_limit.name',
customJwtEnabled: 'custom_jwt_enabled.name', customJwtEnabled: 'custom_jwt_enabled.name',
@ -49,7 +49,7 @@ export const skuQuotaItemUnlimitedPhrasesMap: Record<
auditLogsRetentionDays: 'audit_logs_retention_days.unlimited', auditLogsRetentionDays: 'audit_logs_retention_days.unlimited',
ticketSupportResponseTime: 'email_ticket_support.unlimited', ticketSupportResponseTime: 'email_ticket_support.unlimited',
mfaEnabled: 'mfa_enabled.unlimited', mfaEnabled: 'mfa_enabled.unlimited',
organizationsEnabled: 'organizations_enabled.unlimited', organizationsLimit: 'organizations_enabled.unlimited',
enterpriseSsoLimit: 'sso_enabled.unlimited', enterpriseSsoLimit: 'sso_enabled.unlimited',
tenantMembersLimit: 'tenant_members_limit.unlimited', tenantMembersLimit: 'tenant_members_limit.unlimited',
customJwtEnabled: 'custom_jwt_enabled.unlimited', customJwtEnabled: 'custom_jwt_enabled.unlimited',
@ -76,7 +76,7 @@ export const skuQuotaItemLimitedPhrasesMap: Record<
auditLogsRetentionDays: 'audit_logs_retention_days.limited', auditLogsRetentionDays: 'audit_logs_retention_days.limited',
ticketSupportResponseTime: 'email_ticket_support.limited', ticketSupportResponseTime: 'email_ticket_support.limited',
mfaEnabled: 'mfa_enabled.limited', mfaEnabled: 'mfa_enabled.limited',
organizationsEnabled: 'organizations_enabled.limited', organizationsLimit: 'organizations_enabled.limited',
enterpriseSsoLimit: 'sso_enabled.limited', enterpriseSsoLimit: 'sso_enabled.limited',
tenantMembersLimit: 'tenant_members_limit.limited', tenantMembersLimit: 'tenant_members_limit.limited',
customJwtEnabled: 'custom_jwt_enabled.limited', customJwtEnabled: 'custom_jwt_enabled.limited',
@ -103,7 +103,7 @@ export const skuQuotaItemNotEligiblePhrasesMap: Record<
auditLogsRetentionDays: 'audit_logs_retention_days.not_eligible', auditLogsRetentionDays: 'audit_logs_retention_days.not_eligible',
ticketSupportResponseTime: 'email_ticket_support.not_eligible', ticketSupportResponseTime: 'email_ticket_support.not_eligible',
mfaEnabled: 'mfa_enabled.not_eligible', mfaEnabled: 'mfa_enabled.not_eligible',
organizationsEnabled: 'organizations_enabled.not_eligible', organizationsLimit: 'organizations_enabled.not_eligible',
enterpriseSsoLimit: 'sso_enabled.not_eligible', enterpriseSsoLimit: 'sso_enabled.not_eligible',
tenantMembersLimit: 'tenant_members_limit.not_eligible', tenantMembersLimit: 'tenant_members_limit.not_eligible',
customJwtEnabled: 'custom_jwt_enabled.not_eligible', customJwtEnabled: 'custom_jwt_enabled.not_eligible',

View file

@ -112,7 +112,7 @@ export const defaultLogtoSku: LogtoSkuResponse = {
hooksLimit: null, hooksLimit: null,
auditLogsRetentionDays: 14, auditLogsRetentionDays: 14,
mfaEnabled: true, mfaEnabled: true,
organizationsEnabled: true, organizationsLimit: null,
enterpriseSsoLimit: null, enterpriseSsoLimit: null,
thirdPartyApplicationsLimit: null, thirdPartyApplicationsLimit: null,
tenantMembersLimit: 20, tenantMembersLimit: 20,
@ -137,7 +137,7 @@ export const defaultSubscriptionQuota: NewSubscriptionQuota = {
hooksLimit: 1, hooksLimit: 1,
auditLogsRetentionDays: 3, auditLogsRetentionDays: 3,
mfaEnabled: false, mfaEnabled: false,
organizationsEnabled: false, organizationsLimit: 0,
enterpriseSsoLimit: 0, enterpriseSsoLimit: 0,
thirdPartyApplicationsLimit: 0, thirdPartyApplicationsLimit: 0,
tenantMembersLimit: 1, tenantMembersLimit: 1,
@ -157,7 +157,7 @@ export const defaultSubscriptionUsage: NewSubscriptionCountBasedUsage = {
scopesPerRoleLimit: 0, scopesPerRoleLimit: 0,
hooksLimit: 0, hooksLimit: 0,
mfaEnabled: false, mfaEnabled: false,
organizationsEnabled: false, organizationsLimit: 0,
enterpriseSsoLimit: 0, enterpriseSsoLimit: 0,
thirdPartyApplicationsLimit: 0, thirdPartyApplicationsLimit: 0,
tenantMembersLimit: 0, tenantMembersLimit: 0,

View file

@ -22,6 +22,7 @@ import TablePlaceholder from '@/ds-components/Table/TablePlaceholder';
import useDocumentationUrl from '@/hooks/use-documentation-url'; import useDocumentationUrl from '@/hooks/use-documentation-url';
import useTenantPathname from '@/hooks/use-tenant-pathname'; import useTenantPathname from '@/hooks/use-tenant-pathname';
import pageLayout from '@/scss/page-layout.module.scss'; import pageLayout from '@/scss/page-layout.module.scss';
import { isFeatureEnabled } from '@/utils/subscription';
import Introduction from '../Organizations/Introduction'; import Introduction from '../Organizations/Introduction';
@ -38,7 +39,9 @@ function OrganizationTemplate() {
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const isOrganizationsDisabled = const isOrganizationsDisabled =
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro; isCloud &&
!isFeatureEnabled(currentSubscriptionQuota.organizationsLimit) &&
planId !== ReservedPlanId.Pro;
const { navigate } = useTenantPathname(); const { navigate } = useTenantPathname();
const handleUpgradePlan = useCallback(() => { const handleUpgradePlan = useCallback(() => {

View file

@ -21,7 +21,7 @@ import useApi from '@/hooks/use-api';
import useUserPreferences from '@/hooks/use-user-preferences'; import useUserPreferences from '@/hooks/use-user-preferences';
import modalStyles from '@/scss/modal.module.scss'; import modalStyles from '@/scss/modal.module.scss';
import { trySubmitSafe } from '@/utils/form'; import { trySubmitSafe } from '@/utils/form';
import { isPaidPlan } from '@/utils/subscription'; import { isPaidPlan, isFeatureEnabled } from '@/utils/subscription';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -42,7 +42,9 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
update, update,
} = useUserPreferences(); } = useUserPreferences();
const isOrganizationsDisabled = const isOrganizationsDisabled =
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro; isCloud &&
!isFeatureEnabled(currentSubscriptionQuota.organizationsLimit) &&
planId !== ReservedPlanId.Pro;
const { const {
reset, reset,

View file

@ -15,6 +15,7 @@ import CardTitle from '@/ds-components/CardTitle';
import useDocumentationUrl from '@/hooks/use-documentation-url'; import useDocumentationUrl from '@/hooks/use-documentation-url';
import useTenantPathname from '@/hooks/use-tenant-pathname'; import useTenantPathname from '@/hooks/use-tenant-pathname';
import pageLayout from '@/scss/page-layout.module.scss'; import pageLayout from '@/scss/page-layout.module.scss';
import { isFeatureEnabled } from '@/utils/subscription';
import CreateOrganizationModal from './CreateOrganizationModal'; import CreateOrganizationModal from './CreateOrganizationModal';
import OrganizationsTable from './OrganizationsTable'; import OrganizationsTable from './OrganizationsTable';
@ -35,7 +36,9 @@ function Organizations() {
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const isOrganizationsDisabled = const isOrganizationsDisabled =
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro; isCloud &&
!isFeatureEnabled(currentSubscriptionQuota.organizationsLimit) &&
planId !== ReservedPlanId.Pro;
const upgradePlan = useCallback(() => { const upgradePlan = useCallback(() => {
navigate(subscriptionPage); navigate(subscriptionPage);

View file

@ -1,5 +1,5 @@
import { ReservedPlanId } from '@logto/schemas'; import { ReservedPlanId } from '@logto/schemas';
import { conditional, trySafe } from '@silverhand/essentials'; import { conditional, trySafe, type Nullable } from '@silverhand/essentials';
import { ResponseError } from '@withtyped/client'; import { ResponseError } from '@withtyped/client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -98,3 +98,7 @@ export const pickupFeaturedLogtoSkus = (logtoSkus: LogtoSkuResponse[]): LogtoSku
export const isPaidPlan = (planId: string, isEnterprisePlan: boolean) => export const isPaidPlan = (planId: string, isEnterprisePlan: boolean) =>
planId === ReservedPlanId.Pro || isEnterprisePlan; planId === ReservedPlanId.Pro || isEnterprisePlan;
export const isFeatureEnabled = (quota: Nullable<number>): boolean => {
return quota === null || quota > 0;
};

View file

@ -97,7 +97,7 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@logto/cloud": "0.2.5-20fd0a2", "@logto/cloud": "0.2.5-91ab76c",
"@silverhand/eslint-config": "6.0.1", "@silverhand/eslint-config": "6.0.1",
"@silverhand/ts-config": "6.0.0", "@silverhand/ts-config": "6.0.0",
"@types/adm-zip": "^0.5.5", "@types/adm-zip": "^0.5.5",

View file

@ -46,9 +46,9 @@ export default function organizationRoleRoutes<T extends ManagementApiRouter>(
ManagementApiRouterContext ManagementApiRouterContext
>(OrganizationRoles, roles, { >(OrganizationRoles, roles, {
middlewares: condArray( middlewares: condArray(
koaQuotaGuard({ key: 'organizationsEnabled', quota, methods: ['POST', 'PUT'] }), koaQuotaGuard({ key: 'organizationsLimit', quota, methods: ['POST', 'PUT'] }),
koaReportSubscriptionUpdates({ koaReportSubscriptionUpdates({
key: 'organizationsEnabled', key: 'organizationsLimit',
quota, quota,
methods: ['POST', 'PUT', 'DELETE'], methods: ['POST', 'PUT', 'DELETE'],
}) })

View file

@ -20,9 +20,9 @@ export default function organizationScopeRoutes<T extends ManagementApiRouter>(
) { ) {
const router = new SchemaRouter(OrganizationScopes, scopes, { const router = new SchemaRouter(OrganizationScopes, scopes, {
middlewares: condArray( middlewares: condArray(
koaQuotaGuard({ key: 'organizationsEnabled', quota, methods: ['POST', 'PUT'] }), koaQuotaGuard({ key: 'organizationsLimit', quota, methods: ['POST', 'PUT'] }),
koaReportSubscriptionUpdates({ koaReportSubscriptionUpdates({
key: 'organizationsEnabled', key: 'organizationsLimit',
quota, quota,
methods: ['POST', 'PUT', 'DELETE'], methods: ['POST', 'PUT', 'DELETE'],
}) })

View file

@ -31,9 +31,9 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
const router = new SchemaRouter(Organizations, organizations, { const router = new SchemaRouter(Organizations, organizations, {
middlewares: condArray( middlewares: condArray(
koaQuotaGuard({ key: 'organizationsEnabled', quota, methods: ['POST', 'PUT'] }), koaQuotaGuard({ key: 'organizationsLimit', quota, methods: ['POST', 'PUT'] }),
koaReportSubscriptionUpdates({ koaReportSubscriptionUpdates({
key: 'organizationsEnabled', key: 'organizationsLimit',
quota, quota,
methods: ['POST', 'PUT', 'DELETE'], methods: ['POST', 'PUT', 'DELETE'],
}) })

View file

@ -21,27 +21,32 @@ export type Subscription = RouteResponseType<GetRoutes['/api/tenants/:tenantId/s
*/ */
export type SubscriptionQuota = Omit< export type SubscriptionQuota = Omit<
RouteResponseType<GetRoutes['/api/tenants/:tenantId/subscription/quota']>, RouteResponseType<GetRoutes['/api/tenants/:tenantId/subscription/quota']>,
'auditLogsRetentionDays' // Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the usage keys for now to avoid confusion.
'auditLogsRetentionDays' | 'organizationsEnabled'
>; >;
/** /**
* The type of the response of the `GET /api/tenants/:tenantId/subscription/usage` endpoint. * The type of the response of the `GET /api/tenants/:tenantId/subscription/usage` endpoint.
* It is the same as the response type of `GET /api/tenants/my/subscription/usage` endpoint. * It is the same as the response type of `GET /api/tenants/my/subscription/usage` endpoint.
*/ */
export type SubscriptionUsage = RouteResponseType< export type SubscriptionUsage = Omit<
GetRoutes['/api/tenants/:tenantId/subscription/usage'] RouteResponseType<GetRoutes['/api/tenants/:tenantId/subscription/usage']>,
// Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the usage keys for now to avoid confusion.
'organizationsEnabled'
>; >;
export type ReportSubscriptionUpdatesUsageKey = RouteRequestBodyType< export type ReportSubscriptionUpdatesUsageKey = Exclude<
PostRoutes['/api/tenants/my/subscription/item-updates'] RouteRequestBodyType<PostRoutes['/api/tenants/my/subscription/item-updates']>['usageKey'],
>['usageKey']; // Since we are deprecation the `organizationsEnabled` key soon (use `organizationsLimit` instead), we exclude it from the usage keys for now to avoid confusion.
'organizationsEnabled'
>;
// Have to manually define this variable since we can only get the literal union from the @logto/cloud/routes module. // Have to manually define this variable since we can only get the literal union from the @logto/cloud/routes module.
export const allReportSubscriptionUpdatesUsageKeys = Object.freeze([ export const allReportSubscriptionUpdatesUsageKeys = Object.freeze([
'machineToMachineLimit', 'machineToMachineLimit',
'resourcesLimit', 'resourcesLimit',
'mfaEnabled', 'mfaEnabled',
'organizationsEnabled', 'organizationsLimit',
'tenantMembersLimit', 'tenantMembersLimit',
'enterpriseSsoLimit', 'enterpriseSsoLimit',
'hooksLimit', 'hooksLimit',

View file

@ -2568,8 +2568,8 @@ importers:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.5.0 version: 29.5.0
'@logto/cloud': '@logto/cloud':
specifier: 0.2.5-20fd0a2 specifier: 0.2.5-91ab76c
version: 0.2.5-20fd0a2(zod@3.23.8) version: 0.2.5-91ab76c(zod@3.23.8)
'@logto/connector-kit': '@logto/connector-kit':
specifier: workspace:^4.0.0 specifier: workspace:^4.0.0
version: link:../toolkit/connector-kit version: link:../toolkit/connector-kit
@ -3064,8 +3064,8 @@ importers:
version: 3.23.8 version: 3.23.8
devDependencies: devDependencies:
'@logto/cloud': '@logto/cloud':
specifier: 0.2.5-20fd0a2 specifier: 0.2.5-91ab76c
version: 0.2.5-20fd0a2(zod@3.23.8) version: 0.2.5-91ab76c(zod@3.23.8)
'@silverhand/eslint-config': '@silverhand/eslint-config':
specifier: 6.0.1 specifier: 6.0.1
version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3) version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3)
@ -5571,14 +5571,14 @@ packages:
'@logto/client@2.7.2': '@logto/client@2.7.2':
resolution: {integrity: sha512-jsmuDl9QpXfR3uLEMPE67tvYoL5XcjJi+4yGqucYPjd4GH6SUHp3N9skk8C/OyygnKDPLY+ttwD0LaIbpGvn+Q==} resolution: {integrity: sha512-jsmuDl9QpXfR3uLEMPE67tvYoL5XcjJi+4yGqucYPjd4GH6SUHp3N9skk8C/OyygnKDPLY+ttwD0LaIbpGvn+Q==}
'@logto/cloud@0.2.5-20fd0a2':
resolution: {integrity: sha512-j0f2RDpi/OEI59WXKnih7QeFSywNFV91PkulZdmcGa8HCRNmht94siw+LILzheg6bzwfvHU/aN4tJYL1/Px1BA==}
engines: {node: ^20.9.0}
'@logto/cloud@0.2.5-582d792': '@logto/cloud@0.2.5-582d792':
resolution: {integrity: sha512-0fIZzqwyjQguTS0a5+XbgVZlGEB/MXIf6pbuBDkHh6JHlMTJ/XH041rWX+e+nMk5N7/Xk2XXS+d2RJUWumnmpw==} resolution: {integrity: sha512-0fIZzqwyjQguTS0a5+XbgVZlGEB/MXIf6pbuBDkHh6JHlMTJ/XH041rWX+e+nMk5N7/Xk2XXS+d2RJUWumnmpw==}
engines: {node: ^20.9.0} engines: {node: ^20.9.0}
'@logto/cloud@0.2.5-91ab76c':
resolution: {integrity: sha512-t/ZVrFICVxtqw6zh6/OJ+0VYt+fl+waNz77CdAJkhxC91KFMfojm4hWNrx1qTNSTUzg+gc/2p8cbKC9cH1ngeA==}
engines: {node: ^20.9.0}
'@logto/js@4.1.4': '@logto/js@4.1.4':
resolution: {integrity: sha512-6twud1nFBQmj89/aflzej6yD1QwXfPiYmRtyYuN4a7O9OaaW3X/kJBVwjKUn5NC9IUt+rd+jXsI3QJXENfaLAw==} resolution: {integrity: sha512-6twud1nFBQmj89/aflzej6yD1QwXfPiYmRtyYuN4a7O9OaaW3X/kJBVwjKUn5NC9IUt+rd+jXsI3QJXENfaLAw==}
@ -15244,14 +15244,14 @@ snapshots:
camelcase-keys: 7.0.2 camelcase-keys: 7.0.2
jose: 5.6.3 jose: 5.6.3
'@logto/cloud@0.2.5-20fd0a2(zod@3.23.8)': '@logto/cloud@0.2.5-582d792(zod@3.23.8)':
dependencies: dependencies:
'@silverhand/essentials': 2.9.1 '@silverhand/essentials': 2.9.1
'@withtyped/server': 0.14.0(zod@3.23.8) '@withtyped/server': 0.14.0(zod@3.23.8)
transitivePeerDependencies: transitivePeerDependencies:
- zod - zod
'@logto/cloud@0.2.5-582d792(zod@3.23.8)': '@logto/cloud@0.2.5-91ab76c(zod@3.23.8)':
dependencies: dependencies:
'@silverhand/essentials': 2.9.1 '@silverhand/essentials': 2.9.1
'@withtyped/server': 0.14.0(zod@3.23.8) '@withtyped/server': 0.14.0(zod@3.23.8)