From 4256e89a998be862458a5afd60623bcfdebc1a65 Mon Sep 17 00:00:00 2001 From: mertmit Date: Fri, 23 Jan 2026 08:34:16 +0000 Subject: [PATCH] refactor: sandbox to managed app backend --- packages/nocodb-sdk/src/lib/globals.ts | 4 +- .../controllers/internal/operationScopes.ts | 34 +-- packages/nocodb/src/helpers/NcErrorV1.ts | 2 +- packages/nocodb/src/meta/meta.service.ts | 6 +- .../meta/migrations/XcMigrationSourcev0.ts | 4 + .../meta/migrations/v0/nc_015_managed_apps.ts | 262 ++++++++++++++++++ packages/nocodb/src/models/Base.ts | 34 +-- packages/nocodb/src/utils/globals.ts | 21 +- 8 files changed, 318 insertions(+), 49 deletions(-) create mode 100644 packages/nocodb/src/meta/migrations/v0/nc_015_managed_apps.ts diff --git a/packages/nocodb-sdk/src/lib/globals.ts b/packages/nocodb-sdk/src/lib/globals.ts index c10a016209..a2709d0f6e 100644 --- a/packages/nocodb-sdk/src/lib/globals.ts +++ b/packages/nocodb-sdk/src/lib/globals.ts @@ -370,12 +370,12 @@ export enum BaseVersion { V3 = 3, } -export enum SandboxVersionStatus { +export enum ManagedAppVersionStatus { DRAFT = 'draft', PUBLISHED = 'published', } -export enum SandboxVisibility { +export enum ManagedAppVisibility { PUBLIC = 'public', PRIVATE = 'private', UNLISTED = 'unlisted', diff --git a/packages/nocodb/src/controllers/internal/operationScopes.ts b/packages/nocodb/src/controllers/internal/operationScopes.ts index cbb421c354..d845f5cfd6 100644 --- a/packages/nocodb/src/controllers/internal/operationScopes.ts +++ b/packages/nocodb/src/controllers/internal/operationScopes.ts @@ -125,21 +125,21 @@ export const OPERATION_SCOPES = { extensionUpdate: 'base', extensionDelete: 'base', - // Sandbox Operations - sandboxStoreList: 'org', - sandboxList: 'workspace', - sandboxGet: 'org', - sandboxCreate: 'workspace', - sandboxUpdate: 'base', - sandboxDelete: 'base', - sandboxPublish: 'base', - sandboxCreateDraft: 'base', - sandboxUnpublish: 'base', - sandboxInstall: 'workspace', - sandboxGetUpdates: 'base', - sandboxVersionsList: 'base', - sandboxInstallationsList: 'base', - sandboxDeployments: 'base', - sandboxVersionDeployments: 'base', - sandboxDeploymentLogs: 'base', + // Managed App Operations + managedAppStoreList: 'org', + managedAppList: 'workspace', + managedAppGet: 'org', + managedAppCreate: 'workspace', + managedAppUpdate: 'base', + managedAppDelete: 'base', + managedAppPublish: 'base', + managedAppCreateDraft: 'base', + managedAppUnpublish: 'base', + managedAppInstall: 'workspace', + managedAppGetUpdates: 'base', + managedAppVersionsList: 'base', + managedAppInstallationsList: 'base', + managedAppDeployments: 'base', + managedAppVersionDeployments: 'base', + managedAppDeploymentLogs: 'base', } as const; diff --git a/packages/nocodb/src/helpers/NcErrorV1.ts b/packages/nocodb/src/helpers/NcErrorV1.ts index 4de8f98854..acd226ebe7 100644 --- a/packages/nocodb/src/helpers/NcErrorV1.ts +++ b/packages/nocodb/src/helpers/NcErrorV1.ts @@ -138,7 +138,7 @@ export class NcErrorV1 extends NcErrorBase { schemaLocked(message?: string): never { return this.forbidden( message || - 'Schema modifications are not allowed on installed sandbox bases', + 'Schema modifications are not allowed on managed apps', ); } } diff --git a/packages/nocodb/src/meta/meta.service.ts b/packages/nocodb/src/meta/meta.service.ts index fe20b92fd5..50001b416e 100644 --- a/packages/nocodb/src/meta/meta.service.ts +++ b/packages/nocodb/src/meta/meta.service.ts @@ -131,9 +131,9 @@ export class MetaService { [MetaTable.INSTALLATIONS]: 'inst', [MetaTable.AUTOMATIONS]: 'aut', [MetaTable.AUTOMATION_EXECUTIONS]: 'auex', - [MetaTable.SANDBOXES]: 'sb', - [MetaTable.SANDBOX_VERSIONS]: 'sbv', - [MetaTable.SANDBOX_DEPLOYMENT_LOGS]: 'sbdl', + [MetaTable.MANAGED_APPS]: 'ma', + [MetaTable.MANAGED_APP_VERSIONS]: 'mav', + [MetaTable.MANAGED_APP_DEPLOYMENT_LOGS]: 'madl', }; const prefix = prefixMap[target] || 'nc'; diff --git a/packages/nocodb/src/meta/migrations/XcMigrationSourcev0.ts b/packages/nocodb/src/meta/migrations/XcMigrationSourcev0.ts index 5e3283df6e..a7294f5a8c 100644 --- a/packages/nocodb/src/meta/migrations/XcMigrationSourcev0.ts +++ b/packages/nocodb/src/meta/migrations/XcMigrationSourcev0.ts @@ -12,6 +12,7 @@ import * as nc_011_merge_workflows_scripts from './v0/nc_011_merge_workflows_scr import * as nc_012_workflow_delay from './v0/nc_012_workflow_delay'; import * as nc_013_composite_pk_missing_tables from './v0/nc_013_composite_pk_missing_tables'; import * as nc_014_sandboxes from './v0/nc_014_sandboxes'; +import * as nc_015_managed_apps from './v0/nc_015_managed_apps'; // Create a custom migration source class export default class XcMigrationSourcev0 { @@ -35,6 +36,7 @@ export default class XcMigrationSourcev0 { 'nc_012_workflow_delay', 'nc_013_composite_pk_missing_tables', 'nc_014_sandboxes', + 'nc_015_managed_apps', ]); } @@ -72,6 +74,8 @@ export default class XcMigrationSourcev0 { return nc_013_composite_pk_missing_tables; case 'nc_014_sandboxes': return nc_014_sandboxes; + case 'nc_015_managed_apps': + return nc_015_managed_apps; } } } diff --git a/packages/nocodb/src/meta/migrations/v0/nc_015_managed_apps.ts b/packages/nocodb/src/meta/migrations/v0/nc_015_managed_apps.ts new file mode 100644 index 0000000000..23f2f67721 --- /dev/null +++ b/packages/nocodb/src/meta/migrations/v0/nc_015_managed_apps.ts @@ -0,0 +1,262 @@ +import type { Knex } from 'knex'; +import { MetaTable } from '~/utils/globals'; + +const up = async (knex: Knex) => { + // Rename sandboxes table to managed_apps + await knex.schema.renameTable( + MetaTable.SANDBOXES_OLD, + MetaTable.MANAGED_APPS, + ); + + // Rename sandbox_versions table to managed_app_versions + await knex.schema.renameTable( + MetaTable.SANDBOX_VERSIONS_OLD, + MetaTable.MANAGED_APP_VERSIONS, + ); + + // Rename sandbox_deployment_logs table to managed_app_deployment_logs + await knex.schema.renameTable( + MetaTable.SANDBOX_DEPLOYMENT_LOGS_OLD, + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + ); + + // Rename columns in managed_app_versions table (formerly sandbox_versions) + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + table.renameColumn('fk_sandbox_id', 'fk_managed_app_id'); + }); + + // Rename indexes in managed_app_versions table + // Note: Index renaming varies by database. We'll drop and recreate them. + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + // Drop old indexes + table.dropUnique( + ['fk_sandbox_id', 'version'], + 'nc_sandbox_versions_unique_idx', + ); + table.dropUnique( + ['fk_sandbox_id', 'version_number'], + 'nc_sandbox_versions_number_unique_idx', + ); + table.dropIndex(['fk_sandbox_id'], 'nc_sandbox_versions_sandbox_id_idx'); + table.dropIndex( + ['fk_sandbox_id', 'status'], + 'nc_sandbox_versions_status_idx', + ); + table.dropIndex( + ['fk_sandbox_id', 'version_number'], + 'nc_sandbox_versions_ordering_idx', + ); + }); + + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + // Create new indexes with updated names + table.unique(['fk_managed_app_id', 'version'], { + indexName: 'nc_managed_app_versions_unique_idx', + }); + table.unique(['fk_managed_app_id', 'version_number'], { + indexName: 'nc_managed_app_versions_number_unique_idx', + }); + table.index( + ['fk_managed_app_id'], + 'nc_managed_app_versions_managed_app_id_idx', + ); + table.index( + ['fk_managed_app_id', 'status'], + 'nc_managed_app_versions_status_idx', + ); + table.index( + ['fk_managed_app_id', 'version_number'], + 'nc_managed_app_versions_ordering_idx', + ); + }); + + // Rename columns in managed_app_deployment_logs table (formerly sandbox_deployment_logs) + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + table.renameColumn('fk_sandbox_id', 'fk_managed_app_id'); + }, + ); + + // Rename indexes in managed_app_deployment_logs table + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + // Drop old indexes + table.dropIndex( + ['fk_sandbox_id'], + 'nc_sandbox_deployment_logs_sandbox_id_idx', + ); + }, + ); + + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + // Create new indexes + table.index( + ['fk_managed_app_id'], + 'nc_managed_app_deployment_logs_managed_app_id_idx', + ); + }, + ); + + // Rename columns in bases table (PROJECT) + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Drop old indexes first + table.dropIndex( + ['sandbox_id', 'auto_update'], + 'nc_bases_sandbox_auto_update_idx', + ); + table.dropIndex(['sandbox_version_id'], 'nc_bases_sandbox_version_id_idx'); + table.dropIndex(['sandbox_id'], 'nc_bases_sandbox_id_idx'); + table.dropIndex(['sandbox_master'], 'nc_bases_sandbox_master_idx'); + }); + + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Rename columns + table.renameColumn('sandbox_master', 'managed_app_master'); + table.renameColumn('sandbox_id', 'managed_app_id'); + table.renameColumn('sandbox_version_id', 'managed_app_version_id'); + }); + + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Create new indexes + table.index(['managed_app_master'], 'nc_bases_managed_app_master_idx'); + table.index(['managed_app_id'], 'nc_bases_managed_app_id_idx'); + table.index( + ['managed_app_version_id'], + 'nc_bases_managed_app_version_id_idx', + ); + table.index( + ['managed_app_id', 'auto_update'], + 'nc_bases_managed_app_auto_update_idx', + ); + }); +}; + +const down = async (knex: Knex) => { + // Revert columns in bases table (PROJECT) + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Drop new indexes + table.dropIndex( + ['managed_app_id', 'auto_update'], + 'nc_bases_managed_app_auto_update_idx', + ); + table.dropIndex( + ['managed_app_version_id'], + 'nc_bases_managed_app_version_id_idx', + ); + table.dropIndex(['managed_app_id'], 'nc_bases_managed_app_id_idx'); + table.dropIndex(['managed_app_master'], 'nc_bases_managed_app_master_idx'); + }); + + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Rename columns back + table.renameColumn('managed_app_version_id', 'sandbox_version_id'); + table.renameColumn('managed_app_id', 'sandbox_id'); + table.renameColumn('managed_app_master', 'sandbox_master'); + }); + + await knex.schema.alterTable(MetaTable.PROJECT, (table) => { + // Recreate old indexes + table.index(['sandbox_master'], 'nc_bases_sandbox_master_idx'); + table.index(['sandbox_id'], 'nc_bases_sandbox_id_idx'); + table.index(['sandbox_version_id'], 'nc_bases_sandbox_version_id_idx'); + table.index( + ['sandbox_id', 'auto_update'], + 'nc_bases_sandbox_auto_update_idx', + ); + }); + + // Revert indexes in managed_app_deployment_logs table + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + table.dropIndex( + ['fk_managed_app_id'], + 'nc_managed_app_deployment_logs_managed_app_id_idx', + ); + }, + ); + + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + table.index( + ['fk_sandbox_id'], + 'nc_sandbox_deployment_logs_sandbox_id_idx', + ); + }, + ); + + // Revert columns in managed_app_deployment_logs table + await knex.schema.alterTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + (table) => { + table.renameColumn('fk_managed_app_id', 'fk_sandbox_id'); + }, + ); + + // Revert indexes in managed_app_versions table + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + table.dropIndex( + ['fk_managed_app_id', 'version_number'], + 'nc_managed_app_versions_ordering_idx', + ); + table.dropIndex( + ['fk_managed_app_id', 'status'], + 'nc_managed_app_versions_status_idx', + ); + table.dropIndex( + ['fk_managed_app_id'], + 'nc_managed_app_versions_managed_app_id_idx', + ); + table.dropUnique( + ['fk_managed_app_id', 'version_number'], + 'nc_managed_app_versions_number_unique_idx', + ); + table.dropUnique( + ['fk_managed_app_id', 'version'], + 'nc_managed_app_versions_unique_idx', + ); + }); + + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + table.unique(['fk_sandbox_id', 'version'], { + indexName: 'nc_sandbox_versions_unique_idx', + }); + table.unique(['fk_sandbox_id', 'version_number'], { + indexName: 'nc_sandbox_versions_number_unique_idx', + }); + table.index(['fk_sandbox_id'], 'nc_sandbox_versions_sandbox_id_idx'); + table.index(['fk_sandbox_id', 'status'], 'nc_sandbox_versions_status_idx'); + table.index( + ['fk_sandbox_id', 'version_number'], + 'nc_sandbox_versions_ordering_idx', + ); + }); + + // Revert columns in managed_app_versions table + await knex.schema.alterTable(MetaTable.MANAGED_APP_VERSIONS, (table) => { + table.renameColumn('fk_managed_app_id', 'fk_sandbox_id'); + }); + + // Rename tables back + await knex.schema.renameTable( + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, + MetaTable.SANDBOX_DEPLOYMENT_LOGS_OLD, + ); + + await knex.schema.renameTable( + MetaTable.MANAGED_APP_VERSIONS, + MetaTable.SANDBOX_VERSIONS_OLD, + ); + + await knex.schema.renameTable( + MetaTable.MANAGED_APPS, + MetaTable.SANDBOXES_OLD, + ); +}; + +export { up, down }; diff --git a/packages/nocodb/src/models/Base.ts b/packages/nocodb/src/models/Base.ts index 942ea35641..8974a4d720 100644 --- a/packages/nocodb/src/models/Base.ts +++ b/packages/nocodb/src/models/Base.ts @@ -54,12 +54,12 @@ export default class Base implements BaseType { roles?: string; fk_custom_url_id?: string; - // sandbox props - sandbox_master?: boolean; // Is this base a sandbox master? - sandbox_id?: string; // Points to SANDBOXES (for both master and installed instances) - sandbox_version_id?: string; // Current version ID from SANDBOX_VERSIONS + // managed app props + managed_app_master?: boolean; // Is this base a managed app master? + managed_app_id?: string; // Points to MANAGED_APPS (for both master and installed instances) + managed_app_version_id?: string; // Current version ID from MANAGED_APP_VERSIONS auto_update?: boolean; // For installed instances: auto-update to new published versions - sandbox_schema_locked?: boolean; // Computed: whether schema modifications are allowed + managed_app_schema_locked?: boolean; // Computed: whether schema modifications are allowed constructor(base: Partial) { Object.assign(this, base); @@ -88,9 +88,9 @@ export default class Base implements BaseType { 'color', 'order', 'version', - 'sandbox_master', - 'sandbox_id', - 'sandbox_version_id', + 'managed_app_master', + 'managed_app_id', + 'managed_app_version_id', 'auto_update', ]); @@ -289,9 +289,9 @@ export default class Base implements BaseType { } const base = this.castType(baseData); - // Compute sandbox_schema_locked - if (base && base.sandbox_id) { - base.sandbox_schema_locked = await this.computeSchemaLocked(base); + // Compute managed_app_schema_locked + if (base && base.managed_app_id) { + base.managed_app_schema_locked = await this.computeSchemaLocked(base); } return base; @@ -370,9 +370,9 @@ export default class Base implements BaseType { if (baseData) { const base = this.castType(baseData); - // Compute sandbox_schema_locked - if (base.sandbox_id) { - base.sandbox_schema_locked = await this.computeSchemaLocked(base); + // Compute managed_app_schema_locked + if (base.managed_app_id) { + base.managed_app_schema_locked = await this.computeSchemaLocked(base); } await base.getSources(includeConfig, ncMeta); @@ -471,9 +471,9 @@ export default class Base implements BaseType { 'password', 'roles', 'version', - 'sandbox_master', - 'sandbox_id', - 'sandbox_version_id', + 'managed_app_master', + 'managed_app_id', + 'managed_app_version_id', 'auto_update', ]); diff --git a/packages/nocodb/src/utils/globals.ts b/packages/nocodb/src/utils/globals.ts index 94f1553a5e..2c8b416f79 100644 --- a/packages/nocodb/src/utils/globals.ts +++ b/packages/nocodb/src/utils/globals.ts @@ -102,9 +102,12 @@ export enum MetaTable { AUTOMATION_EXECUTIONS = 'nc_automation_executions', DEPENDENCY_TRACKER = 'nc_dependency_tracker', INSTALLATIONS = 'nc_installations', - SANDBOXES = 'nc_sandboxes', - SANDBOX_VERSIONS = 'nc_sandbox_versions', - SANDBOX_DEPLOYMENT_LOGS = 'nc_sandbox_deployment_logs', + SANDBOXES_OLD = 'nc_sandboxes', + SANDBOX_VERSIONS_OLD = 'nc_sandbox_versions', + SANDBOX_DEPLOYMENT_LOGS_OLD = 'nc_sandbox_deployment_logs', + MANAGED_APPS = 'nc_managed_apps', + MANAGED_APP_VERSIONS = 'nc_managed_app_versions', + MANAGED_APP_DEPLOYMENT_LOGS = 'nc_managed_app_deployment_logs', } export const BaseRelatedMetaTables = [ @@ -326,9 +329,9 @@ export enum CacheScope { DEPENDENCY_TRACKER = 'nc_dependency_tracker', INSTALLATION = 'installation', INSTALLATION_ALIAS = 'installationAlias', - SANDBOX = 'sandbox', - SANDBOX_VERSION = 'sandboxVersion', - SANDBOX_DEPLOYMENT_LOG = 'sandboxDeploymentLog', + MANAGED_APP = 'managedApp', + MANAGED_APP_VERSION = 'managedAppVersion', + MANAGED_APP_DEPLOYMENT_LOG = 'managedAppDeploymentLog', SUBSCRIPTIONS_ALIAS = 'subscriptionsAlias', } @@ -377,9 +380,9 @@ export const RootScopeTables = { MetaTable.JOBS, MetaTable.FILE_REFERENCES, MetaTable.DATA_REFLECTION, - MetaTable.SANDBOXES, - MetaTable.SANDBOX_VERSIONS, - MetaTable.SANDBOX_DEPLOYMENT_LOGS, + MetaTable.MANAGED_APPS, + MetaTable.MANAGED_APP_VERSIONS, + MetaTable.MANAGED_APP_DEPLOYMENT_LOGS, // Temporarily added need to be discussed within team MetaTable.AUDIT, MetaTable.CUSTOM_URLS,