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;