diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue index 0f02c920a5..dcdb5b3b28 100644 --- a/packages/nc-gui/components/cell/SingleSelect.vue +++ b/packages/nc-gui/components/cell/SingleSelect.vue @@ -19,6 +19,7 @@ import { isDrawerOrModalExist, ref, useEventListener, + useProject, useRoles, useSelectedCellKeyupListener, watch, diff --git a/packages/nc-gui/components/cell/attachment/utils.ts b/packages/nc-gui/components/cell/attachment/utils.ts index 438ae5c599..86e490a4fb 100644 --- a/packages/nc-gui/components/cell/attachment/utils.ts +++ b/packages/nc-gui/components/cell/attachment/utils.ts @@ -13,6 +13,7 @@ import { isImage, message, ref, + storeToRefs, useApi, useAttachment, useFileDialog, @@ -51,7 +52,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( /** for image carousel */ const selectedImage = ref() - const { project } = useProject() + const { project } = storeToRefs(useProject()) const { api, isLoading } = useApi() diff --git a/packages/nc-gui/components/dashboard/TreeView.vue b/packages/nc-gui/components/dashboard/TreeView.vue index 2c028ad472..5b1d40e49e 100644 --- a/packages/nc-gui/components/dashboard/TreeView.vue +++ b/packages/nc-gui/components/dashboard/TreeView.vue @@ -17,6 +17,7 @@ import { reactive, ref, resolveComponent, + storeToRefs, useDialog, useGlobal, useNuxtApp, @@ -35,9 +36,12 @@ const { addTab, updateTab } = useTabs() const { $api, $e } = useNuxtApp() -const { bases, tables, loadTables, isSharedBase } = useProject() +const projectStore = useProject() -const { activeTab } = useTabs() +const { loadTables } = projectStore +const { bases, tables, isSharedBase } = storeToRefs(projectStore) + +const { activeTab } = storeToRefs(useTabs()) const { deleteTable } = useTable() diff --git a/packages/nc-gui/components/dashboard/settings/AuditTab.vue b/packages/nc-gui/components/dashboard/settings/AuditTab.vue index b5a4edef5a..b0319244d7 100644 --- a/packages/nc-gui/components/dashboard/settings/AuditTab.vue +++ b/packages/nc-gui/components/dashboard/settings/AuditTab.vue @@ -1,11 +1,11 @@ + diff --git a/packages/nc-gui/pages/index/index/[projectId].vue b/packages/nc-gui/pages/index/index/[projectId].vue index 3188ef3c87..deaeca840d 100644 --- a/packages/nc-gui/pages/index/index/[projectId].vue +++ b/packages/nc-gui/pages/index/index/[projectId].vue @@ -10,13 +10,17 @@ import { projectTitleValidator, reactive, ref, + storeToRefs, useProject, useRoute, } from '#imports' const route = useRoute() -const { project, loadProject, updateProject, isLoading } = useProject() +const projectStore = useProject() + +const { loadProject, updateProject } = projectStore +const { project, isLoading } = storeToRefs(projectStore) const nameValidationRules = [ { diff --git a/packages/nc-gui/composables/useProject.ts b/packages/nc-gui/store/project.ts similarity index 96% rename from packages/nc-gui/composables/useProject.ts rename to packages/nc-gui/store/project.ts index f0c1109385..ab32d6831f 100644 --- a/packages/nc-gui/composables/useProject.ts +++ b/packages/nc-gui/store/project.ts @@ -1,11 +1,11 @@ import type { BaseType, OracleUi, ProjectType, TableType } from 'nocodb-sdk' import { SqlUiFactory } from 'nocodb-sdk' import { isString } from '@vueuse/core' +import { defineStore } from 'pinia' import { ClientType, computed, createEventHook, - createSharedComposable, ref, useApi, useGlobal, @@ -16,7 +16,7 @@ import { } from '#imports' import type { ProjectMetaInfo, ThemeConfig } from '~/lib' -export const useProject = createSharedComposable(() => { +export const useProject = defineStore('projectStore', () => { const { $e } = useNuxtApp() const { api, isLoading } = useApi() @@ -186,6 +186,10 @@ export const useProject = createSharedComposable(() => { setTheme() } + const setProject = (projectVal: ProjectType) => { + project.value = projectVal + } + watch( () => route.params.projectType, (n) => { @@ -217,5 +221,6 @@ export const useProject = createSharedComposable(() => { lastOpenedViewMap, isXcdbBase, hasEmptyOrNullFilters, + setProject, } }) diff --git a/packages/nc-gui/composables/useTabs.ts b/packages/nc-gui/store/tab.ts similarity index 83% rename from packages/nc-gui/composables/useTabs.ts rename to packages/nc-gui/store/tab.ts index c29f118961..adc62b199e 100644 --- a/packages/nc-gui/composables/useTabs.ts +++ b/packages/nc-gui/store/tab.ts @@ -1,5 +1,6 @@ import type { WritableComputedRef } from '@vue/reactivity' -import { computed, createSharedComposable, navigateTo, ref, useProject, useRouter, watch } from '#imports' +import { defineStore } from 'pinia' +import { computed, navigateTo, ref, useProject, useRouter, watch } from '#imports' import type { TabItem } from '~/lib' import { TabType } from '~/lib' @@ -10,14 +11,14 @@ function getPredicate(key: Partial) { (!('type' in key) || tab.type === key.type) } -export const useTabs = createSharedComposable(() => { +export const useTabs = defineStore('tabStore', () => { const tabs = ref([]) const router = useRouter() const route = $(router.currentRoute) - const { bases, tables } = useProject() + const projectStore = useProject() const projectType = $computed(() => route.params.projectType as string) @@ -26,14 +27,14 @@ export const useTabs = createSharedComposable(() => { get() { const routeName = route.name as string - if (routeName.startsWith('projectType-projectId-index-index-type-title-viewTitle') && tables.value.length) { + if (routeName.startsWith('projectType-projectId-index-index-type-title-viewTitle') && projectStore.tables.length) { const tab: TabItem = { type: route.params.type as TabType, title: route.params.title as string } - const currentTable = tables.value.find((t) => t.id === tab.title || t.title === tab.title) + const currentTable = projectStore.tables.find((t) => t.id === tab.title || t.title === tab.title) if (!currentTable) return -1 - const currentBase = bases.value.find((b) => b.id === currentTable.base_id) + const currentBase = projectStore.bases.find((b) => b.id === currentTable.base_id) tab.id = currentTable.id @@ -44,7 +45,7 @@ export const useTabs = createSharedComposable(() => { tab.meta = currentTable.meta // append base alias to tab title if duplicate titles exist on other bases - if (tables.value.find((t) => t.title === currentTable?.title && t.base_id !== currentTable?.base_id)) + if (projectStore.tables.find((t) => t.title === currentTable?.title && t.base_id !== currentTable?.base_id)) tab.title = `${tab.title}${currentBase?.alias ? ` (${currentBase.alias})` : ``}` if (index === -1) { @@ -91,13 +92,13 @@ export const useTabs = createSharedComposable(() => { } // if tab not found add it else { - const currentTable = tables.value.find((t) => t.id === tabMeta.id || t.title === tabMeta.id) - const currentBase = bases.value.find((b) => b.id === currentTable?.base_id) + const currentTable = projectStore.tables.find((t) => t.id === tabMeta.id || t.title === tabMeta.id) + const currentBase = projectStore.bases.find((b) => b.id === currentTable?.base_id) tabMeta.meta = currentTable?.meta // append base alias to tab title if duplicate titles exist on other bases - if (tables.value.find((t) => t.title === currentTable?.title && t.base_id !== currentTable?.base_id)) + if (projectStore.tables.find((t) => t.title === currentTable?.title && t.base_id !== currentTable?.base_id)) tabMeta.title = `${tabMeta.title}${currentBase?.alias ? ` (${currentBase.alias})` : ``}` tabs.value = [...(tabs.value || []), tabMeta] diff --git a/packages/nocodb-sdk/src/lib/formulaHelpers.ts b/packages/nocodb-sdk/src/lib/formulaHelpers.ts index e10e491dea..4d71420952 100644 --- a/packages/nocodb-sdk/src/lib/formulaHelpers.ts +++ b/packages/nocodb-sdk/src/lib/formulaHelpers.ts @@ -179,9 +179,8 @@ export function jsepTreeToFormula(node) { if (node.type === 'Literal') { if (typeof node.value === 'string') { - return '"' + node.value + '"'; + return String.raw`"${escapeDoubleQuotes(node.value)}"`; } - return '' + node.value; } @@ -214,3 +213,7 @@ export function jsepTreeToFormula(node) { return ''; } + +function escapeDoubleQuotes(v: string) { + return v.replace(/"/g, '\\"'); +} diff --git a/packages/nocodb/src/lib/controllers/plugin.ctl.ts b/packages/nocodb/src/lib/controllers/plugin.ctl.ts index fb3ed46341..084c0565c0 100644 --- a/packages/nocodb/src/lib/controllers/plugin.ctl.ts +++ b/packages/nocodb/src/lib/controllers/plugin.ctl.ts @@ -17,6 +17,7 @@ export async function pluginTest(req: Request, res: Response) { export async function pluginRead(req: Request, res: Response) { res.json(await pluginService.pluginRead({ pluginId: req.params.pluginId })); } + export async function pluginUpdate( req: Request, res: Response @@ -27,36 +28,48 @@ export async function pluginUpdate( }); res.json(plugin); } + export async function isPluginActive(req: Request, res: Response) { res.json( await pluginService.isPluginActive({ pluginTitle: req.params.pluginTitle }) ); } +const blockInCloudMw = (_req, res, next) => { + if (process.env.NC_CLOUD === 'true') { + res.status(403).send('Not allowed'); + } else next(); +}; + const router = Router({ mergeParams: true }); router.get( '/api/v1/db/meta/plugins', + blockInCloudMw, metaApiMetrics, ncMetaAclMw(pluginList, 'pluginList') ); router.post( '/api/v1/db/meta/plugins/test', metaApiMetrics, + blockInCloudMw, ncMetaAclMw(pluginTest, 'pluginTest') ); router.get( '/api/v1/db/meta/plugins/:pluginId', metaApiMetrics, + blockInCloudMw, ncMetaAclMw(pluginRead, 'pluginRead') ); router.patch( '/api/v1/db/meta/plugins/:pluginId', metaApiMetrics, + blockInCloudMw, ncMetaAclMw(pluginUpdate, 'pluginUpdate') ); router.get( '/api/v1/db/meta/plugins/:pluginTitle/status', metaApiMetrics, + blockInCloudMw, ncMetaAclMw(isPluginActive, 'isPluginActive') ); export default router; diff --git a/packages/nocodb/src/lib/services/column.svc.ts b/packages/nocodb/src/lib/services/column.svc.ts index 2af2a49ac5..0bcd3ba424 100644 --- a/packages/nocodb/src/lib/services/column.svc.ts +++ b/packages/nocodb/src/lib/services/column.svc.ts @@ -1548,7 +1548,10 @@ async function createLTARColumn(param: { // todo: create index for virtual relations as well // create index for foreign key in pg - if (param.base.type === 'pg') { + if ( + param.base.type === 'pg' || + (param.column as LinkToAnotherColumnReqType).virtual + ) { await createColumnIndex({ column: new Column({ ...newColumn, diff --git a/packages/nocodb/src/lib/services/hook.svc.ts b/packages/nocodb/src/lib/services/hook.svc.ts index 6c46fd681a..284892f0b6 100644 --- a/packages/nocodb/src/lib/services/hook.svc.ts +++ b/packages/nocodb/src/lib/services/hook.svc.ts @@ -1,10 +1,25 @@ import { T } from 'nc-help'; import { validatePayload } from '../meta/api/helpers'; +import { NcError } from '../meta/helpers/catchError'; import { Hook, Model } from '../models'; import { invokeWebhook } from '../meta/helpers/webhookHelpers'; import populateSamplePayload from '../meta/helpers/populateSamplePayload'; import type { HookReqType, HookTestReqType } from 'nocodb-sdk'; +function validateHookPayload(notificationJsonOrObject: string | object) { + let notification: { type?: string } = {}; + try { + notification = + typeof notificationJsonOrObject === 'string' + ? JSON.parse(notificationJsonOrObject) + : notificationJsonOrObject; + } catch {} + + if (notification.type !== 'URL' && process.env.NC_CLOUD === 'true') { + NcError.badRequest('Only URL notification is supported'); + } +} + export async function hookList(param: { tableId: string }) { return await Hook.list({ fk_model_id: param.tableId }); } @@ -15,6 +30,8 @@ export async function hookCreate(param: { }) { validatePayload('swagger.json#/components/schemas/HookReq', param.hook); + validateHookPayload(param.hook.notification); + T.emit('evt', { evt_type: 'webhooks:created' }); // todo: type correction const hook = await Hook.insert({ @@ -35,6 +52,8 @@ export async function hookUpdate(param: { hookId: string; hook: HookReqType }) { T.emit('evt', { evt_type: 'webhooks:updated' }); + validateHookPayload(param.hook.notification); + // todo: correction in swagger return await Hook.update(param.hookId, param.hook as any); } @@ -48,6 +67,8 @@ export async function hookTest(param: { param.hookTest ); + validateHookPayload(param.hookTest.hook?.notification); + const model = await Model.getByIdOrName({ id: param.tableId }); const { @@ -67,6 +88,7 @@ export async function hookTest(param: { return true; } + export async function tableSampleData(param: { tableId: string; operation: 'insert' | 'update'; diff --git a/packages/nocodb/src/lib/services/util.svc.ts b/packages/nocodb/src/lib/services/util.svc.ts index 562fbcdacc..47e5f30308 100644 --- a/packages/nocodb/src/lib/services/util.svc.ts +++ b/packages/nocodb/src/lib/services/util.svc.ts @@ -55,6 +55,7 @@ export async function appInfo(param: { req: { ncSiteUrl: string } }) { ee: Noco.isEE(), ncAttachmentFieldSize: NC_ATTACHMENT_FIELD_SIZE, ncMaxAttachmentsAllowed: +(process.env.NC_MAX_ATTACHMENTS_ALLOWED || 10), + isCloud: process.env.NC_CLOUD === 'true', }; return result; diff --git a/packages/nocodb/src/lib/utils/common/helpers/jsepTreeToFormula.ts b/packages/nocodb/src/lib/utils/common/helpers/jsepTreeToFormula.ts deleted file mode 100644 index 5532d229f0..0000000000 --- a/packages/nocodb/src/lib/utils/common/helpers/jsepTreeToFormula.ts +++ /dev/null @@ -1,67 +0,0 @@ -export default function jsepTreeToFormula(node) { - if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { - return ( - '(' + - jsepTreeToFormula(node.left) + - ' ' + - node.operator + - ' ' + - jsepTreeToFormula(node.right) + - ')' - ); - } - - if (node.type === 'UnaryExpression') { - return node.operator + jsepTreeToFormula(node.argument); - } - - if (node.type === 'MemberExpression') { - return ( - jsepTreeToFormula(node.object) + - '[' + - jsepTreeToFormula(node.property) + - ']' - ); - } - - if (node.type === 'Identifier') { - return node.name; - } - - if (node.type === 'Literal') { - if (typeof node.value === 'string') { - return '"' + node.value + '"'; - } - - return '' + node.value; - } - - if (node.type === 'CallExpression') { - return ( - jsepTreeToFormula(node.callee) + - '(' + - node.arguments.map(jsepTreeToFormula).join(', ') + - ')' - ); - } - - if (node.type === 'ArrayExpression') { - return '[' + node.elements.map(jsepTreeToFormula).join(', ') + ']'; - } - - if (node.type === 'Compound') { - return node.body.map((e) => jsepTreeToFormula(e)).join(' '); - } - - if (node.type === 'ConditionalExpression') { - return ( - jsepTreeToFormula(node.test) + - ' ? ' + - jsepTreeToFormula(node.consequent) + - ' : ' + - jsepTreeToFormula(node.alternate) - ); - } - - return ''; -} diff --git a/packages/nocodb/src/lib/utils/common/helpers/updateColumnNameInFormula.ts b/packages/nocodb/src/lib/utils/common/helpers/updateColumnNameInFormula.ts index d8d5834529..7ddd4c2a46 100644 --- a/packages/nocodb/src/lib/utils/common/helpers/updateColumnNameInFormula.ts +++ b/packages/nocodb/src/lib/utils/common/helpers/updateColumnNameInFormula.ts @@ -1,4 +1,4 @@ -import jsepTreeToFormula from './jsepTreeToFormula'; +import { jsepTreeToFormula } from 'nocodb-sdk'; export default function (args: { virtualColumns;