From 6a963ed4c5d0a311755354dac5e157e27f0526c9 Mon Sep 17 00:00:00 2001 From: Samat Jorobekov Date: Thu, 23 Oct 2025 14:20:33 +0300 Subject: [PATCH 1/5] Check License on Entry Get --- src/db/models/new/entry/index.ts | 9 +++++++ src/registry/plugins/common/index.ts | 4 +++ src/registry/plugins/common/setup.ts | 4 +++ .../plugins/common/utils/entry/types.ts | 11 ++++++++ .../plugins/common/utils/entry/utils.ts | 10 +++++++ src/services/new/entry/get-entry/constants.ts | 14 ++++++++++ src/services/new/entry/get-entry/index.ts | 27 +++++++++++++++++-- src/services/new/entry/get-entry/types.ts | 3 +++ 8 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/db/models/new/entry/index.ts b/src/db/models/new/entry/index.ts index 6aba8d51..f27c2485 100644 --- a/src/db/models/new/entry/index.ts +++ b/src/db/models/new/entry/index.ts @@ -2,6 +2,7 @@ import {Model} from '../../..'; import {EntryPermissions} from '../../../../services/new/entry/types'; import {CollectionModel, CollectionModelColumn} from '../collection'; import {Favorite} from '../favorite'; +import {LicenseAssignment, LicenseAssignmentColumn} from '../license-assignment'; import {RevisionModel} from '../revision'; import {Tenant, TenantColumn} from '../tenant'; import {WorkbookModel} from '../workbook'; @@ -127,6 +128,14 @@ export class Entry extends Model { to: `${Tenant.tableName}.${TenantColumn.TenantId}`, }, }, + licenseAssignment: { + relation: Model.HasOneRelation, + modelClass: LicenseAssignment, + join: { + from: `${Entry.tableName}.${EntryColumn.TenantId}`, + to: `${LicenseAssignment.tableName}.${LicenseAssignmentColumn.TenantId}`, + }, + }, }; } diff --git a/src/registry/plugins/common/index.ts b/src/registry/plugins/common/index.ts index fb337ce9..411ed48d 100644 --- a/src/registry/plugins/common/index.ts +++ b/src/registry/plugins/common/index.ts @@ -13,9 +13,11 @@ import type {WorkbookConstructor} from './entities/workbook/types'; import type {CheckColorPalettesAdmin} from './utils/color-palettes/types'; import type {CheckEmbedding} from './utils/embedding/types'; import type { + CheckLicense, GetEntryAddFormattedFieldsHook, GetEntryBeforeDbRequestHook, GetEntryResolveUserLogin, + IsLicenseRequired, IsNeedBypassEntryByKey, } from './utils/entry/types'; import type {LogEvent} from './utils/log-event/types'; @@ -42,5 +44,7 @@ export const commonPlugin = { processTenantSettings: makeFunctionTemplate(), getZitadelUserRole: makeFunctionTemplate(), checkColorPalettesAdmin: makeFunctionTemplate(), + isLicenseRequired: makeFunctionTemplate(), + checkLicense: makeFunctionTemplate(), }), }; diff --git a/src/registry/plugins/common/setup.ts b/src/registry/plugins/common/setup.ts index 2f29bf64..af6b6c01 100644 --- a/src/registry/plugins/common/setup.ts +++ b/src/registry/plugins/common/setup.ts @@ -8,9 +8,11 @@ import {Workbook} from './entities/workbook/workbook'; import {checkColorPalettesAdmin} from './utils/color-palettes/utils'; import {checkEmbedding} from './utils/embedding/utils'; import { + checkLicense, getEntryAddFormattedFieldsHook, getEntryBeforeDbRequestHook, getEntryResolveUserLogin, + isLicenseRequired, isNeedBypassEntryByKey, } from './utils/entry/utils'; import {logEvent} from './utils/log-event/utils'; @@ -31,6 +33,8 @@ export const setupCommonPlugin = () => { getEntryBeforeDbRequestHook, getEntryAddFormattedFieldsHook, getEntryResolveUserLogin, + isLicenseRequired, + checkLicense, checkEmbedding, logEvent, checkTenant, diff --git a/src/registry/plugins/common/utils/entry/types.ts b/src/registry/plugins/common/utils/entry/types.ts index 65800bdd..855e86f6 100644 --- a/src/registry/plugins/common/utils/entry/types.ts +++ b/src/registry/plugins/common/utils/entry/types.ts @@ -10,3 +10,14 @@ export type GetEntryBeforeDbRequestHook = (args: { export type GetEntryAddFormattedFieldsHook = (args: {ctx: AppContext}) => Record; export type GetEntryResolveUserLogin = (args: {ctx: AppContext}) => Promise; + +export type IsLicenseRequired = (args: {ctx: AppContext}) => boolean; + +export type CheckLicense = (args: { + ctx: AppContext; + licenseAssignment?: { + licenseAssignmentId?: string; + expiredAt?: string | null; + licenseType?: string; + }; +}) => void | Promise; diff --git a/src/registry/plugins/common/utils/entry/utils.ts b/src/registry/plugins/common/utils/entry/utils.ts index 7b4c8e60..f5f88853 100644 --- a/src/registry/plugins/common/utils/entry/utils.ts +++ b/src/registry/plugins/common/utils/entry/utils.ts @@ -1,7 +1,9 @@ import type { + CheckLicense, GetEntryAddFormattedFieldsHook, GetEntryBeforeDbRequestHook, GetEntryResolveUserLogin, + IsLicenseRequired, IsNeedBypassEntryByKey, } from './types'; @@ -12,3 +14,11 @@ export const getEntryBeforeDbRequestHook: GetEntryBeforeDbRequestHook = () => Pr export const getEntryAddFormattedFieldsHook: GetEntryAddFormattedFieldsHook = () => ({}); export const getEntryResolveUserLogin: GetEntryResolveUserLogin = () => Promise.resolve(undefined); + +export const isLicenseRequired: IsLicenseRequired = () => { + return false; +}; + +export const checkLicense: CheckLicense = async () => { + return; +}; diff --git a/src/services/new/entry/get-entry/constants.ts b/src/services/new/entry/get-entry/constants.ts index f31f01b8..f77dddda 100644 --- a/src/services/new/entry/get-entry/constants.ts +++ b/src/services/new/entry/get-entry/constants.ts @@ -1,6 +1,10 @@ import {CollectionModel, CollectionModelColumn} from '../../../../db/models/new/collection'; import {Entry, EntryColumn} from '../../../../db/models/new/entry'; import {Favorite, FavoriteColumn} from '../../../../db/models/new/favorite'; +import { + LicenseAssignment, + LicenseAssignmentColumn, +} from '../../../../db/models/new/license-assignment'; import {RevisionModel, RevisionModelColumn} from '../../../../db/models/new/revision'; import {Tenant, TenantColumn} from '../../../../db/models/new/tenant'; @@ -70,6 +74,16 @@ export const selectedCollectionColumns = collectionColumns.map( (column) => `${CollectionModel.tableName}.${column}`, ); +export const licenseAssignmentColumns = [ + LicenseAssignmentColumn.LicenseAssignmentId, + LicenseAssignmentColumn.ExpiredAt, + LicenseAssignmentColumn.LicenseType, +] as const; + +export const selectedLicenseAssignmentColumns = licenseAssignmentColumns.map( + (column) => `${LicenseAssignment.tableName}.${column}`, +); + export const ENTRY_QUERY_TIMEOUT = 3000; export const ENTITY_BINDING_QUERY_TIMEOUT = 3000; export const GET_PARENTS_QUERY_TIMEOUT = 3000; diff --git a/src/services/new/entry/get-entry/index.ts b/src/services/new/entry/get-entry/index.ts index 7ad45b0c..ad377287 100644 --- a/src/services/new/entry/get-entry/index.ts +++ b/src/services/new/entry/get-entry/index.ts @@ -19,6 +19,7 @@ import { selectedCollectionColumns, selectedEntryColumns, selectedFavoriteColumns, + selectedLicenseAssignmentColumns, selectedRevisionColumns, selectedTenantColumns, } from './constants'; @@ -86,8 +87,13 @@ export const getEntry = async ( const {isPrivateRoute, user, onlyPublic, onlyMirrored, tenantId} = ctx.get('info'); - const {getEntryBeforeDbRequestHook, checkEmbedding, getEntryResolveUserLogin} = - registry.common.functions.get(); + const { + getEntryBeforeDbRequestHook, + checkEmbedding, + getEntryResolveUserLogin, + isLicenseRequired, + checkLicense, + } = registry.common.functions.get(); let userLoginPromise: Promise = Promise.resolve(undefined); @@ -106,6 +112,10 @@ export const getEntry = async ( const graphRelations = ['workbook', 'tenant(tenantModifier)', 'collection(collectionModifier)']; + if (isLicenseRequired({ctx})) { + graphRelations.push('licenseAssignment(licenseAssignmentModifier)'); + } + if (revId) { graphRelations.push('revisions(revisionsModifier)'); } else if (branch === 'saved') { @@ -162,6 +172,15 @@ export const getEntry = async ( favoriteModifier(builder) { builder.select(selectedFavoriteColumns).where({login: userLogin}); }, + licenseAssignmentModifier(builder) { + builder + .select(selectedLicenseAssignmentColumns) + .where({ + tenantId, + userId: user.userId, + }) + .first(); + }, }) .first() .timeout(ENTRY_QUERY_TIMEOUT); @@ -180,6 +199,10 @@ export const getEntry = async ( }); } + if (isLicenseRequired({ctx})) { + await checkLicense({ctx, licenseAssignment: entry.licenseAssignment}); + } + const checkWorkbookIsolationEnabled = !isPrivateRoute && !onlyPublic && diff --git a/src/services/new/entry/get-entry/types.ts b/src/services/new/entry/get-entry/types.ts index e2eedc33..3bf86376 100644 --- a/src/services/new/entry/get-entry/types.ts +++ b/src/services/new/entry/get-entry/types.ts @@ -1,6 +1,7 @@ import {CollectionModel} from '../../../../db/models/new/collection'; import {Entry} from '../../../../db/models/new/entry'; import {Favorite} from '../../../../db/models/new/favorite'; +import {LicenseAssignment} from '../../../../db/models/new/license-assignment'; import {RevisionModel} from '../../../../db/models/new/revision'; import {Tenant} from '../../../../db/models/new/tenant'; import {WorkbookModel} from '../../../../db/models/new/workbook'; @@ -9,6 +10,7 @@ import { collectionColumns, entryColumns, favoriteColumns, + licenseAssignmentColumns, revisionColumns, tenantColumns, } from './constants'; @@ -29,4 +31,5 @@ export type SelectedEntry = Pick> & { collection?: SelectedCollection; favorite?: SelectedFavorite; tenant?: SelectedTenant; + licenseAssignment?: Pick>; }; From 4cc4ca7f4b4b2fddec138d9e9b73a1d6123f718a Mon Sep 17 00:00:00 2001 From: Samat Jorobekov Date: Thu, 23 Oct 2025 16:49:28 +0300 Subject: [PATCH 2/5] Add isLicenseRequired and checkLicense --- api/registry/types.ts | 6 +++++- src/services/new/entry/get-entry/index.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/api/registry/types.ts b/api/registry/types.ts index 8df47dde..737c7282 100644 --- a/api/registry/types.ts +++ b/api/registry/types.ts @@ -60,7 +60,11 @@ export type { GetServicePlan, ProcessTenantSettings, } from '../../src/registry/plugins/common/utils/tenant/types'; -export type {GetEntryResolveUserLogin} from '../../src/registry/plugins/common/utils/entry/types'; +export type { + GetEntryResolveUserLogin, + IsLicenseRequired, + CheckLicense, +} from '../../src/registry/plugins/common/utils/entry/types'; export type { CollectionConstructor, CollectionInstance, diff --git a/src/services/new/entry/get-entry/index.ts b/src/services/new/entry/get-entry/index.ts index ad377287..9639dc0f 100644 --- a/src/services/new/entry/get-entry/index.ts +++ b/src/services/new/entry/get-entry/index.ts @@ -112,7 +112,10 @@ export const getEntry = async ( const graphRelations = ['workbook', 'tenant(tenantModifier)', 'collection(collectionModifier)']; - if (isLicenseRequired({ctx})) { + const licenseRequired = + !isPrivateRoute && !onlyPublic && !isEmbedding && isLicenseRequired({ctx}); + + if (licenseRequired) { graphRelations.push('licenseAssignment(licenseAssignmentModifier)'); } @@ -172,6 +175,7 @@ export const getEntry = async ( favoriteModifier(builder) { builder.select(selectedFavoriteColumns).where({login: userLogin}); }, + licenseAssignmentModifier(builder) { builder .select(selectedLicenseAssignmentColumns) @@ -199,7 +203,7 @@ export const getEntry = async ( }); } - if (isLicenseRequired({ctx})) { + if (licenseRequired) { await checkLicense({ctx, licenseAssignment: entry.licenseAssignment}); } From 71d58c25043967f5c12bc6a095329c825efc8c0e Mon Sep 17 00:00:00 2001 From: Samat Jorobekov Date: Sat, 25 Oct 2025 13:03:27 +0300 Subject: [PATCH 3/5] Fixes --- .../plugins/common/utils/entry/types.ts | 3 ++- src/services/new/entry/get-entry/index.ts | 24 +++++++++++++++---- src/services/new/entry/get-entry/types.ts | 9 ++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/registry/plugins/common/utils/entry/types.ts b/src/registry/plugins/common/utils/entry/types.ts index 855e86f6..8d0b10f8 100644 --- a/src/registry/plugins/common/utils/entry/types.ts +++ b/src/registry/plugins/common/utils/entry/types.ts @@ -19,5 +19,6 @@ export type CheckLicense = (args: { licenseAssignmentId?: string; expiredAt?: string | null; licenseType?: string; + isActive?: boolean; }; -}) => void | Promise; +}) => void; diff --git a/src/services/new/entry/get-entry/index.ts b/src/services/new/entry/get-entry/index.ts index 9639dc0f..06701922 100644 --- a/src/services/new/entry/get-entry/index.ts +++ b/src/services/new/entry/get-entry/index.ts @@ -1,10 +1,12 @@ import {AppError} from '@gravity-ui/nodekit'; +import {raw} from 'objection'; import {Feature, isEnabledFeature} from '../../../../components/features'; -import {US_ERRORS} from '../../../../const'; +import {CURRENT_TIMESTAMP, US_ERRORS} from '../../../../const'; import OldEntry from '../../../../db/models/entry'; import {CollectionModelColumn} from '../../../../db/models/new/collection'; import {Entry, EntryColumn} from '../../../../db/models/new/entry'; +import {LicenseAssignmentColumnRaw} from '../../../../db/models/new/license-assignment'; import {TenantColumn} from '../../../../db/models/new/tenant'; import {WorkbookModelColumn} from '../../../../db/models/new/workbook'; import {DlsActions} from '../../../../types/models'; @@ -178,12 +180,24 @@ export const getEntry = async ( licenseAssignmentModifier(builder) { builder - .select(selectedLicenseAssignmentColumns) + .select([ + ...selectedLicenseAssignmentColumns, + raw(`coalesce(?? > ${CURRENT_TIMESTAMP}, true)`, [ + LicenseAssignmentColumnRaw.ExpiredAt, + ]).as('is_active'), + ]) .where({ tenantId, userId: user.userId, - }) - .first(); + }); + // .andWhere((qb) => { + // qb.where(raw('?? IS NULL', [LicenseAssignmentColumnRaw.ExpiredAt])) + // .orWhere( + // raw(`?? > ${CURRENT_TIMESTAMP}`, [ + // LicenseAssignmentColumnRaw.ExpiredAt, + // ]), + // ); + // }); }, }) .first() @@ -204,7 +218,7 @@ export const getEntry = async ( } if (licenseRequired) { - await checkLicense({ctx, licenseAssignment: entry.licenseAssignment}); + checkLicense({ctx, licenseAssignment: entry.licenseAssignment}); } const checkWorkbookIsolationEnabled = diff --git a/src/services/new/entry/get-entry/types.ts b/src/services/new/entry/get-entry/types.ts index 3bf86376..a0f123f3 100644 --- a/src/services/new/entry/get-entry/types.ts +++ b/src/services/new/entry/get-entry/types.ts @@ -31,5 +31,12 @@ export type SelectedEntry = Pick> & { collection?: SelectedCollection; favorite?: SelectedFavorite; tenant?: SelectedTenant; - licenseAssignment?: Pick>; + licenseAssignment?: SelectedLicenseAssignment; +}; + +export type SelectedLicenseAssignment = Pick< + LicenseAssignment, + ArrayElement +> & { + isActive: boolean; }; From 5fd519e33889418c703c9f8be3a510046cacb714 Mon Sep 17 00:00:00 2001 From: Samat Jorobekov Date: Sat, 25 Oct 2025 13:18:03 +0300 Subject: [PATCH 4/5] Fixes --- src/services/new/entry/get-entry/constants.ts | 2 +- src/services/new/entry/get-entry/index.ts | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/services/new/entry/get-entry/constants.ts b/src/services/new/entry/get-entry/constants.ts index f77dddda..6289b962 100644 --- a/src/services/new/entry/get-entry/constants.ts +++ b/src/services/new/entry/get-entry/constants.ts @@ -76,7 +76,7 @@ export const selectedCollectionColumns = collectionColumns.map( export const licenseAssignmentColumns = [ LicenseAssignmentColumn.LicenseAssignmentId, - LicenseAssignmentColumn.ExpiredAt, + LicenseAssignmentColumn.ExpiresAt, LicenseAssignmentColumn.LicenseType, ] as const; diff --git a/src/services/new/entry/get-entry/index.ts b/src/services/new/entry/get-entry/index.ts index 06701922..92b67bb3 100644 --- a/src/services/new/entry/get-entry/index.ts +++ b/src/services/new/entry/get-entry/index.ts @@ -183,21 +183,13 @@ export const getEntry = async ( .select([ ...selectedLicenseAssignmentColumns, raw(`coalesce(?? > ${CURRENT_TIMESTAMP}, true)`, [ - LicenseAssignmentColumnRaw.ExpiredAt, + LicenseAssignmentColumnRaw.ExpiresAt, ]).as('is_active'), ]) .where({ tenantId, userId: user.userId, }); - // .andWhere((qb) => { - // qb.where(raw('?? IS NULL', [LicenseAssignmentColumnRaw.ExpiredAt])) - // .orWhere( - // raw(`?? > ${CURRENT_TIMESTAMP}`, [ - // LicenseAssignmentColumnRaw.ExpiredAt, - // ]), - // ); - // }); }, }) .first() From 378ad791d9c1939b9e0541965860e84d113b3960 Mon Sep 17 00:00:00 2001 From: Samat Jorobekov Date: Sun, 26 Oct 2025 14:46:54 +0300 Subject: [PATCH 5/5] Fixes --- src/registry/plugins/common/utils/entry/types.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/registry/plugins/common/utils/entry/types.ts b/src/registry/plugins/common/utils/entry/types.ts index 8d0b10f8..5d4867c5 100644 --- a/src/registry/plugins/common/utils/entry/types.ts +++ b/src/registry/plugins/common/utils/entry/types.ts @@ -1,5 +1,7 @@ import type {AppContext} from '@gravity-ui/nodekit'; +import type {LicenseType} from '../../../../../db/models/new/license-assignment/types'; + export type IsNeedBypassEntryByKey = (ctx: AppContext, key?: string) => boolean; export type GetEntryBeforeDbRequestHook = (args: { @@ -16,9 +18,9 @@ export type IsLicenseRequired = (args: {ctx: AppContext}) => boolean; export type CheckLicense = (args: { ctx: AppContext; licenseAssignment?: { - licenseAssignmentId?: string; - expiredAt?: string | null; - licenseType?: string; - isActive?: boolean; + licenseAssignmentId: string; + expiresAt: string | null; + licenseType: `${LicenseType}`; + isActive: boolean; }; }) => void;