diff --git a/.gitignore b/.gitignore index cd84f0a073..9be392eaa2 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,6 @@ httpbin result .cursorignore -.claude @@ -110,6 +109,9 @@ antd.variable.css # Claude - per-branch working memory (ephemeral, local only) .claude/branches/ -# nocodb-api skill runtime state -.claude/skills/nocodb-dev-api/.state.json -/.claude/worktrees/ \ No newline at end of file +# nocodb-api skill credential store +.claude/skills/nocodb-dev-api/.credentials.json +/.claude/worktrees/ +.playwright-mcp +.playwright-cli +.claude \ No newline at end of file diff --git a/packages/noco-integrations/core/src/sync/types.ts b/packages/noco-integrations/core/src/sync/types.ts index 1eeece324f..54f8035b9b 100644 --- a/packages/noco-integrations/core/src/sync/types.ts +++ b/packages/noco-integrations/core/src/sync/types.ts @@ -370,6 +370,7 @@ export abstract class SyncIntegration extends IntegrationWrapper { async fetchOptions( _auth: AuthIntegration, _key: string, + _searchQuery?: string, ): Promise<{ label: string; value: string; diff --git a/packages/noco-integrations/core/src/types.ts b/packages/noco-integrations/core/src/types.ts index ae51ebd14d..582293bbb0 100644 --- a/packages/noco-integrations/core/src/types.ts +++ b/packages/noco-integrations/core/src/types.ts @@ -6,6 +6,7 @@ import { IntegrationsType as IntegrationType, SyncCategory, UITypes, + EntitySelectorMode, } from 'nocodb-sdk'; import type { IntegrationWrapper } from './integration'; @@ -43,4 +44,5 @@ export { IntegrationType, SyncCategory, UITypes, + EntitySelectorMode, }; diff --git a/packages/noco-integrations/scripts/build-optimized.js b/packages/noco-integrations/scripts/build-optimized.js index 6a06338d93..da8abe2824 100755 --- a/packages/noco-integrations/scripts/build-optimized.js +++ b/packages/noco-integrations/scripts/build-optimized.js @@ -1,4 +1,4 @@ -import { execSync } from 'child_process'; +import { spawn } from 'child_process'; import { createHash } from 'crypto'; import { existsSync, @@ -8,9 +8,13 @@ import { writeFileSync, } from 'fs'; import { join, resolve } from 'path'; +import { cpus } from 'os'; const CHECKSUM_FILE = '.build-checksums.json'; -const WORKSPACE_PACKAGES = ['core', ...getPackages()]; +const ALL_PACKAGES = ['core', ...getPackages()]; +const BUILD_LEVELS = computeBuildLevels(ALL_PACKAGES); +// Flatten levels → stable iteration order for checksum checks +const WORKSPACE_PACKAGES = BUILD_LEVELS.flat(); function getPackages() { try { @@ -29,6 +33,91 @@ function getPackages() { } } +/** + * Returns the @noco-integrations/* workspace dependency short-names for a package. + * e.g. { "@noco-integrations/smtp-auth": "workspace:*" } → ["smtp-auth"] + */ +function getWorkspaceDependencies(packagePath) { + const pkgJsonPath = join(packagePath, 'package.json'); + if (!existsSync(pkgJsonPath)) return []; + try { + const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; + return Object.entries(allDeps) + .filter(([name, version]) => + name.startsWith('@noco-integrations/') && + String(version).startsWith('workspace:'), + ) + .map(([name]) => name.replace('@noco-integrations/', '')); + } catch { + return []; + } +} + +/** + * Returns packages grouped into build levels via BFS. + * All packages within a level have no unbuilt dependencies — they can be built + * in parallel. Level N finishes completely before level N+1 starts. + * + * Example with 60 packages all depending only on core: + * Level 0: [core] + * Level 1: [all-60-packages] ← built in parallel + */ +function computeBuildLevels(packages) { + const nameToPath = new Map(); + for (const pkg of packages) { + const shortName = pkg === 'core' ? 'core' : pkg.replace('packages/', ''); + nameToPath.set(shortName, pkg); + } + + const inDegree = new Map(); + const dependents = new Map(); + for (const pkg of packages) { + inDegree.set(pkg, 0); + dependents.set(pkg, []); + } + for (const pkg of packages) { + for (const depName of getWorkspaceDependencies(pkg)) { + const depPath = nameToPath.get(depName); + if (depPath) { + inDegree.set(pkg, inDegree.get(pkg) + 1); + dependents.get(depPath).push(pkg); + } + } + } + + const levels = []; + const placed = new Set(); + let current = packages.filter((pkg) => inDegree.get(pkg) === 0); + current.forEach((pkg) => placed.add(pkg)); + + while (current.length > 0) { + levels.push([...current]); + const next = []; + for (const pkg of current) { + for (const dep of dependents.get(pkg) || []) { + inDegree.set(dep, inDegree.get(dep) - 1); + if (inDegree.get(dep) === 0 && !placed.has(dep)) { + next.push(dep); + placed.add(dep); + } + } + } + current = next; + } + + // Any unplaced packages have a cycle — build them last with a warning + const remaining = packages.filter((pkg) => !placed.has(pkg)); + if (remaining.length > 0) { + console.warn( + `⚠️ Cycle detected, building last: ${remaining.join(', ')}`, + ); + levels.push(remaining); + } + + return levels; +} + function getAllFiles(dir, files = []) { try { const items = readdirSync(dir); @@ -36,12 +125,7 @@ function getAllFiles(dir, files = []) { const fullPath = join(dir, item); const stat = statSync(fullPath); if (stat.isDirectory()) { - // Skip node_modules and dist directories - if ( - item !== 'node_modules' && - item !== 'dist' && - !item.startsWith('.') - ) { + if (item !== 'node_modules' && item !== 'dist' && !item.startsWith('.')) { getAllFiles(fullPath, files); } } else { @@ -62,19 +146,13 @@ function calculateChecksum(packagePath) { const files = getAllFiles(packagePath); const hash = createHash('sha256'); - - // Sort files for consistent ordering across platforms files.sort(); for (const file of files) { try { - // Add the relative file path to the hash for structure consistency const relativePath = file.replace(packagePath, '').replace(/\\/g, '/'); hash.update(relativePath); - - // Add the file contents - const content = readFileSync(file); - hash.update(content); + hash.update(readFileSync(file)); } catch { // Skip files we can't read } @@ -82,10 +160,7 @@ function calculateChecksum(packagePath) { return hash.digest('hex'); } catch (error) { - console.error( - `Error calculating checksum for ${packagePath}:`, - error.message, - ); + console.error(`Error calculating checksum for ${packagePath}:`, error.message); return null; } } @@ -120,20 +195,80 @@ function hasDistFolder(packagePath) { return existsSync(join(packagePath, 'dist')); } -function buildPackage(packagePath) { - console.log(`📦 Building ${packagePath}...`); - try { - execSync('pnpm build', { cwd: packagePath, stdio: 'inherit' }); - return true; - } catch (error) { - console.error(`❌ Failed to build ${packagePath}:`, error.message); - return false; +/** + * Builds a single package asynchronously. Output is buffered and printed + * atomically when the process exits so parallel builds don't interleave. + */ +function buildPackageAsync(packagePath) { + return new Promise((resolve) => { + const proc = spawn('pnpm', ['build'], { + cwd: packagePath, + shell: process.platform === 'win32', + }); + + const chunks = []; + proc.stdout?.on('data', (d) => chunks.push({ stream: 'out', d })); + proc.stderr?.on('data', (d) => chunks.push({ stream: 'err', d })); + + proc.on('close', (code) => { + const success = code === 0; + if (success) { + console.log(` ✅ ${packagePath}`); + } else { + console.error(` ❌ ${packagePath} (exit ${code})`); + if (chunks.length) { + console.error(` --- output for ${packagePath} ---`); + for (const { stream, d } of chunks) { + (stream === 'out' ? process.stdout : process.stderr).write(d); + } + console.error(` --- end ${packagePath} ---`); + } + } + resolve(success); + }); + + proc.on('error', (err) => { + console.error(` ❌ ${packagePath}: spawn error — ${err.message}`); + resolve(false); + }); + }); +} + +/** + * Builds an array of packages in parallel, capped at maxConcurrent workers. + * Returns { successCount, failCount }. + */ +async function buildLevel(packages, maxConcurrent) { + const queue = [...packages]; + let successCount = 0; + let failCount = 0; + + async function worker() { + while (queue.length > 0) { + const pkg = queue.shift(); + if (!pkg) break; + if (await buildPackageAsync(pkg)) { + successCount++; + } else { + failCount++; + } + } } + + await Promise.all( + Array.from({ length: Math.min(maxConcurrent, packages.length) }, worker), + ); + + return { successCount, failCount }; } async function main() { const force = process.argv.includes('--force'); const verbose = process.argv.includes('--verbose'); + const concurrencyArg = process.argv.find((a) => a.startsWith('--concurrency=')); + const maxConcurrent = concurrencyArg + ? Math.max(1, parseInt(concurrencyArg.split('=')[1], 10)) + : cpus().length; if (force) { console.log('🔄 Force rebuild requested, building all packages...'); @@ -155,16 +290,13 @@ async function main() { const currentChecksum = calculateChecksum(packagePath); if (!currentChecksum) { - console.log( - `⚠️ Could not calculate checksum for ${packagePath}, skipping...`, - ); + console.log(`⚠️ Could not calculate checksum for ${packagePath}, skipping...`); continue; } newChecksums[packagePath] = currentChecksum; - const oldChecksum = oldChecksums[packagePath]; - const hasChanged = oldChecksum !== currentChecksum; + const hasChanged = oldChecksums[packagePath] !== currentChecksum; const missingDist = !hasDistFolder(packagePath); if (force || hasChanged || missingDist) { @@ -183,47 +315,67 @@ async function main() { return; } - console.log(`\n🚀 Building ${packagesNeedingBuild.length} package(s)...`); + const packagesNeedingBuildSet = new Set(packagesNeedingBuild); + console.log( + `\n🚀 Building ${packagesNeedingBuild.length} package(s) (up to ${maxConcurrent} parallel)...`, + ); - let successCount = 0; - let failCount = 0; + let totalSuccess = 0; + let totalFail = 0; - for (const packagePath of packagesNeedingBuild) { - if (buildPackage(packagePath)) { - successCount++; - } else { - failCount++; + for (let i = 0; i < BUILD_LEVELS.length; i++) { + const toBuild = BUILD_LEVELS[i].filter((pkg) => + packagesNeedingBuildSet.has(pkg), + ); + if (toBuild.length === 0) continue; + + console.log( + `\n⚡ Level ${i} — ${toBuild.length} package(s) in parallel`, + ); + if (verbose) { + console.log(` ${toBuild.join(', ')}`); + } + + const { successCount, failCount } = await buildLevel(toBuild, maxConcurrent); + totalSuccess += successCount; + totalFail += failCount; + + if (failCount > 0) { + console.error( + `\n❌ ${failCount} package(s) failed in level ${i} — stopping build`, + ); + process.exit(1); } } - // Save checksums only for successfully built packages - if (successCount > 0) { + if (totalSuccess > 0) { saveChecksums(newChecksums); - console.log(`\n✅ Successfully built ${successCount} package(s)`); + console.log(`\n✅ Successfully built ${totalSuccess} package(s)`); } - if (failCount > 0) { - console.log(`❌ Failed to build ${failCount} package(s)`); + if (totalFail > 0) { + console.log(`❌ Failed to build ${totalFail} package(s)`); process.exit(1); } console.log('🎯 Build optimization complete!'); } -// Handle CLI arguments if (process.argv.includes('--help')) { console.log(` Usage: node scripts/build-optimized.js [options] Options: - --force Force rebuild all packages regardless of changes - --verbose Show detailed output for all packages - --help Show this help message + --force Force rebuild all packages regardless of changes + --verbose Show detailed output for all packages + --concurrency=N Max parallel builds per level (default: CPU count) + --help Show this help message Examples: - node scripts/build-optimized.js # Build only changed packages - node scripts/build-optimized.js --force # Force rebuild all packages - node scripts/build-optimized.js --verbose # Show detailed output + node scripts/build-optimized.js # Build only changed packages + node scripts/build-optimized.js --force # Force rebuild all + node scripts/build-optimized.js --concurrency=4 # Limit to 4 parallel + node scripts/build-optimized.js --verbose # Show per-package details `); process.exit(0); } diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index c8646199ac..c03a93fd86 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -2977,7 +2977,9 @@ export interface ColumnType { | 'LastModifiedBy' | 'AI' | 'Order' - | 'Meta'; + | 'Meta' + | 'Colour' + | 'UUID'; /** Is Unsigned? */ un?: BoolType; /** Is unique? */ @@ -3192,6 +3194,10 @@ export interface FilterType { fk_value_col_id?: StringOrNullType; /** Foreign Key to Link Column */ fk_link_col_id?: StringOrNullType; + /** Foreign Key to RLS Policy */ + fk_rls_policy_id?: StringOrNullType; + /** Foreign Key to Button Column */ + fk_button_col_id?: StringOrNullType; /** Unique ID */ id?: IdType; /** Is this filter grouped? */ @@ -3207,6 +3213,10 @@ export interface FilterType { * @example 1 */ order?: number; + /** Whether this filter is enabled. Disabled filters are skipped during evaluation. */ + enabled?: BoolType; + /** Foreign Key to List View Level */ + fk_level_id?: StringOrNullType; } /** @@ -3349,6 +3359,10 @@ export interface FilterReqType { logical_op?: 'and' | 'not' | 'or'; /** The filter value. Can be NULL for some operators. */ value?: any; + /** Whether this filter is enabled. Disabled filters are skipped during evaluation. */ + enabled?: BoolType; + /** Foreign Key to List View Level */ + fk_level_id?: StringOrNullType; } export interface FollowerType { @@ -3579,6 +3593,8 @@ export interface ButtonType { fk_integration_id?: string; /** AI model */ model?: string; + /** Visibility condition filters for the button */ + filters?: FilterType[]; } /** @@ -4268,7 +4284,7 @@ export interface LinkToAnotherColumnReqType { /** The title of the virtual column */ title: string; /** The type of the relationship */ - type: 'bt' | 'hm' | 'mm' | 'oo'; + type: 'bt' | 'hm' | 'mm' | 'om' | 'mo' | 'oo'; /** Abstract type of the relationship */ uidt: 'LinkToAnotherRecord' | 'Links'; /** Is this relationship virtual? */ @@ -4303,6 +4319,7 @@ export interface LinkToAnotherRecordType { base_id?: string; fk_related_source_id?: string; fk_mm_source_id?: string; + version?: number; } /** @@ -4535,7 +4552,9 @@ export interface NormalColumnRequestType { | 'LastModifiedBy' | 'AI' | 'Order' - | 'Meta'; + | 'Meta' + | 'Colour' + | 'UUID'; /** Is this column unique? */ un?: BoolType; /** Is this column unique? */ @@ -4777,6 +4796,10 @@ export interface BaseType { id: string; }[]; }[]; + /** Indicates if the base is a sandbox */ + is_sandbox?: BoolType; + /** Indicates if the base is a sandbox master */ + is_sandbox_master?: BoolType; } /** @@ -5094,6 +5117,8 @@ export interface SortType { * @example p_9sx43moxhqtjm3 */ base_id?: string; + /** Foreign Key to List View Level */ + fk_level_id?: StringOrNullType; } /** @@ -5114,6 +5139,8 @@ export interface SortReqType { fk_column_id?: IdType; /** Sort direction */ direction?: 'asc' | 'desc'; + /** Foreign Key to List View Level */ + fk_level_id?: StringOrNullType; } /** @@ -5334,7 +5361,14 @@ export interface ViewType { | KanbanType | MapType | CalendarType - | (FormType & GalleryType & GridType & KanbanType & MapType & CalendarType); + | ListType + | (FormType & + GalleryType & + GridType & + KanbanType & + MapType & + CalendarType & + ListType); /** ID of view owner user */ owned_by?: IdType; /** The row coloring mode whether it is select, condition or not set */ @@ -5922,6 +5956,118 @@ export interface CustomUrlType { custom_path?: string; } +/** + * Model for List View Level + */ +export interface ListViewLevelType { + /** Unique ID for Level */ + id?: string; + /** Foreign Key to View */ + fk_view_id?: string; + /** Level number (1, 2, or 3) */ + level?: number; + /** Foreign Key to Model (table) for this level */ + fk_model_id?: string; + /** Foreign Key to Link Column connecting levels */ + fk_link_column_id?: string; + /** Enable nested records (Level 1 only) */ + enable_nested_records?: BoolType; + /** Foreign Key to Self-Link Column */ + fk_self_link_column_id?: string; + /** Wrap column headers in this level */ + wrap_headers?: BoolType; + /** Meta data for this level */ + meta?: MetaType; +} + +/** + * Model for List View + */ +export interface ListType { + /** The ID of the source that this view belongs to */ + source_id?: string; + /** Columns in this view */ + columns?: ListColumnType[]; + /** Foreign Key to View */ + fk_view_id?: string; + /** Meta data for this view */ + meta?: MetaType; + /** The order of the list */ + order?: number; + /** The ID of the base that this view belongs to */ + base_id?: string; + /** To show this view or not */ + show?: boolean; + /** Title of List View */ + title?: string; + /** Show empty parent sections */ + show_empty_parents?: BoolType; + /** Row height for this list view */ + row_height?: number; + /** Foreign Key to Prefix Column */ + fk_prefix_column_id?: StringOrNullType; + /** Levels configuration for this list view */ + levels?: ListViewLevelType[]; +} + +/** + * Model for List Column + */ +export interface ListColumnType { + /** The ID of the source */ + source_id?: string; + /** Foreign Key to Column */ + fk_column_id?: string; + /** Foreign Key to View */ + fk_view_id?: string; + /** Foreign Key to Level */ + fk_level_id?: string; + /** Unique ID of List Column */ + id?: string; + /** The order in the list of columns */ + order?: number; + /** The ID of the base */ + base_id?: string; + /** Whether to show this column or not */ + show?: number; + /** Column width */ + width?: string; +} + +/** + * Model for List View Update Request + */ +export interface ListUpdateReqType { + /** Meta data for this view */ + meta?: MetaType; + /** Show empty parent sections */ + show_empty_parents?: BoolType; + /** Row height for this list view */ + row_height?: number; + /** Foreign Key to Prefix Column */ + fk_prefix_column_id?: StringOrNullType; + /** Levels configuration for this list view */ + levels?: ListViewLevelReqType[]; +} + +/** + * Model for List View Level Request + */ +export interface ListViewLevelReqType { + /** Level number (1, 2, or 3) */ + level: number; + /** Foreign Key to Model (table) for this level */ + fk_model_id: string; + /** Foreign Key to Link Column */ + fk_link_column_id?: StringOrNullType; + /** Model for Bool */ + enable_nested_records?: BoolType; + /** Foreign Key to Self-Link Column */ + fk_self_link_column_id?: StringOrNullType; + /** Wrap column headers in this level */ + wrap_headers?: BoolType; +} + import type { AxiosInstance, AxiosRequestConfig, @@ -11568,7 +11714,7 @@ export class Api< baseName: string, tableName: string, rowId: any, - relationType: 'mm' | 'hm' | 'bt' | 'oo', + relationType: 'mm' | 'hm' | 'bt' | 'oo' | 'ln', columnName: string, query?: { /** @min 1 */ @@ -11616,7 +11762,7 @@ export class Api< baseName: string, tableName: string, rowId: any, - relationType: 'mm' | 'hm' | 'bt' | 'oo', + relationType: 'mm' | 'hm' | 'bt' | 'oo' | 'ln', columnName: string, refRowId: string, query?: { @@ -11683,7 +11829,7 @@ export class Api< baseName: string, tableName: string, rowId: any, - relationType: 'mm' | 'hm' | 'bt' | 'oo', + relationType: 'mm' | 'hm' | 'bt' | 'oo' | 'ln', columnName: string, refRowId: string, params: RequestParams = {} @@ -11723,7 +11869,7 @@ export class Api< baseName: string, tableName: string, rowId: any, - relationType: 'mm' | 'hm' | 'bt' | 'oo', + relationType: 'mm' | 'hm' | 'bt' | 'oo' | 'ln', columnName: string, query?: { /** @min 1 */ @@ -12925,7 +13071,7 @@ export class Api< dataNestedList: ( sharedViewUuid: string, rowId: any, - relationType: 'mm' | 'hm' | 'bt' | 'oo', + relationType: 'mm' | 'hm' | 'bt' | 'oo' | 'ln', columnName: string, query?: { /** Which fields to be shown */ diff --git a/packages/nocodb/Dockerfile b/packages/nocodb/Dockerfile index 51a65af878..60e3882def 100644 --- a/packages/nocodb/Dockerfile +++ b/packages/nocodb/Dockerfile @@ -50,7 +50,7 @@ RUN echo "node-linker=hoisted" > .npmrc # Install production dependencies, reduce node_modules size with modclean, # remove sqlite deps, and add execute permission to start.sh RUN pnpm install --prod --shamefully-hoist \ - && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,@linear/**,jsdom/**,formdata-polyfill/**" --run \ + && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,@linear/**,jsdom/**,formdata-polyfill/**,**/lru-cache/**,**/@asamuzakjp/**,**/cssstyle/**" --run \ && rm -rf ./node_modules/sqlite3/deps \ && chmod +x /usr/src/appEntry/start.sh diff --git a/packages/nocodb/Dockerfile.local b/packages/nocodb/Dockerfile.local index 52121e2f54..1ecbb7e9e3 100644 --- a/packages/nocodb/Dockerfile.local +++ b/packages/nocodb/Dockerfile.local @@ -35,7 +35,7 @@ RUN echo "node-linker=hoisted" > .npmrc # remove sqlite deps, and add execute permission to start.sh RUN pnpm uninstall nocodb-sdk RUN pnpm install --prod --shamefully-hoist \ - && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,@linear/**,jsdom/**,formdata-polyfill/**" --run \ + && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,@linear/**,jsdom/**,formdata-polyfill/**,**/lru-cache/**,**/@asamuzakjp/**,**/cssstyle/**" --run \ && rm -rf ./node_modules/sqlite3/deps \ && chmod +x /usr/src/appEntry/start.sh diff --git a/packages/nocodb/Dockerfile.timely b/packages/nocodb/Dockerfile.timely index 9422a2282b..4a2219ba7e 100644 --- a/packages/nocodb/Dockerfile.timely +++ b/packages/nocodb/Dockerfile.timely @@ -53,7 +53,7 @@ RUN echo "node-linker=hoisted" > .npmrc # Install production dependencies, reduce node_modules size with modclean, # remove sqlite deps, and add execute permission to start.sh RUN pnpm install --prod --shamefully-hoist \ - && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,jsdom/**,formdata-polyfill/**" --run \ + && pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**,@react-email/**,jsdom/**,formdata-polyfill/**,**/lru-cache/**,**/@asamuzakjp/**,**/cssstyle/**" --run \ && rm -rf ./node_modules/sqlite3/deps \ && chmod +x /usr/src/appEntry/start.sh diff --git a/packages/nocodb/src/helpers/dataReflectionHelpers.ts b/packages/nocodb/src/helpers/dataReflectionHelpers.ts index 61e9d3fd0e..3f2aedd91c 100644 --- a/packages/nocodb/src/helpers/dataReflectionHelpers.ts +++ b/packages/nocodb/src/helpers/dataReflectionHelpers.ts @@ -43,7 +43,10 @@ const createDatabaseUser = async (knex, username, password, database) => { CREATE USER :username: WITH PASSWORD :password; REVOKE ALL ON SCHEMA public FROM :username:; REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM :username:; + REVOKE TEMPORARY ON DATABASE :database: FROM :username:; GRANT CONNECT ON DATABASE :database: TO :username:; + ALTER ROLE :username: SET statement_timeout = '60s'; + ALTER ROLE :username: SET idle_in_transaction_session_timeout = '60s'; `; const preparedQuery = knex diff --git a/packages/nocodb/src/services/view-columns.service.ts b/packages/nocodb/src/services/view-columns.service.ts index a0040a0017..62361fa342 100644 --- a/packages/nocodb/src/services/view-columns.service.ts +++ b/packages/nocodb/src/services/view-columns.service.ts @@ -132,6 +132,10 @@ export class ViewColumnsService { ncMeta, ); + if (!oldViewColumn) { + NcError.get(context).fieldNotFound(param.columnId); + } + const column = await Column.get( context, {