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/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..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: { @@ -10,3 +12,15 @@ 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; + expiresAt: string | null; + licenseType: `${LicenseType}`; + isActive: boolean; + }; +}) => void; 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..6289b962 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.ExpiresAt, + 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..92b67bb3 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'; @@ -19,6 +21,7 @@ import { selectedCollectionColumns, selectedEntryColumns, selectedFavoriteColumns, + selectedLicenseAssignmentColumns, selectedRevisionColumns, selectedTenantColumns, } from './constants'; @@ -86,8 +89,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 +114,13 @@ export const getEntry = async ( const graphRelations = ['workbook', 'tenant(tenantModifier)', 'collection(collectionModifier)']; + const licenseRequired = + !isPrivateRoute && !onlyPublic && !isEmbedding && isLicenseRequired({ctx}); + + if (licenseRequired) { + graphRelations.push('licenseAssignment(licenseAssignmentModifier)'); + } + if (revId) { graphRelations.push('revisions(revisionsModifier)'); } else if (branch === 'saved') { @@ -162,6 +177,20 @@ export const getEntry = async ( favoriteModifier(builder) { builder.select(selectedFavoriteColumns).where({login: userLogin}); }, + + licenseAssignmentModifier(builder) { + builder + .select([ + ...selectedLicenseAssignmentColumns, + raw(`coalesce(?? > ${CURRENT_TIMESTAMP}, true)`, [ + LicenseAssignmentColumnRaw.ExpiresAt, + ]).as('is_active'), + ]) + .where({ + tenantId, + userId: user.userId, + }); + }, }) .first() .timeout(ENTRY_QUERY_TIMEOUT); @@ -180,6 +209,10 @@ export const getEntry = async ( }); } + if (licenseRequired) { + 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..a0f123f3 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,12 @@ export type SelectedEntry = Pick> & { collection?: SelectedCollection; favorite?: SelectedFavorite; tenant?: SelectedTenant; + licenseAssignment?: SelectedLicenseAssignment; +}; + +export type SelectedLicenseAssignment = Pick< + LicenseAssignment, + ArrayElement +> & { + isActive: boolean; };