Browse Source

Merge branch 'develop' into feat/node-20

feat/node-20
աɨռɢӄաօռɢ 1 year ago
parent
commit
e682c3fc96
  1. 4
      markdown/readme/languages/spanish.md
  2. 2
      packages/nc-gui/components/smartsheet/Toolbar.vue
  3. 7
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  4. 2
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  5. 8
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  6. 70
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  7. 1
      packages/nc-gui/components/virtual-cell/Links.vue
  8. 1
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  9. 5
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  10. 1
      packages/nc-gui/composables/useGlobal/state.ts
  11. 1
      packages/nc-gui/composables/useGlobal/types.ts
  12. 2
      packages/nc-gui/package.json
  13. 2
      packages/nc-gui/pages/signin.vue
  14. 2
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  15. 61
      packages/nocodb/src/db/BaseModelSqlv2.ts
  16. 32
      packages/nocodb/src/models/Store.ts
  17. 15
      packages/nocodb/src/services/columns.service.ts
  18. 11
      packages/nocodb/src/services/utils.service.ts
  19. 26
      packages/nocodb/src/strategies/authtoken.strategy/authtoken.strategy.ts
  20. 1
      packages/nocodb/src/utils/globals.ts
  21. 4
      packages/nocodb/tests/unit/factory/column.ts
  22. 4
      packages/nocodb/tests/unit/rest/tests/filter.test.ts
  23. 72
      pnpm-lock.yaml

4
markdown/readme/languages/spanish.md

@ -194,8 +194,8 @@ Por favor diríjase a [Contribution Guide](https://github.com/nocodb/nocodb/blob
# Por qué estamos construyendo esto? # Por qué estamos construyendo esto?
La mayoría de las empresas de Internet emplean una hoja de cálculo o una base de datos para resolver sus necesidades comerciales. Las hojas de cálculo son utilizadas por billones de personas o más de manera colaborativa todos los días. Sin embargo, estamos lejos de trabajar a velocidades similares en bases de datos, ya que son herramientas computacionalmente más poderosas. Los intentos de resolver esto con soluciones SaaS han significado horribles controles de acceso, dependencia de un proveedor, dependencia de datos, cambios abruptos de precios y lo que es más importante, un techo de cristal sobre lo que es posible en el futuro." La mayoría de las empresas de Internet emplean una hoja de cálculo o una base de datos para resolver sus necesidades comerciales. Las hojas de cálculo son utilizadas por billones de personas o más de manera colaborativa todos los días. Sin embargo, estamos lejos de trabajar a velocidades similares en bases de datos, ya que son herramientas computacionalmente más poderosas. Los intentos de resolver esto con soluciones SaaS han significado horribles controles de acceso, dependencia de un proveedor, dependencia de datos, cambios abruptos de precios y lo que es más importante, un techo de cristal sobre lo que es posible en el futuro.
# Nuestra misión # Nuestra misión
Nuestra misión es proporcionar la interfaz sin-código más potente para bases de datos, la cual es open-source para negocios de Internet en el mundo. Esto no solo democratizaría el acceso a una poderosa herramienta de computación, sino que también producirá a billones de personas o más con habilidades radicales de perfección y construcción en Internet." Nuestra misión es proporcionar la interfaz sin-código más potente para bases de datos, la cual es open-source para negocios de Internet en el mundo. Esto no solo democratizaría el acceso a una poderosa herramienta de computación, sino que también producirá a miles de millones de personas o más con habilidades radicales de perfección y construcción en Internet.

2
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -39,7 +39,7 @@ const { allowCSVDownload } = useSharedView()
<!-- <LazySmartsheetToolbarQrScannerButton v-if="isMobileMode && (isGrid || isKanban || isGallery)" /> --> <!-- <LazySmartsheetToolbarQrScannerButton v-if="isMobileMode && (isGrid || isKanban || isGallery)" /> -->
<LazySmartsheetToolbarExport v-if="(!isPublic && !isUIAllowed('dataInsert')) || (isPublic && allowCSVDownload)" /> <LazySmartsheetToolbarExport v-if="isPublic && allowCSVDownload" />
<div class="flex-1" /> <div class="flex-1" />
</template> </template>

7
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -31,6 +31,7 @@ const props = defineProps<{
modelValue: any modelValue: any
row?: Row row?: Row
active?: boolean active?: boolean
readOnly?: boolean
}>() }>()
const emit = defineEmits(['update:modelValue', 'navigate', 'save']) const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
@ -38,12 +39,14 @@ const emit = defineEmits(['update:modelValue', 'navigate', 'save'])
const column = toRef(props, 'column') const column = toRef(props, 'column')
const active = toRef(props, 'active', false) const active = toRef(props, 'active', false)
const row = toRef(props, 'row') const row = toRef(props, 'row')
const readOnly = toRef(props, 'readOnly', false)
provide(ColumnInj, column) provide(ColumnInj, column)
provide(ActiveCellInj, active) provide(ActiveCellInj, active)
provide(RowInj, row) provide(RowInj, row)
provide(CellValueInj, toRef(props, 'modelValue')) provide(CellValueInj, toRef(props, 'modelValue'))
provide(SaveRowInj, () => emit('save')) provide(SaveRowInj, () => emit('save'))
provide(ReadonlyInj, readOnly)
const isGrid = inject(IsGridInj, ref(false)) const isGrid = inject(IsGridInj, ref(false))
@ -94,7 +97,9 @@ onUnmounted(() => {
<div <div
ref="elementToObserve" ref="elementToObserve"
class="nc-virtual-cell w-full flex items-center" class="nc-virtual-cell w-full flex items-center"
:class="{ 'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm }" :class="{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
}"
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
> >

2
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -223,7 +223,7 @@ if (props.fromTableExplorer) {
'!w-146': isTextArea(formState) && formState.meta.richMode, '!w-146': isTextArea(formState) && formState.meta.richMode,
'!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode, '!w-[600px]': formState.uidt === UITypes.Formula && !props.embedMode,
'!w-[500px]': formState.uidt === UITypes.Attachment && !props.embedMode && !appInfo.ee, '!w-[500px]': formState.uidt === UITypes.Attachment && !props.embedMode && !appInfo.ee,
'shadow-lg border-1 border-gray-50 shadow-gray-100 rounded-md p-6': !embedMode, 'shadow-lg border-1 border-gray-100 shadow-gray-300 rounded-xl p-6': !embedMode,
}" }"
@keydown="handleEscape" @keydown="handleEscape"
@click.stop @click.stop

8
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -88,6 +88,8 @@ const isRecordLinkCopied = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value)
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { addOrEditStackRow } = useKanbanViewStoreOrThrow() const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
@ -669,6 +671,7 @@ export default {
:class="{ :class="{
'px-1': isReadOnlyVirtualCell(col), 'px-1': isReadOnlyVirtualCell(col),
}" }"
:read-only="readOnly"
/> />
<LazySmartsheetCell <LazySmartsheetCell
@ -677,7 +680,7 @@ export default {
:column="col" :column="col"
:edit-enabled="true" :edit-enabled="true"
:active="true" :active="true"
:read-only="isPublic" :read-only="readOnly"
@update:model-value="changedColumns.add(col.title)" @update:model-value="changedColumns.add(col.title)"
/> />
</SmartsheetDivDataCell> </SmartsheetDivDataCell>
@ -737,6 +740,7 @@ export default {
v-model="_row.row[col.title]" v-model="_row.row[col.title]"
:row="_row" :row="_row"
:column="col" :column="col"
:read-only="readOnly"
/> />
<LazySmartsheetCell <LazySmartsheetCell
@ -745,7 +749,7 @@ export default {
:column="col" :column="col"
:edit-enabled="true" :edit-enabled="true"
:active="true" :active="true"
:read-only="isPublic" :read-only="readOnly"
@update:model-value="changedColumns.add(col.title)" @update:model-value="changedColumns.add(col.title)"
/> />
</LazySmartsheetDivDataCell> </LazySmartsheetDivDataCell>

70
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -239,44 +239,48 @@ const onDelete = async () => {
<LazySmartsheetToolbarExportSubActions /> <LazySmartsheetToolbarExportSubActions />
</NcSubMenu> </NcSubMenu>
<NcDivider />
</template> </template>
<NcSubMenu v-if="isUIAllowed('viewCreateOrEdit')" key="lock-type" class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0"> <template v-if="isUIAllowed('viewCreateOrEdit')">
<template #title> <NcDivider />
<div
v-e="[ <NcSubMenu key="lock-type" class="scrollbar-thin-dull max-h-90vh overflow-auto !py-0">
'c:navdraw:preview-as', <template #title>
{ <div
sidebar: props.inSidebar, v-e="[
}, 'c:navdraw:preview-as',
]" {
class="flex flex-row items-center gap-x-3" sidebar: props.inSidebar,
> },
<div> ]"
{{ $t('labels.viewMode') }} class="flex flex-row items-center gap-x-3"
</div> >
<div class="nc-base-menu-item flex !flex-shrink group !py-1 !px-1 rounded-md bg-brand-50"> <div>
<LazySmartsheetToolbarLockType {{ $t('labels.viewMode') }}
hide-tick </div>
:type="lockType" <div class="nc-base-menu-item flex !flex-shrink group !py-1 !px-1 rounded-md bg-brand-50">
class="flex nc-view-actions-lock-type !text-brand-500 !flex-shrink" <LazySmartsheetToolbarLockType
/> hide-tick
:type="lockType"
class="flex nc-view-actions-lock-type !text-brand-500 !flex-shrink"
/>
</div>
<div class="flex flex-grow"></div>
</div> </div>
<div class="flex flex-grow"></div> </template>
</div>
</template>
<template #expandIcon></template> <template #expandIcon></template>
<div class="flex py-3 px-4 font-bold uppercase text-xs text-gray-500">{{ $t('labels.viewMode') }}</div> <div class="flex py-3 px-4 font-bold uppercase text-xs text-gray-500">{{ $t('labels.viewMode') }}</div>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction"> <a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Collaborative" @click="changeLockType(LockType.Collaborative)" /> <LazySmartsheetToolbarLockType :type="LockType.Collaborative" @click="changeLockType(LockType.Collaborative)" />
</a-menu-item> </a-menu-item>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
</template>
<a-menu-item class="!mx-1 !py-2 !rounded-md nc-view-action-lock-subaction">
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
<template v-if="!view.is_default"> <template v-if="!view.is_default">
<NcDivider /> <NcDivider />
<NcTooltip v-if="lockType === LockType.Locked"> <NcTooltip v-if="lockType === LockType.Locked">

1
packages/nc-gui/components/virtual-cell/Links.vue

@ -127,7 +127,6 @@ const openListDlg = () => {
@click.stop="openListDlg" @click.stop="openListDlg"
/> />
</div> </div>
<LazyVirtualCellComponentsListItems <LazyVirtualCellComponentsListItems
v-if="listItemsDlg || childListDlg" v-if="listItemsDlg || childListDlg"
v-model="listItemsDlg" v-model="listItemsDlg"

1
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -166,6 +166,7 @@ const isDataExist = computed<boolean>(() => {
const linkOrUnLink = (rowRef: Record<string, string>, id: string) => { const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
if (isSharedBase.value) return if (isSharedBase.value) return
if (readonly.value) return
if (isPublic.value && !isForm.value) return if (isPublic.value && !isForm.value) return
if (isNew.value || isChildrenListLinked.value[parseInt(id)]) { if (isNew.value || isChildrenListLinked.value[parseInt(id)]) {

5
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -77,6 +77,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
'!bg-white': isLoading, '!bg-white': isLoading,
'!border-1': isLinked && !isLoading, '!border-1': isLinked && !isLoading,
'!hover:border-gray-400': !isLinked, '!hover:border-gray-400': !isLinked,
'!cursor-auto !hover:bg-white': readonly,
}" }"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
:hoverable="false" :hoverable="false"
@ -109,7 +110,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
v-if="isLinked && !isLoading" v-if="isLinked && !isLoading"
class="text-brand-500 text-0.875" class="text-brand-500 text-0.875"
:class="{ :class="{
'!group-hover:mr-12': fields.length === 0, '!group-hover:mr-12': fields.length === 0 && !readonly,
}" }"
> >
<LinkIcon class="w-4 h-4" /> <LinkIcon class="w-4 h-4" />
@ -118,7 +119,7 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
<MdiLoading <MdiLoading
v-else-if="isLoading" v-else-if="isLoading"
:class="{ :class="{
'!group-hover:mr-8': fields.length === 0, '!group-hover:mr-8': fields.length === 0 && !readonly,
}" }"
class="w-6 h-6 !text-brand-500 animate-spin" class="w-6 h-6 !text-brand-500 animate-spin"
/> />

1
packages/nc-gui/composables/useGlobal/state.ts

@ -109,6 +109,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
automationLogLevel: 'OFF', automationLogLevel: 'OFF',
disableEmailAuth: false, disableEmailAuth: false,
dashboardPath: '/dashboard', dashboardPath: '/dashboard',
inviteOnlySignup: false,
}) })
/** reactive token payload */ /** reactive token payload */

1
packages/nc-gui/composables/useGlobal/types.ts

@ -34,6 +34,7 @@ export interface AppInfo {
disableEmailAuth: boolean disableEmailAuth: boolean
mainSubDomain?: string mainSubDomain?: string
dashboardPath: string dashboardPath: string
inviteOnlySignup: boolean
} }
export interface StoredState { export interface StoredState {

2
packages/nc-gui/package.json

@ -144,7 +144,7 @@
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
"@unocss/nuxt": "^0.57.7", "@unocss/nuxt": "^0.57.7",
"@vitest/ui": "^0.18.1", "@vitest/ui": "^0.18.1",
"@vue/compiler-sfc": "^3.3.8", "@vue/compiler-sfc": "^3.3.9",
"@vue/test-utils": "^2.0.2", "@vue/test-utils": "^2.0.2",
"@vueuse/nuxt": "^10.6.1", "@vueuse/nuxt": "^10.6.1",
"@windicss/plugin-animations": "^1.0.9", "@windicss/plugin-animations": "^1.0.9",

2
packages/nc-gui/pages/signin.vue

@ -184,7 +184,7 @@ function navigateForgotPassword() {
</a> </a>
</div> </div>
<div class="text-end prose-sm"> <div class="text-end prose-sm" v-if="!appInfo.inviteOnlySignup">
{{ $t('msg.info.signUp.dontHaveAccount') }} {{ $t('msg.info.signUp.dontHaveAccount') }}
<nuxt-link @click="navigateSignUp">{{ $t('general.signUp') }}</nuxt-link> <nuxt-link @click="navigateSignUp">{{ $t('general.signUp') }}</nuxt-link>
</div> </div>

2
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -93,7 +93,7 @@ export function substituteColumnIdWithAliasInFormula(
c.column_name === colNameOrId || c.column_name === colNameOrId ||
c.title === colNameOrId c.title === colNameOrId
); );
pt.name = column?.id || ptRaw?.name || pt?.name; pt.name = column?.title || ptRaw?.name || pt?.name;
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {
substituteId(pt.left, ptRaw?.left); substituteId(pt.left, ptRaw?.left);
substituteId(pt.right, ptRaw?.right); substituteId(pt.right, ptRaw?.right);

61
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -2977,6 +2977,8 @@ class BaseModelSqlv2 {
} }
} }
await this.validateOptions(col, insertObj);
// validate data // validate data
if (col?.meta?.validate && col?.validate) { if (col?.meta?.validate && col?.validate) {
const validate = col.getValidators(); const validate = col.getValidators();
@ -3208,7 +3210,12 @@ class BaseModelSqlv2 {
} }
async bulkUpdateAll( async bulkUpdateAll(
args: { where?: string; filterArr?: Filter[]; viewId?: string } = {}, args: {
where?: string;
filterArr?: Filter[];
viewId?: string;
skipValidationAndHooks?: boolean;
} = {},
data, data,
{ cookie }: { cookie?: any } = {}, { cookie }: { cookie?: any } = {},
) { ) {
@ -3219,7 +3226,7 @@ class BaseModelSqlv2 {
this.clientMeta, this.clientMeta,
this.dbDriver, this.dbDriver,
); );
await this.validate(updateData); if (!args.skipValidationAndHooks) await this.validate(updateData);
const pkValues = await this._extractPksValues(updateData); const pkValues = await this._extractPksValues(updateData);
if (pkValues) { if (pkValues) {
// pk is specified - by pass // pk is specified - by pass
@ -3253,7 +3260,7 @@ class BaseModelSqlv2 {
); );
} }
await conditionV2(this, conditionObj, qb); await conditionV2(this, conditionObj, qb, undefined, true);
count = ( count = (
await this.execAndParse( await this.execAndParse(
@ -3271,7 +3278,8 @@ class BaseModelSqlv2 {
await this.execAndParse(qb, null, { raw: true }); await this.execAndParse(qb, null, { raw: true });
} }
await this.afterBulkUpdate(null, count, this.dbDriver, cookie, true); if (!args.skipValidationAndHooks)
await this.afterBulkUpdate(null, count, this.dbDriver, cookie, true);
return count; return count;
} catch (e) { } catch (e) {
@ -3763,6 +3771,8 @@ class BaseModelSqlv2 {
// let cols = Object.keys(this.columns); // let cols = Object.keys(this.columns);
for (let i = 0; i < this.model.columns.length; ++i) { for (let i = 0; i < this.model.columns.length; ++i) {
const column = this.model.columns[i]; const column = this.model.columns[i];
await this.validateOptions(column, columns);
// skip validation if `validate` is undefined or false // skip validation if `validate` is undefined or false
if (!column?.meta?.validate || !column?.validate) continue; if (!column?.meta?.validate || !column?.validate) continue;
@ -3797,6 +3807,49 @@ class BaseModelSqlv2 {
return true; return true;
} }
// method for validating otpions if column is single/multi select
private async validateOptions(
column: Column<any>,
insertOrUpdateObject: Record<string, any>,
) {
// if SingleSelect or MultiSelect, then validate the options
if (
!(
column.uidt === UITypes.SingleSelect ||
column.uidt === UITypes.MultiSelect
)
) {
return;
}
const options = await column
.getColOptions<{ options: SelectOption[] }>()
.then(({ options }) => options.map((opt) => opt.title));
const columnTitle = column.title;
const columnName = column.column_name;
const columnValue =
insertOrUpdateObject?.[columnTitle] ?? insertOrUpdateObject?.[columnName];
if (!columnValue) {
return;
}
// if multi select, then split the values
const columnValueArr =
column.uidt === UITypes.MultiSelect
? columnValue.split(',')
: [columnValue];
for (let j = 0; j < columnValueArr.length; ++j) {
const val = columnValueArr[j];
if (!options.includes(val)) {
NcError.badRequest(
`Invalid option "${val}" provided for column "${columnTitle}". Valid options are "${options.join(
', ',
)}"`,
);
}
}
}
async addChild({ async addChild({
colId, colId,
rowId, rowId,

32
packages/nocodb/src/models/Store.ts

@ -2,7 +2,8 @@ import type { SortType } from 'nocodb-sdk';
import { NcError } from '~/helpers/catchError'; import { NcError } from '~/helpers/catchError';
import { extractProps } from '~/helpers/extractProps'; import { extractProps } from '~/helpers/extractProps';
import Noco from '~/Noco'; import Noco from '~/Noco';
import { MetaTable } from '~/utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
import NocoCache from '~/cache/NocoCache';
// Store is used for storing key value pairs // Store is used for storing key value pairs
export default class Store { export default class Store {
@ -18,8 +19,30 @@ export default class Store {
Object.assign(this, data); Object.assign(this, data);
} }
public static get(key: string, ncMeta = Noco.ncMeta): Promise<Store> { public static async get(
return ncMeta.metaGet(null, null, MetaTable.STORE, { key }); key: string,
lookInCache = false,
ncMeta = Noco.ncMeta,
): Promise<Store> {
// get from cache if lookInCache is true
if (lookInCache) {
const storeData =
key &&
(await NocoCache.get(
`${CacheScope.STORE}:${key}`,
CacheGetType.TYPE_OBJECT,
));
if (storeData) return storeData;
}
const storeData = await ncMeta.metaGet(null, null, MetaTable.STORE, {
key,
});
if (lookInCache)
await NocoCache.set(`${CacheScope.STORE}:${key}`, storeData);
return storeData;
} }
static async saveOrUpdate(store: Store, ncMeta = Noco.ncMeta) { static async saveOrUpdate(store: Store, ncMeta = Noco.ncMeta) {
@ -35,7 +58,7 @@ export default class Store {
'tag', 'tag',
]); ]);
const existing = await Store.get(store.key, ncMeta); const existing = await Store.get(store.key,false, ncMeta);
if (existing) { if (existing) {
await ncMeta.metaUpdate(null, null, MetaTable.STORE, insertObj, { await ncMeta.metaUpdate(null, null, MetaTable.STORE, insertObj, {
key: store.key, key: store.key,
@ -43,5 +66,6 @@ export default class Store {
} else { } else {
await ncMeta.metaInsert(null, null, MetaTable.STORE, insertObj); await ncMeta.metaInsert(null, null, MetaTable.STORE, insertObj);
} }
if (store.key) await NocoCache.del(`${CacheScope.STORE}:${store.key}`);
} }
} }

15
packages/nocodb/src/services/columns.service.ts

@ -546,7 +546,10 @@ export class ColumnsService {
]); ]);
} else { } else {
await baseModel.bulkUpdateAll( await baseModel.bulkUpdateAll(
{ where: `(${column.title},eq,${option.title})` }, {
where: `(${column.title},eq,${option.title})`,
skipValidationAndHooks: true,
},
{ [column.column_name]: null }, { [column.column_name]: null },
{ cookie }, { cookie },
); );
@ -728,7 +731,10 @@ export class ColumnsService {
]); ]);
} else { } else {
await baseModel.bulkUpdateAll( await baseModel.bulkUpdateAll(
{ where: `(${column.title},eq,${option.title})` }, {
where: `(${column.title},eq,${option.title})`,
skipValidationAndHooks: true,
},
{ [column.column_name]: newOp.title }, { [column.column_name]: newOp.title },
{ cookie }, { cookie },
); );
@ -814,7 +820,10 @@ export class ColumnsService {
]); ]);
} else { } else {
await baseModel.bulkUpdateAll( await baseModel.bulkUpdateAll(
{ where: `(${column.title},eq,${ch.temp_title})` }, {
where: `(${column.title},eq,${ch.temp_title})`,
skipValidationAndHooks: true,
},
{ [column.column_name]: newOp.title }, { [column.column_name]: newOp.title },
{ cookie }, { cookie },
); );

11
packages/nocodb/src/services/utils.service.ts

@ -5,10 +5,10 @@ import { ViewTypes } from 'nocodb-sdk';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { useAgent } from 'request-filtering-agent'; import { useAgent } from 'request-filtering-agent';
import type { AppConfig } from '~/interface/config'; import type { AppConfig } from '~/interface/config';
import { NC_ATTACHMENT_FIELD_SIZE } from '~/constants'; import { NC_APP_SETTINGS, NC_ATTACHMENT_FIELD_SIZE } from '~/constants';
import SqlMgrv2 from '~/db/sql-mgr/v2/SqlMgrv2'; import SqlMgrv2 from '~/db/sql-mgr/v2/SqlMgrv2';
import { NcError } from '~/helpers/catchError'; import { NcError } from '~/helpers/catchError';
import { Base, User } from '~/models'; import { Base, Store, User } from '~/models';
import Noco from '~/Noco'; import Noco from '~/Noco';
import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2'; import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2';
import { MetaTable } from '~/utils/globals'; import { MetaTable } from '~/utils/globals';
@ -365,6 +365,12 @@ export class UtilsService {
async appInfo(param: { req: { ncSiteUrl: string } }) { async appInfo(param: { req: { ncSiteUrl: string } }) {
const baseHasAdmin = !(await User.isFirst()); const baseHasAdmin = !(await User.isFirst());
let settings: { invite_only_signup?: boolean } = {};
try {
settings = JSON.parse((await Store.get(NC_APP_SETTINGS, true))?.value);
} catch {}
const oidcAuthEnabled = !!( const oidcAuthEnabled = !!(
process.env.NC_OIDC_ISSUER && process.env.NC_OIDC_ISSUER &&
process.env.NC_OIDC_AUTHORIZATION_URL && process.env.NC_OIDC_AUTHORIZATION_URL &&
@ -415,6 +421,7 @@ export class UtilsService {
}), }),
mainSubDomain: this.configService.get('mainSubDomain', { infer: true }), mainSubDomain: this.configService.get('mainSubDomain', { infer: true }),
dashboardPath: this.configService.get('dashboardPath', { infer: true }), dashboardPath: this.configService.get('dashboardPath', { infer: true }),
inviteOnlySignup: settings.invite_only_signup,
}; };
return result; return result;

26
packages/nocodb/src/strategies/authtoken.strategy/authtoken.strategy.ts

@ -3,7 +3,7 @@ import { PassportStrategy } from '@nestjs/passport';
import { extractRolesObj, ProjectRoles } from 'nocodb-sdk'; import { extractRolesObj, ProjectRoles } from 'nocodb-sdk';
import { Strategy } from 'passport-custom'; import { Strategy } from 'passport-custom';
import type { Request } from 'express'; import type { Request } from 'express';
import { ApiToken, BaseUser, User } from '~/models'; import { ApiToken, User } from '~/models';
import { sanitiseUserObj } from '~/utils'; import { sanitiseUserObj } from '~/utils';
@Injectable() @Injectable()
@ -22,12 +22,21 @@ export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
is_api_token: true, is_api_token: true,
}; };
// old auth tokens will not have fk_user_id, so we return editor role
if (!apiToken.fk_user_id) { if (!apiToken.fk_user_id) {
user.base_roles = extractRolesObj(ProjectRoles.EDITOR); user.base_roles = extractRolesObj(ProjectRoles.EDITOR);
return callback(null, user); return callback(null, user);
} }
const dbUser: Record<string, any> = await User.get(apiToken.fk_user_id); const dbUser: Record<string, any> = await User.getWithRoles(
apiToken.fk_user_id,
{
baseId: req['ncBaseId'],
...(req['ncWorkspaceId']
? { workspaceId: req['ncWorkspaceId'] }
: {}),
},
);
if (!dbUser) { if (!dbUser) {
return callback({ msg: 'User not found' }); return callback({ msg: 'User not found' });
} }
@ -35,16 +44,11 @@ export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
Object.assign(user, { Object.assign(user, {
id: dbUser.id, id: dbUser.id,
roles: extractRolesObj(dbUser.roles), roles: extractRolesObj(dbUser.roles),
base_roles: extractRolesObj(dbUser.base_roles),
...(dbUser.workspace_roles
? { workspace_roles: extractRolesObj(dbUser.workspace_roles) }
: {}),
}); });
if (req['ncProjectId']) {
const baseUser = await BaseUser.get(req['ncProjectId'], dbUser.id);
user.base_roles = extractRolesObj(baseUser?.roles);
if (user.base_roles.owner) {
user.base_roles.creator = true;
}
return callback(null, sanitiseUserObj(user));
}
} }
return callback(null, sanitiseUserObj(user)); return callback(null, sanitiseUserObj(user));
} catch (error) { } catch (error) {

1
packages/nocodb/src/utils/globals.ts

@ -158,6 +158,7 @@ export enum CacheScope {
SINGLE_QUERY = 'singleQuery', SINGLE_QUERY = 'singleQuery',
JOBS = 'nc_jobs', JOBS = 'nc_jobs',
PRESIGNED_URL = 'presignedUrl', PRESIGNED_URL = 'presignedUrl',
STORE = 'store',
} }
export enum CacheGetType { export enum CacheGetType {

4
packages/nocodb/tests/unit/factory/column.ts

@ -148,13 +148,13 @@ const customColumns = function (type: string, options: any = {}) {
column_name: 'SingleSelect', column_name: 'SingleSelect',
title: 'SingleSelect', title: 'SingleSelect',
uidt: UITypes.SingleSelect, uidt: UITypes.SingleSelect,
dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'",
}, },
{ {
column_name: 'MultiSelect', column_name: 'MultiSelect',
title: 'MultiSelect', title: 'MultiSelect',
uidt: UITypes.MultiSelect, uidt: UITypes.MultiSelect,
dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'",
}, },
]; ];
case 'custom': case 'custom':

4
packages/nocodb/tests/unit/rest/tests/filter.test.ts

@ -519,13 +519,13 @@ function filterSelectBased() {
column_name: 'SingleSelect', column_name: 'SingleSelect',
title: 'SingleSelect', title: 'SingleSelect',
uidt: UITypes.SingleSelect, uidt: UITypes.SingleSelect,
dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'",
}, },
{ {
column_name: 'MultiSelect', column_name: 'MultiSelect',
title: 'MultiSelect', title: 'MultiSelect',
uidt: UITypes.MultiSelect, uidt: UITypes.MultiSelect,
dtxp: "'jan','feb','mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'", dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'",
}, },
], ],
}); });

72
pnpm-lock.yaml

@ -349,8 +349,8 @@ importers:
specifier: ^0.18.1 specifier: ^0.18.1
version: 0.18.1 version: 0.18.1
'@vue/compiler-sfc': '@vue/compiler-sfc':
specifier: ^3.3.8 specifier: ^3.3.9
version: 3.3.8 version: 3.3.9
'@vue/test-utils': '@vue/test-utils':
specifier: ^2.0.2 specifier: ^2.0.2
version: 2.0.2(vue@3.3.9) version: 2.0.2(vue@3.3.9)
@ -395,7 +395,7 @@ importers:
version: 9.4.4(webpack@5.89.0) version: 9.4.4(webpack@5.89.0)
unplugin-icons: unplugin-icons:
specifier: ^0.14.15 specifier: ^0.14.15
version: 0.14.15(@vue/compiler-sfc@3.3.8) version: 0.14.15(@vue/compiler-sfc@3.3.9)
unplugin-vue-components: unplugin-vue-components:
specifier: ^0.22.12 specifier: ^0.22.12
version: 0.22.12(vue@3.3.9) version: 0.22.12(vue@3.3.9)
@ -3049,14 +3049,6 @@ packages:
chalk: 2.4.2 chalk: 2.4.2
js-tokens: 4.0.0 js-tokens: 4.0.0
/@babel/parser@7.23.0:
resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.23.4
dev: true
/@babel/parser@7.23.4: /@babel/parser@7.23.4:
resolution: {integrity: sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==} resolution: {integrity: sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -4567,7 +4559,7 @@ packages:
'@intlify/bundle-utils': 7.0.2(vue-i18n@9.7.1) '@intlify/bundle-utils': 7.0.2(vue-i18n@9.7.1)
'@intlify/shared': 9.3.0-beta.24 '@intlify/shared': 9.3.0-beta.24
'@rollup/pluginutils': 5.0.5(rollup@4.5.0) '@rollup/pluginutils': 5.0.5(rollup@4.5.0)
'@vue/compiler-sfc': 3.3.8 '@vue/compiler-sfc': 3.3.9
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
fast-glob: 3.3.1 fast-glob: 3.3.1
js-yaml: 4.1.0 js-yaml: 4.1.0
@ -9492,7 +9484,7 @@ packages:
dependencies: dependencies:
'@babel/types': 7.23.4 '@babel/types': 7.23.4
'@rollup/pluginutils': 5.0.5(rollup@4.5.0) '@rollup/pluginutils': 5.0.5(rollup@4.5.0)
'@vue/compiler-sfc': 3.3.8 '@vue/compiler-sfc': 3.3.9
ast-kit: 0.11.2 ast-kit: 0.11.2
local-pkg: 0.4.3 local-pkg: 0.4.3
magic-string-ast: 0.3.0 magic-string-ast: 0.3.0
@ -9524,15 +9516,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@vue/compiler-core@3.3.8:
resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==}
dependencies:
'@babel/parser': 7.23.4
'@vue/shared': 3.3.8
estree-walker: 2.0.2
source-map-js: 1.0.2
dev: true
/@vue/compiler-core@3.3.9: /@vue/compiler-core@3.3.9:
resolution: {integrity: sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==} resolution: {integrity: sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==}
dependencies: dependencies:
@ -9541,34 +9524,12 @@ packages:
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map-js: 1.0.2 source-map-js: 1.0.2
/@vue/compiler-dom@3.3.8:
resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==}
dependencies:
'@vue/compiler-core': 3.3.8
'@vue/shared': 3.3.8
dev: true
/@vue/compiler-dom@3.3.9: /@vue/compiler-dom@3.3.9:
resolution: {integrity: sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==} resolution: {integrity: sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==}
dependencies: dependencies:
'@vue/compiler-core': 3.3.9 '@vue/compiler-core': 3.3.9
'@vue/shared': 3.3.9 '@vue/shared': 3.3.9
/@vue/compiler-sfc@3.3.8:
resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==}
dependencies:
'@babel/parser': 7.23.0
'@vue/compiler-core': 3.3.8
'@vue/compiler-dom': 3.3.8
'@vue/compiler-ssr': 3.3.8
'@vue/reactivity-transform': 3.3.8
'@vue/shared': 3.3.8
estree-walker: 2.0.2
magic-string: 0.30.5
postcss: 8.4.31
source-map-js: 1.0.2
dev: true
/@vue/compiler-sfc@3.3.9: /@vue/compiler-sfc@3.3.9:
resolution: {integrity: sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==} resolution: {integrity: sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==}
dependencies: dependencies:
@ -9583,13 +9544,6 @@ packages:
postcss: 8.4.31 postcss: 8.4.31
source-map-js: 1.0.2 source-map-js: 1.0.2
/@vue/compiler-ssr@3.3.8:
resolution: {integrity: sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==}
dependencies:
'@vue/compiler-dom': 3.3.8
'@vue/shared': 3.3.8
dev: true
/@vue/compiler-ssr@3.3.9: /@vue/compiler-ssr@3.3.9:
resolution: {integrity: sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==} resolution: {integrity: sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==}
dependencies: dependencies:
@ -9599,16 +9553,6 @@ packages:
/@vue/devtools-api@6.5.0: /@vue/devtools-api@6.5.0:
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
/@vue/reactivity-transform@3.3.8:
resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==}
dependencies:
'@babel/parser': 7.23.4
'@vue/compiler-core': 3.3.8
'@vue/shared': 3.3.8
estree-walker: 2.0.2
magic-string: 0.30.5
dev: true
/@vue/reactivity-transform@3.3.9: /@vue/reactivity-transform@3.3.9:
resolution: {integrity: sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==} resolution: {integrity: sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==}
dependencies: dependencies:
@ -24160,7 +24104,7 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
/unplugin-icons@0.14.15(@vue/compiler-sfc@3.3.8): /unplugin-icons@0.14.15(@vue/compiler-sfc@3.3.9):
resolution: {integrity: sha512-J6YBA+fUzVM2IZPXCK3Pnk36jYVwQ6lkjRgOnZaXNIxpMDsmwDqrE1AGJ0zUbfuEoOa90OBGc0OPfN1r+qlSIQ==} resolution: {integrity: sha512-J6YBA+fUzVM2IZPXCK3Pnk36jYVwQ6lkjRgOnZaXNIxpMDsmwDqrE1AGJ0zUbfuEoOa90OBGc0OPfN1r+qlSIQ==}
peerDependencies: peerDependencies:
'@svgr/core': '>=5.5.0' '@svgr/core': '>=5.5.0'
@ -24180,7 +24124,7 @@ packages:
'@antfu/install-pkg': 0.1.1 '@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.7.6 '@antfu/utils': 0.7.6
'@iconify/utils': 2.1.9 '@iconify/utils': 2.1.9
'@vue/compiler-sfc': 3.3.8 '@vue/compiler-sfc': 3.3.9
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
kolorist: 1.8.0 kolorist: 1.8.0
local-pkg: 0.4.3 local-pkg: 0.4.3
@ -24701,7 +24645,7 @@ packages:
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.3) '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.3)
'@babel/plugin-transform-typescript': 7.22.15(@babel/core@7.23.3) '@babel/plugin-transform-typescript': 7.22.15(@babel/core@7.23.3)
'@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.3) '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.3)
'@vue/compiler-dom': 3.3.8 '@vue/compiler-dom': 3.3.9
kolorist: 1.8.0 kolorist: 1.8.0
magic-string: 0.30.5 magic-string: 0.30.5
vite: 4.5.0(@types/node@20.3.1)(sass@1.69.5) vite: 4.5.0(@types/node@20.3.1)(sass@1.69.5)

Loading…
Cancel
Save