Browse Source

Merge pull request #5774 from nocodb/develop

pull/5775/head 0.108.0
github-actions[bot] 1 year ago committed by GitHub
parent
commit
a3227632a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/nc-gui/components/smartsheet/Gallery.vue
  2. 2
      packages/nc-gui/components/smartsheet/header/Cell.vue
  3. 3
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  4. 5
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  5. 5
      packages/nc-gui/components/virtual-cell/HasMany.vue
  6. 3
      packages/nc-gui/components/virtual-cell/Lookup.vue
  7. 5
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  8. 3
      packages/nc-gui/composables/useApi/interceptors.ts
  9. 1
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  10. 4
      packages/nc-gui/composables/useMultiSelect/index.ts
  11. 1
      packages/nc-gui/context/index.ts
  12. 52
      packages/nc-gui/package-lock.json
  13. 2
      packages/nc-gui/package.json
  14. 9
      packages/nc-gui/pages/index/index/index.vue
  15. 4
      packages/nocodb-sdk/package-lock.json
  16. 74
      packages/nocodb-sdk/src/lib/Api.ts
  17. 10
      packages/nocodb/.eslintrc.js
  18. 30
      packages/nocodb/package-lock.json
  19. 4
      packages/nocodb/package.json
  20. 6
      packages/nocodb/src/cache/RedisCacheMgr.ts
  21. 2
      packages/nocodb/src/cache/RedisMockCacheMgr.ts
  22. 1
      packages/nocodb/src/controllers/api-tokens.controller.ts
  23. 1
      packages/nocodb/src/controllers/attachments.controller.ts
  24. 1
      packages/nocodb/src/controllers/audits.controller.ts
  25. 1
      packages/nocodb/src/controllers/bases.controller.ts
  26. 1
      packages/nocodb/src/controllers/bulk-data-alias.controller.ts
  27. 1
      packages/nocodb/src/controllers/columns.controller.ts
  28. 1
      packages/nocodb/src/controllers/data-alias-export.controller.ts
  29. 3
      packages/nocodb/src/controllers/data-alias-nested.controller.ts
  30. 1
      packages/nocodb/src/controllers/data-alias.controller.ts
  31. 2
      packages/nocodb/src/controllers/filters.controller.ts
  32. 1
      packages/nocodb/src/controllers/form-columns.controller.ts
  33. 1
      packages/nocodb/src/controllers/galleries.controller.ts
  34. 1
      packages/nocodb/src/controllers/grid-columns.controller.ts
  35. 1
      packages/nocodb/src/controllers/grids.controller.ts
  36. 1
      packages/nocodb/src/controllers/maps.controller.ts
  37. 1
      packages/nocodb/src/controllers/meta-diffs.controller.ts
  38. 1
      packages/nocodb/src/controllers/model-visibilities.controller.ts
  39. 1
      packages/nocodb/src/controllers/old-datas/old-datas.controller.ts
  40. 1
      packages/nocodb/src/controllers/org-users.controller.ts
  41. 10
      packages/nocodb/src/controllers/plugins.controller.ts
  42. 1
      packages/nocodb/src/controllers/project-users.controller.ts
  43. 77
      packages/nocodb/src/controllers/projects.controller.ts
  44. 1
      packages/nocodb/src/controllers/shared-bases.controller.ts
  45. 1
      packages/nocodb/src/controllers/sorts.controller.ts
  46. 1
      packages/nocodb/src/controllers/tables.controller.ts
  47. 117
      packages/nocodb/src/controllers/users/users.controller.ts
  48. 3
      packages/nocodb/src/controllers/view-columns.controller.ts
  49. 6
      packages/nocodb/src/db/BaseModelSql.ts
  50. 19
      packages/nocodb/src/db/BaseModelSqlv2.ts
  51. 2
      packages/nocodb/src/db/CustomKnex.ts
  52. 2
      packages/nocodb/src/db/conditionV2.ts
  53. 2
      packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts
  54. 1
      packages/nocodb/src/interceptors/is-upload-allowed/is-upload-allowed.interceptor.ts
  55. 9
      packages/nocodb/src/middlewares/extract-project-id/extract-project-id.middleware.ts
  56. 2
      packages/nocodb/src/models/Model.ts
  57. 20
      packages/nocodb/src/models/ProjectUser.ts
  58. 1
      packages/nocodb/src/modules/datas/helpers.ts
  59. 3
      packages/nocodb/src/modules/jobs/jobs/at-import/helpers/readAndProcessData.ts
  60. 5
      packages/nocodb/src/modules/users/users.module.ts
  61. 1
      packages/nocodb/src/run/docker.ts
  62. 1
      packages/nocodb/src/run/dockerRunPG_CyQuick.ts
  63. 150
      packages/nocodb/src/schema/swagger.json
  64. 5
      packages/nocodb/src/services/bulk-data-alias.service.ts
  65. 90
      packages/nocodb/src/services/public-metas.service.ts
  66. 4
      packages/nocodb/src/services/users/users.service.ts
  67. 2
      packages/nocodb/src/utils/nc-config/NcConfig.ts
  68. 1
      packages/nocodb/src/version-upgrader/v1-legacy/gql/GqlApiBuilder.ts
  69. 5
      packages/nocodb/src/version-upgrader/v1-legacy/rest/RestApiBuilder.ts
  70. 34
      tests/playwright/tests/db/timezone.spec.ts

8
packages/nc-gui/components/smartsheet/Gallery.vue

@ -7,6 +7,7 @@ import {
IsFormInj, IsFormInj,
IsGalleryInj, IsGalleryInj,
IsGridInj, IsGridInj,
IsPublicInj,
MetaInj, MetaInj,
NavigateDir, NavigateDir,
OpenNewRecordFormHookInj, OpenNewRecordFormHookInj,
@ -62,6 +63,8 @@ provide(IsGridInj, ref(false))
provide(PaginationDataInj, paginationData) provide(PaginationDataInj, paginationData)
provide(ChangePageInj, changePage) provide(ChangePageInj, changePage)
const isPublic = inject(IsPublicInj, ref(false))
const fields = inject(FieldsInj, ref([])) const fields = inject(FieldsInj, ref([]))
const route = useRoute() const route = useRoute()
@ -125,6 +128,10 @@ const attachments = (record: any): Attachment[] => {
} }
const expandForm = (row: RowType, state?: Record<string, any>) => { const expandForm = (row: RowType, state?: Record<string, any>) => {
if (isPublic.value) {
return
}
const rowId = extractPkFromRow(row.row, meta.value!.columns!) const rowId = extractPkFromRow(row.row, meta.value!.columns!)
if (rowId) { if (rowId) {
@ -234,6 +241,7 @@ watch(view, async (nextView) => {
:data-testid="`nc-gallery-card-${record.row.id}`" :data-testid="`nc-gallery-card-${record.row.id}`"
@click="expandFormClick($event, record)" @click="expandFormClick($event, record)"
@contextmenu="showContextMenu($event, { row: rowIndex })" @contextmenu="showContextMenu($event, { row: rowIndex })"
:style="isPublic ? { cursor: 'default' } : { cursor: 'pointer' }"
> >
<template v-if="galleryData?.fk_cover_image_col_id" #cover> <template v-if="galleryData?.fk_cover_image_col_id" #cover>
<a-carousel v-if="!reloadAttachments && attachments(record).length" autoplay class="gallery-carousel" arrows> <a-carousel v-if="!reloadAttachments && attachments(record).length" autoplay class="gallery-carousel" arrows>

2
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -52,7 +52,7 @@ const openHeaderMenu = () => {
<span <span
v-if="column" v-if="column"
class="name" class="name"
:class="{ 'cursor-pointer': !isForm && isUIAllowed('edit-column') }" :class="{ 'cursor-pointer': !isForm && isUIAllowed('edit-column') && !hideMenu }"
style="white-space: nowrap" style="white-space: nowrap"
:title="column.title" :title="column.title"
@dblclick="openHeaderMenu" @dblclick="openHeaderMenu"

3
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -225,7 +225,8 @@ defineExpose({
/> />
<span v-else :key="`${i}dummy`" /> <span v-else :key="`${i}dummy`" />
<div :key="`${i}nested`" class="flex"> <span v-if="!i" class="flex items-center">{{ $t('labels.where') }}</span>
<div v-else :key="`${i}nested`" class="flex bob">
<a-select <a-select
v-model:value="filter.logical_op" v-model:value="filter.logical_op"
:dropdown-match-select-width="false" :dropdown-match-select-width="false"

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

@ -7,6 +7,7 @@ import {
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsLockedInj, IsLockedInj,
IsUnderLookupInj,
ReadonlyInj, ReadonlyInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
RowInj, RowInj,
@ -36,6 +37,8 @@ const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false)) const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
@ -89,7 +92,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
</div> </div>
<div <div
v-if="!readOnly && !isLocked && (isUIAllowed('xcDatatableEditable') || isForm)" v-if="!readOnly && !isLocked && (isUIAllowed('xcDatatableEditable') || isForm) && !isUnderLookup"
class="flex justify-end gap-1 min-h-[30px] items-center" class="flex justify-end gap-1 min-h-[30px] items-center"
> >
<GeneralIcon <GeneralIcon

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

@ -16,6 +16,7 @@ import {
useSelectedCellKeyupListener, useSelectedCellKeyupListener,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
useUIPermission, useUIPermission,
IsUnderLookupInj
} from '#imports' } from '#imports'
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -32,6 +33,8 @@ const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
const childListDlg = ref(false) const childListDlg = ref(false)
@ -112,7 +115,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</template> </template>
</div> </div>
<div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isLocked && !isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon <GeneralIcon
icon="expand" icon="expand"
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

3
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -5,6 +5,7 @@ import {
CellUrlDisableOverlayInj, CellUrlDisableOverlayInj,
CellValueInj, CellValueInj,
ColumnInj, ColumnInj,
IsUnderLookupInj,
MetaInj, MetaInj,
computed, computed,
inject, inject,
@ -70,6 +71,8 @@ const arrValue = computed(() => {
provide(MetaInj, lookupTableMeta) provide(MetaInj, lookupTableMeta)
provide(IsUnderLookupInj, ref(true))
provide(CellUrlDisableOverlayInj, ref(true)) provide(CellUrlDisableOverlayInj, ref(true))
const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activateShowEditNonEditableFieldWarning } = const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activateShowEditNonEditableFieldWarning } =

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

@ -7,6 +7,7 @@ import {
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsLockedInj, IsLockedInj,
IsUnderLookupInj,
ReadonlyInj, ReadonlyInj,
ReloadRowDataHookInj, ReloadRowDataHookInj,
RowInj, RowInj,
@ -34,6 +35,8 @@ const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
const childListDlg = ref(false) const childListDlg = ref(false)
@ -114,7 +117,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</template> </template>
</div> </div>
<div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isLocked && !isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon <GeneralIcon
icon="expand" icon="expand"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

3
packages/nc-gui/composables/useApi/interceptors.ts

@ -67,9 +67,8 @@ export function addAxiosInterceptors(api: Api<any>) {
}) })
.catch(async (error) => { .catch(async (error) => {
await state.signOut() await state.signOut()
// todo: handle new user
navigateTo('/signIn') if (!route.meta.public) navigateTo('/signIn')
return Promise.reject(error) return Promise.reject(error)
}) })

1
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -7,7 +7,6 @@ import { parseProp } from '#imports'
export default function convertCellData( export default function convertCellData(
args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo },
isMysql = false, isMysql = false,
isXcdbBase = false,
) { ) {
const { from, to, value } = args const { from, to, value } = args
if (from === to && ![UITypes.Attachment, UITypes.Date, UITypes.DateTime, UITypes.Time, UITypes.Year].includes(to)) { if (from === to && ![UITypes.Attachment, UITypes.Date, UITypes.DateTime, UITypes.Time, UITypes.Year].includes(to)) {

4
packages/nc-gui/composables/useMultiSelect/index.ts

@ -55,7 +55,7 @@ export function useMultiSelect(
const { appInfo } = useGlobal() const { appInfo } = useGlobal()
const { isMysql, isXcdbBase } = useProject() const { isMysql } = useProject()
let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null)
@ -359,7 +359,6 @@ export function useMultiSelect(
appInfo: unref(appInfo), appInfo: unref(appInfo),
}, },
isMysql(meta.value?.base_id), isMysql(meta.value?.base_id),
isXcdbBase(meta.value?.base_id),
) )
e.preventDefault() e.preventDefault()
@ -394,7 +393,6 @@ export function useMultiSelect(
appInfo: unref(appInfo), appInfo: unref(appInfo),
}, },
isMysql(meta.value?.base_id), isMysql(meta.value?.base_id),
isXcdbBase(meta.value?.base_id),
) )
e.preventDefault() e.preventDefault()
syncCellData?.(activeCell) syncCellData?.(activeCell)

1
packages/nc-gui/context/index.ts

@ -38,3 +38,4 @@ export const ToggleDialogInj: InjectionKey<Function> = Symbol('toggle-dialog-inj
export const CellClickHookInj: InjectionKey<EventHook<MouseEvent> | undefined> = Symbol('cell-click-injection') export const CellClickHookInj: InjectionKey<EventHook<MouseEvent> | undefined> = Symbol('cell-click-injection')
export const SaveRowInj: InjectionKey<(() => void) | undefined> = Symbol('save-row-injection') export const SaveRowInj: InjectionKey<(() => void) | undefined> = Symbol('save-row-injection')
export const CurrentCellInj: InjectionKey<Ref<Element | undefined>> = Symbol('current-cell-injection') export const CurrentCellInj: InjectionKey<Ref<Element | undefined>> = Symbol('current-cell-injection')
export const IsUnderLookupInj: InjectionKey<Ref<boolean>> = Symbol('is-under-lookup-injection')

52
packages/nc-gui/package-lock.json generated

@ -30,7 +30,7 @@
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"locale-codes": "^1.3.1", "locale-codes": "^1.3.1",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"nocodb-sdk": "0.108.0-beta.0", "nocodb-sdk": "file:../nocodb-sdk",
"papaparse": "^5.3.2", "papaparse": "^5.3.2",
"pinia": "^2.0.33", "pinia": "^2.0.33",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
@ -111,7 +111,6 @@
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.108.0-beta.0", "version": "0.108.0-beta.0",
"extraneous": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -8776,6 +8775,7 @@
"version": "1.15.1", "version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -12294,21 +12294,8 @@
} }
}, },
"node_modules/nocodb-sdk": { "node_modules/nocodb-sdk": {
"version": "0.108.0-beta.0", "resolved": "../nocodb-sdk",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.108.0-beta.0.tgz", "link": true
"integrity": "sha512-nLbOOVGxx3KqCvEKHhPE4/ZBS47gBOeo2P6mefo2J0jTVUVq6L7b8iIh5mvvmsXp+UOPxtxfcdKlYiTF++bYtw==",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
}
},
"node_modules/nocodb-sdk/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
}, },
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.23.0", "version": "3.23.0",
@ -24810,7 +24797,8 @@
"follow-redirects": { "follow-redirects": {
"version": "1.15.1", "version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"devOptional": true
}, },
"form-data": { "form-data": {
"version": "4.0.0", "version": "4.0.0",
@ -27360,22 +27348,22 @@
} }
}, },
"nocodb-sdk": { "nocodb-sdk": {
"version": "0.108.0-beta.0", "version": "file:../nocodb-sdk",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.108.0-beta.0.tgz",
"integrity": "sha512-nLbOOVGxx3KqCvEKHhPE4/ZBS47gBOeo2P6mefo2J0jTVUVq6L7b8iIh5mvvmsXp+UOPxtxfcdKlYiTF++bYtw==",
"requires": { "requires": {
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"jsep": "^1.3.6" "cspell": "^4.1.0",
}, "eslint": "^7.8.0",
"dependencies": { "eslint-config-prettier": "^6.11.0",
"axios": { "eslint-plugin-eslint-comments": "^3.2.0",
"version": "0.21.4", "eslint-plugin-functional": "^3.0.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "eslint-plugin-import": "^2.22.0",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "eslint-plugin-prettier": "^4.0.0",
"requires": { "jsep": "^1.3.6",
"follow-redirects": "^1.14.0" "npm-run-all": "^4.1.5",
} "prettier": "^2.1.1",
} "typescript": "^4.0.2"
} }
}, },
"node-abi": { "node-abi": {

2
packages/nc-gui/package.json

@ -54,7 +54,7 @@
"leaflet.markercluster": "^1.5.3", "leaflet.markercluster": "^1.5.3",
"locale-codes": "^1.3.1", "locale-codes": "^1.3.1",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"nocodb-sdk": "0.108.0-beta.0", "nocodb-sdk": "file:../nocodb-sdk",
"papaparse": "^5.3.2", "papaparse": "^5.3.2",
"pinia": "^2.0.33", "pinia": "^2.0.33",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",

9
packages/nc-gui/pages/index/index/index.vue

@ -308,6 +308,7 @@ const copyProjectMeta = async () => {
<div v-if="record.status !== ProjectStatus.JOB" class="flex items-center gap-2"> <div v-if="record.status !== ProjectStatus.JOB" class="flex items-center gap-2">
<component <component
:is="iconMap.edit" :is="iconMap.edit"
v-if="isUIAllowed('projectUpdate', true)"
v-e="['c:project:edit:rename']" v-e="['c:project:edit:rename']"
class="nc-action-btn" class="nc-action-btn"
@click.stop="navigateTo(`/${text}`)" @click.stop="navigateTo(`/${text}`)"
@ -315,12 +316,18 @@ const copyProjectMeta = async () => {
<component <component
:is="iconMap.delete" :is="iconMap.delete"
v-if="isUIAllowed('projectDelete', true)"
class="nc-action-btn" class="nc-action-btn"
:data-testid="`delete-project-${record.title}`" :data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)" @click.stop="deleteProject(record)"
/> />
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop> <a-dropdown
v-if="isUIAllowed('duplicateProject', true)"
:trigger="['click']"
overlay-class-name="nc-dropdown-import-menu"
@click.stop
>
<GeneralIcon <GeneralIcon
icon="threeDotVertical" icon="threeDotVertical"
class="nc-import-menu outline-0" class="nc-import-menu outline-0"

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.107.5", "version": "0.108.0-beta.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.107.5", "version": "0.108.0-beta.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

74
packages/nocodb-sdk/src/lib/Api.ts

@ -546,7 +546,39 @@ export interface FilterType {
| 'notchecked' | 'notchecked'
| 'notempty' | 'notempty'
| 'notnull' | 'notnull'
| 'null'; | 'null'
| null
| (
| 'allof'
| 'anyof'
| 'blank'
| 'btw'
| 'checked'
| 'empty'
| 'eq'
| 'ge'
| 'gt'
| 'gte'
| 'in'
| 'is'
| 'isWithin'
| 'isnot'
| 'le'
| 'like'
| 'lt'
| 'lte'
| 'nallof'
| 'nanyof'
| 'nbtw'
| 'neq'
| 'nlike'
| 'not'
| 'notblank'
| 'notchecked'
| 'notempty'
| 'notnull'
| ('null' & null)
);
/** Comparison Sub-Operator */ /** Comparison Sub-Operator */
comparison_sub_op?: comparison_sub_op?:
| 'daysAgo' | 'daysAgo'
@ -589,7 +621,7 @@ export interface FilterType {
| ('yesterday' & null) | ('yesterday' & null)
); );
/** Foreign Key to Column */ /** Foreign Key to Column */
fk_column_id?: IdType; fk_column_id?: StringOrNullType;
/** Foreign Key to Hook */ /** Foreign Key to Hook */
fk_hook_id?: StringOrNullType; fk_hook_id?: StringOrNullType;
/** Foreign Key to Model */ /** Foreign Key to Model */
@ -664,7 +696,39 @@ export interface FilterReqType {
| 'notchecked' | 'notchecked'
| 'notempty' | 'notempty'
| 'notnull' | 'notnull'
| 'null'; | 'null'
| null
| (
| 'allof'
| 'anyof'
| 'blank'
| 'btw'
| 'checked'
| 'empty'
| 'eq'
| 'ge'
| 'gt'
| 'gte'
| 'in'
| 'is'
| 'isWithin'
| 'isnot'
| 'le'
| 'like'
| 'lt'
| 'lte'
| 'nallof'
| 'nanyof'
| 'nbtw'
| 'neq'
| 'nlike'
| 'not'
| 'notblank'
| 'notchecked'
| 'notempty'
| 'notnull'
| ('null' & null)
);
/** Comparison Sub-Operator */ /** Comparison Sub-Operator */
comparison_sub_op?: comparison_sub_op?:
| 'daysAgo' | 'daysAgo'
@ -707,7 +771,7 @@ export interface FilterReqType {
| ('yesterday' & null) | ('yesterday' & null)
); );
/** Foreign Key to Column */ /** Foreign Key to Column */
fk_column_id?: IdType; fk_column_id?: StringOrNullType;
/** Belong to which filter ID */ /** Belong to which filter ID */
fk_parent_id?: StringOrNullType; fk_parent_id?: StringOrNullType;
/** Is this filter grouped? */ /** Is this filter grouped? */
@ -2255,6 +2319,8 @@ export interface UserType {
* @example org-level-viewer * @example org-level-viewer
*/ */
roles?: string; roles?: string;
/** Access token version */
token_version?: string;
} }
/** /**

10
packages/nocodb/.eslintrc.js

@ -63,12 +63,20 @@ module.exports = {
], ],
}, },
], ],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-this-alias': 'off',
// todo: enable // todo: enable
'@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'no-useless-catch': 'off', 'no-useless-catch': 'off',
'no-empty': 'off', 'no-empty': 'off',

30
packages/nocodb/package-lock.json generated

@ -83,7 +83,7 @@
"nc-lib-gui": "0.108.0-beta.0", "nc-lib-gui": "0.108.0-beta.0",
"nc-plugin": "^0.1.3", "nc-plugin": "^0.1.3",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "0.108.0-beta.0", "nocodb-sdk": "file:../nocodb-sdk",
"nodemailer": "^6.4.10", "nodemailer": "^6.4.10",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"os-locale": "^6.0.2", "os-locale": "^6.0.2",
@ -191,7 +191,6 @@
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.108.0-beta.0", "version": "0.108.0-beta.0",
"extraneous": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -13207,13 +13206,8 @@
} }
}, },
"node_modules/nocodb-sdk": { "node_modules/nocodb-sdk": {
"version": "0.108.0-beta.0", "resolved": "../nocodb-sdk",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.108.0-beta.0.tgz", "link": true
"integrity": "sha512-nLbOOVGxx3KqCvEKHhPE4/ZBS47gBOeo2P6mefo2J0jTVUVq6L7b8iIh5mvvmsXp+UOPxtxfcdKlYiTF++bYtw==",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
}
}, },
"node_modules/node-abort-controller": { "node_modules/node-abort-controller": {
"version": "3.1.1", "version": "3.1.1",
@ -28485,12 +28479,22 @@
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="
}, },
"nocodb-sdk": { "nocodb-sdk": {
"version": "0.108.0-beta.0", "version": "file:../nocodb-sdk",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.108.0-beta.0.tgz",
"integrity": "sha512-nLbOOVGxx3KqCvEKHhPE4/ZBS47gBOeo2P6mefo2J0jTVUVq6L7b8iIh5mvvmsXp+UOPxtxfcdKlYiTF++bYtw==",
"requires": { "requires": {
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"jsep": "^1.3.6" "cspell": "^4.1.0",
"eslint": "^7.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"jsep": "^1.3.6",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.1",
"typescript": "^4.0.2"
} }
}, },
"node-abort-controller": { "node-abort-controller": {

4
packages/nocodb/package.json

@ -116,7 +116,7 @@
"nc-lib-gui": "0.108.0-beta.0", "nc-lib-gui": "0.108.0-beta.0",
"nc-plugin": "^0.1.3", "nc-plugin": "^0.1.3",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "0.108.0-beta.0", "nocodb-sdk": "file:../nocodb-sdk",
"nodemailer": "^6.4.10", "nodemailer": "^6.4.10",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"os-locale": "^6.0.2", "os-locale": "^6.0.2",
@ -203,4 +203,4 @@
"coverageDirectory": "../coverage", "coverageDirectory": "../coverage",
"testEnvironment": "node" "testEnvironment": "node"
} }
} }

6
packages/nocodb/src/cache/RedisCacheMgr.ts vendored

@ -45,7 +45,7 @@ export default class RedisCacheMgr extends CacheMgr {
} }
// @ts-ignore // @ts-ignore
async get(key: string, type: string, config?: any): Promise<any> { async get(key: string, type: string): Promise<any> {
log(`RedisCacheMgr::get: getting key ${key} with type ${type}`); log(`RedisCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) { if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key); return this.client.smembers(key);
@ -135,7 +135,7 @@ export default class RedisCacheMgr extends CacheMgr {
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"] // e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`RedisCacheMgr::getList: getting list with key ${key}`); log(`RedisCacheMgr::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr[0] === 'NONE'; const isNoneList = arr.length && arr.includes('NONE');
if (isNoneList) { if (isNoneList) {
return Promise.resolve({ return Promise.resolve({
@ -248,7 +248,7 @@ export default class RedisCacheMgr extends CacheMgr {
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; : `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`); log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`);
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (list.length && list[0] === 'NONE') { if (list.length && list.includes('NONE')) {
list = []; list = [];
await this.del(listKey); await this.del(listKey);
} }

2
packages/nocodb/src/cache/RedisMockCacheMgr.ts vendored

@ -40,7 +40,7 @@ export default class RedisMockCacheMgr extends CacheMgr {
} }
// @ts-ignore // @ts-ignore
async get(key: string, type: string, config?: any): Promise<any> { async get(key: string, type: string): Promise<any> {
log(`RedisMockCacheMgr::get: getting key ${key} with type ${type}`); log(`RedisMockCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) { if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key); return this.client.smembers(key);

1
packages/nocodb/src/controllers/api-tokens.controller.ts

@ -9,7 +9,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {

1
packages/nocodb/src/controllers/attachments.controller.ts

@ -30,7 +30,6 @@ export class AttachmentsController {
@UploadedFiles() files: Array<any>, @UploadedFiles() files: Array<any>,
@Body() body: any, @Body() body: any,
@Request() req: any, @Request() req: any,
@Query('path') path: string,
) { ) {
const attachments = await this.attachmentsService.upload({ const attachments = await this.attachmentsService.upload({
files: files, files: files,

1
packages/nocodb/src/controllers/audits.controller.ts

@ -10,7 +10,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {

1
packages/nocodb/src/controllers/bases.controller.ts

@ -10,7 +10,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { BaseReqType } from 'nocodb-sdk'; import { BaseReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {

1
packages/nocodb/src/controllers/bulk-data-alias.controller.ts

@ -10,7 +10,6 @@ import {
Response, Response,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/columns.controller.ts

@ -11,7 +11,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ColumnReqType } from 'nocodb-sdk'; import { ColumnReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/data-alias-export.controller.ts

@ -1,6 +1,5 @@
import { Controller, Get, Request, Response, UseGuards } from '@nestjs/common'; import { Controller, Get, Request, Response, UseGuards } from '@nestjs/common';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

3
packages/nocodb/src/controllers/data-alias-nested.controller.ts

@ -8,7 +8,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,
@ -131,7 +130,6 @@ export class DataAliasNestedController {
@Param('projectName') projectName: string, @Param('projectName') projectName: string,
@Param('tableName') tableName: string, @Param('tableName') tableName: string,
@Param('refRowId') refRowId: string, @Param('refRowId') refRowId: string,
@Param('relationType') relationType: string,
) { ) {
await this.dataAliasNestedService.relationDataRemove({ await this.dataAliasNestedService.relationDataRemove({
columnName: columnName, columnName: columnName,
@ -158,7 +156,6 @@ export class DataAliasNestedController {
@Param('projectName') projectName: string, @Param('projectName') projectName: string,
@Param('tableName') tableName: string, @Param('tableName') tableName: string,
@Param('refRowId') refRowId: string, @Param('refRowId') refRowId: string,
@Param('relationType') relationType: string,
) { ) {
await this.dataAliasNestedService.relationDataAdd({ await this.dataAliasNestedService.relationDataAdd({
columnName: columnName, columnName: columnName,

1
packages/nocodb/src/controllers/data-alias.controller.ts

@ -11,7 +11,6 @@ import {
Response, Response,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { parseHrtimeToSeconds } from '../helpers'; import { parseHrtimeToSeconds } from '../helpers';
import { import {

2
packages/nocodb/src/controllers/filters.controller.ts

@ -9,14 +9,12 @@ import {
Post, Post,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { FilterReqType } from 'nocodb-sdk'; import { FilterReqType } from 'nocodb-sdk';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {
Acl, Acl,
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
UseAclMiddleware,
} from '../middlewares/extract-project-id/extract-project-id.middleware'; } from '../middlewares/extract-project-id/extract-project-id.middleware';
import { FiltersService } from '../services/filters.service'; import { FiltersService } from '../services/filters.service';

1
packages/nocodb/src/controllers/form-columns.controller.ts

@ -1,5 +1,4 @@
import { Body, Controller, Param, Patch, UseGuards } from '@nestjs/common'; import { Body, Controller, Param, Patch, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/galleries.controller.ts

@ -9,7 +9,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { GalleryUpdateReqType, ViewCreateReqType } from 'nocodb-sdk'; import { GalleryUpdateReqType, ViewCreateReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/grid-columns.controller.ts

@ -1,6 +1,5 @@
import { Body, Controller, Get, Param, Patch, UseGuards } from '@nestjs/common'; import { Body, Controller, Get, Param, Patch, UseGuards } from '@nestjs/common';
import { GridColumnReqType } from 'nocodb-sdk'; import { GridColumnReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/grids.controller.ts

@ -8,7 +8,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ViewCreateReqType } from 'nocodb-sdk'; import { ViewCreateReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/maps.controller.ts

@ -9,7 +9,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { MapUpdateReqType, ViewCreateReqType } from 'nocodb-sdk'; import { MapUpdateReqType, ViewCreateReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/meta-diffs.controller.ts

@ -6,7 +6,6 @@ import {
Post, Post,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/model-visibilities.controller.ts

@ -8,7 +8,6 @@ import {
Query, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/old-datas/old-datas.controller.ts

@ -11,7 +11,6 @@ import {
Response, Response,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../../guards/global/global.guard'; import { GlobalGuard } from '../../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/org-users.controller.ts

@ -10,7 +10,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';

10
packages/nocodb/src/controllers/plugins.controller.ts

@ -14,11 +14,11 @@ import { Acl } from '../middlewares/extract-project-id/extract-project-id.middle
import { PluginsService } from '../services/plugins.service'; import { PluginsService } from '../services/plugins.service';
// todo: move to a interceptor // todo: move to a interceptor
const blockInCloudMw = (_req, res, next) => { // const blockInCloudMw = (_req, res, next) => {
if (process.env.NC_CLOUD === 'true') { // if (process.env.NC_CLOUD === 'true') {
res.status(403).send('Not allowed'); // res.status(403).send('Not allowed');
} else next(); // } else next();
}; // };
@Controller() @Controller()
@UseGuards(GlobalGuard) @UseGuards(GlobalGuard)

1
packages/nocodb/src/controllers/project-users.controller.ts

@ -11,7 +11,6 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ProjectUserReqType } from 'nocodb-sdk'; import { ProjectUserReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

77
packages/nocodb/src/controllers/projects.controller.ts

@ -11,15 +11,13 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import isDocker from 'is-docker'; import isDocker from 'is-docker';
import { ProjectReqType } from 'nocodb-sdk'; import { ProjectReqType } from 'nocodb-sdk';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {
Acl,
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
UseAclMiddleware,
UseProjectIdMiddleware,
} from '../middlewares/extract-project-id/extract-project-id.middleware'; } from '../middlewares/extract-project-id/extract-project-id.middleware';
import Noco from '../Noco'; import Noco from '../Noco';
import { packageVersion } from '../utils/packageVersion'; import { packageVersion } from '../utils/packageVersion';
@ -31,9 +29,7 @@ import type { ProjectType } from 'nocodb-sdk';
export class ProjectsController { export class ProjectsController {
constructor(private readonly projectsService: ProjectsService) {} constructor(private readonly projectsService: ProjectsService) {}
@UseAclMiddleware({ @Acl('projectList')
permissionName: 'projectList',
})
@Get('/api/v1/db/meta/projects/') @Get('/api/v1/db/meta/projects/')
async list(@Query() queryParams: Record<string, any>, @Request() req) { async list(@Query() queryParams: Record<string, any>, @Request() req) {
const projects = await this.projectsService.projectList({ const projects = await this.projectsService.projectList({
@ -57,7 +53,7 @@ export class ProjectsController {
PackageVersion: packageVersion, PackageVersion: packageVersion,
}; };
} }
@Acl('projectGet')
@Get('/api/v1/db/meta/projects/:projectId') @Get('/api/v1/db/meta/projects/:projectId')
async projectGet(@Param('projectId') projectId: string) { async projectGet(@Param('projectId') projectId: string) {
const project = await this.projectsService.getProjectWithInfo({ const project = await this.projectsService.getProjectWithInfo({
@ -68,7 +64,7 @@ export class ProjectsController {
return project; return project;
} }
@Acl('projectUpdate')
@Patch('/api/v1/db/meta/projects/:projectId') @Patch('/api/v1/db/meta/projects/:projectId')
async projectUpdate( async projectUpdate(
@Param('projectId') projectId: string, @Param('projectId') projectId: string,
@ -82,6 +78,7 @@ export class ProjectsController {
return project; return project;
} }
@Acl('projectDelete')
@Delete('/api/v1/db/meta/projects/:projectId') @Delete('/api/v1/db/meta/projects/:projectId')
async projectDelete(@Param('projectId') projectId: string) { async projectDelete(@Param('projectId') projectId: string) {
const deleted = await this.projectsService.projectSoftDelete({ const deleted = await this.projectsService.projectSoftDelete({
@ -91,6 +88,7 @@ export class ProjectsController {
return deleted; return deleted;
} }
@Acl('projectCreate')
@Post('/api/v1/db/meta/projects') @Post('/api/v1/db/meta/projects')
@HttpCode(200) @HttpCode(200)
async projectCreate(@Body() projectBody: ProjectReqType, @Request() req) { async projectCreate(@Body() projectBody: ProjectReqType, @Request() req) {
@ -102,66 +100,3 @@ export class ProjectsController {
return project; return project;
} }
} }
/*
// // Project CRUD
export async function projectCost(req, res) {
let cost = 0;
const project = await Project.getWithInfo(req.params.projectId);
for (const base of project.bases) {
const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
const userCount = await ProjectUser.getUsersCount(req.query);
const recordCount = (await sqlClient.totalRecords())?.data.TotalRecords;
if (recordCount > 100000) {
// 36,000 or $79/user/month
cost = Math.max(36000, 948 * userCount);
} else if (recordCount > 50000) {
// $36,000 or $50/user/month
cost = Math.max(36000, 600 * userCount);
} else if (recordCount > 10000) {
// $240/user/yr
cost = Math.min(240 * userCount, 36000);
} else if (recordCount > 1000) {
// $120/user/yr
cost = Math.min(120 * userCount, 36000);
}
}
T.event({
event: 'a:project:cost',
data: {
cost,
},
});
res.json({ cost });
}
export async function hasEmptyOrNullFilters(req, res) {
res.json(await Filter.hasEmptyOrNullFilters(req.params.projectId));
}
export default (router) => {
router.get(
'/api/v1/db/meta/projects/:projectId/cost',
metaApiMetrics,
ncMetaAclMw(projectCost, 'projectCost')
);
router.get(
'/api/v1/db/meta/projects/:projectId/has-empty-or-null-filters',
metaApiMetrics,
ncMetaAclMw(hasEmptyOrNullFilters, 'hasEmptyOrNullFilters')
);
};
* */

1
packages/nocodb/src/controllers/shared-bases.controller.ts

@ -10,7 +10,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { import {
Acl, Acl,

1
packages/nocodb/src/controllers/sorts.controller.ts

@ -9,7 +9,6 @@ import {
Post, Post,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { SortReqType } from 'nocodb-sdk'; import { SortReqType } from 'nocodb-sdk';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';

1
packages/nocodb/src/controllers/tables.controller.ts

@ -11,7 +11,6 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { TableReqType } from 'nocodb-sdk'; import { TableReqType } from 'nocodb-sdk';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import extractRolesObj from '../utils/extractRolesObj'; import extractRolesObj from '../utils/extractRolesObj';

117
packages/nocodb/src/controllers/users/users.controller.ts

@ -1,5 +1,3 @@
import { promisify } from 'util';
import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
import { import {
Body, Body,
Controller, Controller,
@ -19,23 +17,17 @@ import {
Acl, Acl,
ExtractProjectIdMiddleware, ExtractProjectIdMiddleware,
} from '../../middlewares/extract-project-id/extract-project-id.middleware'; } from '../../middlewares/extract-project-id/extract-project-id.middleware';
import Noco from '../../Noco'; import { User } from '../../models';
import { GoogleStrategy } from '../../strategies/google.strategy/google.strategy';
import extractRolesObj from '../../utils/extractRolesObj';
import { Audit, User } from '../../models';
import { import {
genJwt,
randomTokenString, randomTokenString,
setTokenCookie, setTokenCookie,
} from '../../services/users/helpers'; } from '../../services/users/helpers';
import { UsersService } from '../../services/users/users.service'; import { UsersService } from '../../services/users/users.service';
import extractRolesObj from '../../utils/extractRolesObj';
@Controller() @Controller()
export class UsersController { export class UsersController {
constructor( constructor(private readonly usersService: UsersService) {}
private readonly usersService: UsersService,
private googleStrategy: GoogleStrategy,
) {}
@Post([ @Post([
'/auth/user/signup', '/auth/user/signup',
@ -59,56 +51,14 @@ export class UsersController {
'/api/v1/auth/token/refresh', '/api/v1/auth/token/refresh',
]) ])
@HttpCode(200) @HttpCode(200)
async refreshToken(@Request() req: any, @Request() res: any): Promise<any> { async refreshToken(@Request() req: any, @Response() res: any): Promise<any> {
return await this.usersService.refreshToken({ res.json(
body: req.body, await this.usersService.refreshToken({
req, body: req.body,
res, req,
}); res,
} }),
);
async successfulSignIn({ user, err, info, req, res, auditDescription }) {
try {
if (!user || !user.email) {
if (err) {
return res.status(400).send(err);
}
if (info) {
return res.status(400).send(info);
}
return res.status(400).send({ msg: 'Your signin has failed' });
}
await promisify((req as any).login.bind(req))(user);
const refreshToken = randomTokenString();
if (!user.token_version) {
user.token_version = randomTokenString();
}
await User.update(user.id, {
refresh_token: refreshToken,
email: user.email,
token_version: user.token_version,
});
setTokenCookie(res, refreshToken);
await Audit.insert({
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.SIGNIN,
user: user.email,
ip: req.clientIp,
description: auditDescription,
});
res.json({
token: genJwt(user, Noco.getConfig()),
} as any);
} catch (e) {
console.log(e);
throw e;
}
} }
@Post([ @Post([
@ -118,8 +68,9 @@ export class UsersController {
]) ])
@UseGuards(AuthGuard('local')) @UseGuards(AuthGuard('local'))
@HttpCode(200) @HttpCode(200)
async signin(@Request() req) { async signin(@Request() req, @Response() res) {
return this.usersService.login(req.user); await this.setRefreshToken({ req, res });
res.json(this.usersService.login(req.user));
} }
@Post('/api/v1/auth/user/signout') @Post('/api/v1/auth/user/signout')
@ -136,18 +87,15 @@ export class UsersController {
@Post(`/auth/google/genTokenByCode`) @Post(`/auth/google/genTokenByCode`)
@HttpCode(200) @HttpCode(200)
@UseGuards(AuthGuard('google')) @UseGuards(AuthGuard('google'))
async googleSignin(@Request() req) { async googleSignin(@Request() req, @Response() res) {
return this.usersService.login(req.user); await this.setRefreshToken({ req, res });
res.json(this.usersService.login(req.user));
} }
@Get('/auth/google') @Get('/auth/google')
@UseGuards(AuthGuard('google')) @UseGuards(AuthGuard('google'))
googleAuthenticate(@Request() req) { googleAuthenticate() {
// this.googleStrategy.authenticate(req, { // google strategy will take care the request
// scope: ['profile', 'email'],
// state: req.query.state,
// callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath,
// });
} }
@Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me']) @Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me'])
@ -168,7 +116,7 @@ export class UsersController {
@UseGuards(GlobalGuard) @UseGuards(GlobalGuard)
@Acl('passwordChange') @Acl('passwordChange')
@HttpCode(200) @HttpCode(200)
async passwordChange(@Request() req: any, @Body() body: any): Promise<any> { async passwordChange(@Request() req: any): Promise<any> {
if (!(req as any).isAuthenticated()) { if (!(req as any).isAuthenticated()) {
NcError.forbidden('Not allowed'); NcError.forbidden('Not allowed');
} }
@ -188,7 +136,7 @@ export class UsersController {
'/api/v1/auth/password/forgot', '/api/v1/auth/password/forgot',
]) ])
@HttpCode(200) @HttpCode(200)
async passwordForgot(@Request() req: any, @Body() body: any): Promise<any> { async passwordForgot(@Request() req: any): Promise<any> {
await this.usersService.passwordForgot({ await this.usersService.passwordForgot({
siteUrl: (req as any).ncSiteUrl, siteUrl: (req as any).ncSiteUrl,
body: req.body, body: req.body,
@ -269,4 +217,27 @@ export class UsersController {
return res.status(400).json({ msg: e.message }); return res.status(400).json({ msg: e.message });
} }
} }
async setRefreshToken({ res, req }) {
const userId = req.user?.id;
if (!userId) return;
const user = await User.get(userId);
if (!user) return;
const refreshToken = randomTokenString();
if (!user.token_version) {
user.token_version = randomTokenString();
}
await User.update(user.id, {
refresh_token: refreshToken,
email: user.email,
token_version: user.token_version,
});
setTokenCookie(res, refreshToken);
}
} }

3
packages/nocodb/src/controllers/view-columns.controller.ts

@ -8,8 +8,7 @@ import {
Post, Post,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ColumnReqType, ViewColumnReqType } from 'nocodb-sdk'; import { ViewColumnReqType } from 'nocodb-sdk';
import { AuthGuard } from '@nestjs/passport';
import { GlobalGuard } from '../guards/global/global.guard'; import { GlobalGuard } from '../guards/global/global.guard';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { import {

6
packages/nocodb/src/db/BaseModelSql.ts

@ -1613,7 +1613,7 @@ class BaseModelSql extends BaseModel {
} }
} }
async nestedRead(id, { where, fields: fields1, f, ...rest }, trx = null) { async nestedRead(id, { fields: fields1, f, ...rest }, trx = null) {
rest = Object.assign({}, this.defaultNestedQueryParams, rest); rest = Object.assign({}, this.defaultNestedQueryParams, rest);
const { hm: childs = '', bt: parents = '', mm: many = '' } = rest; const { hm: childs = '', bt: parents = '', mm: many = '' } = rest;
@ -1852,7 +1852,7 @@ class BaseModelSql extends BaseModel {
return null; return null;
} }
// @ts-ignore // @ts-ignore
const { tn, cn, vtn, vcn, vrcn, rtn, rcn } = const { vtn, vcn, vrcn, rtn, rcn } =
this.manyToManyRelations.find(({ vtn }) => assoc === vtn) || {}; this.manyToManyRelations.find(({ vtn }) => assoc === vtn) || {};
const childModel = this.dbModels[rtn]; const childModel = this.dbModels[rtn];
@ -1894,7 +1894,7 @@ class BaseModelSql extends BaseModel {
return null; return null;
} }
// @ts-ignore // @ts-ignore
const { tn, cn, vtn, vcn, vrcn, rtn, rcn } = const { vtn, vcn, vrcn, rtn, rcn } =
this.manyToManyRelations.find(({ vtn }) => assoc === vtn) || {}; this.manyToManyRelations.find(({ vtn }) => assoc === vtn) || {};
const childModel = this.dbModels[rtn]; const childModel = this.dbModels[rtn];

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

@ -19,16 +19,7 @@ import DOMPurify from 'isomorphic-dompurify';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { NcError } from '../helpers/catchError'; import { NcError } from '../helpers/catchError';
import getAst from '../helpers/getAst'; import getAst from '../helpers/getAst';
import { import { Audit, Column, Filter, Model, Project, Sort, View } from '../models';
Audit,
Base,
Column,
Filter,
Model,
Project,
Sort,
View,
} from '../models';
import { sanitize, unsanitize } from '../helpers/sqlSanitize'; import { sanitize, unsanitize } from '../helpers/sqlSanitize';
import { import {
COMPARISON_OPS, COMPARISON_OPS,
@ -57,7 +48,7 @@ import type {
SelectOption, SelectOption,
} from '../models'; } from '../models';
import type { Knex } from 'knex'; import type { Knex } from 'knex';
import type { BoolType, SortType } from 'nocodb-sdk'; import type { SortType } from 'nocodb-sdk';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
@ -1361,12 +1352,6 @@ class BaseModelSqlv2 {
const columns = await this.model.getColumns(); const columns = await this.model.getColumns();
for (const column of columns) { for (const column of columns) {
switch (column.uidt) { switch (column.uidt) {
case UITypes.Rollup:
{
// @ts-ignore
const colOptions: RollupColumn = await column.getColOptions();
}
break;
case UITypes.Lookup: case UITypes.Lookup:
{ {
// @ts-ignore // @ts-ignore

2
packages/nocodb/src/db/CustomKnex.ts

@ -1,6 +1,6 @@
import { Knex, knex } from 'knex'; import { Knex, knex } from 'knex';
import { SnowflakeClient } from 'nc-help'; import { SnowflakeClient } from 'nc-help';
import pg, { types } from 'pg'; import { types } from 'pg';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Filter from '../models/Filter'; import Filter from '../models/Filter';
import type { FilterType } from 'nocodb-sdk'; import type { FilterType } from 'nocodb-sdk';

2
packages/nocodb/src/db/conditionV2.ts

@ -1,5 +1,5 @@
import { isNumericCol, RelationTypes, UITypes } from 'nocodb-sdk'; import { isNumericCol, RelationTypes, UITypes } from 'nocodb-sdk';
import dayjs, { extend } from 'dayjs'; import dayjs from 'dayjs';
// import customParseFormat from 'dayjs/plugin/customParseFormat.js'; // import customParseFormat from 'dayjs/plugin/customParseFormat.js';
import Filter from '../models/Filter'; import Filter from '../models/Filter';
import { sanitize } from '../helpers/sqlSanitize'; import { sanitize } from '../helpers/sqlSanitize';

2
packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts

@ -1,7 +1,5 @@
import fs from 'fs'; import fs from 'fs';
import { promisify } from 'util'; import { promisify } from 'util';
import Noco from '../../../Noco';
import SqlClientFactoryEE from './ee/SqlClientFactoryEE';
import MySqlClient from './mysql/MysqlClient'; import MySqlClient from './mysql/MysqlClient';
import MssqlClient from './mssql/MssqlClient'; import MssqlClient from './mssql/MssqlClient';
import OracleClient from './oracle/OracleClient'; import OracleClient from './oracle/OracleClient';

1
packages/nocodb/src/interceptors/is-upload-allowed/is-upload-allowed.interceptor.ts

@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk'; import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk';
import { throwError } from 'rxjs';
import { NcError } from '../../helpers/catchError'; import { NcError } from '../../helpers/catchError';
import Noco from '../../Noco'; import Noco from '../../Noco';
import extractRolesObj from '../../utils/extractRolesObj'; import extractRolesObj from '../../utils/extractRolesObj';

9
packages/nocodb/src/middlewares/extract-project-id/extract-project-id.middleware.ts

@ -1,10 +1,7 @@
import { promisify } from 'util';
import { Injectable, SetMetadata, UseInterceptors } from '@nestjs/common'; import { Injectable, SetMetadata, UseInterceptors } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { NextFunction, Request, Response } from 'express';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import passport from 'passport'; import { map } from 'rxjs';
import { map, throwError } from 'rxjs';
import { import {
Column, Column,
Filter, Filter,
@ -19,8 +16,7 @@ import {
} from '../../models'; } from '../../models';
import extractRolesObj from '../../utils/extractRolesObj'; import extractRolesObj from '../../utils/extractRolesObj';
import projectAcl from '../../utils/projectAcl'; import projectAcl from '../../utils/projectAcl';
import catchError, { NcError } from '../catchError'; import { NcError } from '../catchError';
import extractProjectIdAndAuthenticate from '../extractProjectIdAndAuthenticate';
import type { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import type { import type {
CallHandler, CallHandler,
@ -216,7 +212,6 @@ export class AclMiddleware implements NestInterceptor {
); );
const req = context.switchToHttp().getRequest(); const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
req.customProperty = 'This is a custom property'; req.customProperty = 'This is a custom property';
const roles: Record<string, boolean> = extractRolesObj(req.user?.roles); const roles: Record<string, boolean> = extractRolesObj(req.user?.roles);

2
packages/nocodb/src/models/Model.ts

@ -16,7 +16,6 @@ import { sanitize } from '../helpers/sqlSanitize';
import { extractProps } from '../helpers/extractProps'; import { extractProps } from '../helpers/extractProps';
import Audit from './Audit'; import Audit from './Audit';
import View from './View'; import View from './View';
import Base from './Base';
import Column from './Column'; import Column from './Column';
import type { BoolType, TableReqType, TableType } from 'nocodb-sdk'; import type { BoolType, TableReqType, TableType } from 'nocodb-sdk';
import type { XKnex } from '../db/CustomKnex'; import type { XKnex } from '../db/CustomKnex';
@ -471,7 +470,6 @@ export default class Model implements TableType {
knex, knex,
) { ) {
const insertObj = {}; const insertObj = {};
const base = await Base.get(this.base_id);
for (const col of await this.getColumns()) { for (const col of await this.getColumns()) {
if (isVirtualCol(col)) continue; if (isVirtualCol(col)) continue;
let val = let val =

20
packages/nocodb/src/models/ProjectUser.ts

@ -174,11 +174,6 @@ export default class ProjectUser {
} }
static async delete(projectId: string, userId: string, ncMeta = Noco.ncMeta) { static async delete(projectId: string, userId: string, ncMeta = Noco.ncMeta) {
// await NocoCache.deepDel(
// CacheScope.PROJECT_USER,
// `${CacheScope.PROJECT_USER}:${projectId}:${userId}`,
// CacheDelDirection.CHILD_TO_PARENT
// );
const { email } = await ncMeta.metaGet2(null, null, MetaTable.USERS, { const { email } = await ncMeta.metaGet2(null, null, MetaTable.USERS, {
id: userId, id: userId,
}); });
@ -194,11 +189,16 @@ export default class ProjectUser {
const { isNoneList } = cachedList; const { isNoneList } = cachedList;
if (!isNoneList && cachedProjectList?.length) { if (!isNoneList && cachedProjectList?.length) {
cachedProjectList = cachedProjectList.filter((p) => p.id !== projectId); cachedProjectList = cachedProjectList.filter((p) => p.id !== projectId);
await NocoCache.setList( // delete the whole list first so that the old one won't be included
CacheScope.USER_PROJECT, await NocoCache.del(`${CacheScope.USER_PROJECT}:${userId}:list`);
[userId], if (cachedProjectList.length > 0) {
cachedProjectList, // set the updated list (i.e. excluding the to-be-deleted project id)
); await NocoCache.setList(
CacheScope.USER_PROJECT,
[userId],
cachedProjectList,
);
}
} }
await NocoCache.del(`${CacheScope.PROJECT_USER}:${projectId}:${userId}`); await NocoCache.del(`${CacheScope.PROJECT_USER}:${projectId}:${userId}`);

1
packages/nocodb/src/modules/datas/helpers.ts

@ -12,7 +12,6 @@ import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import type { BaseModelSqlv2 } from '../../db/BaseModelSqlv2'; import type { BaseModelSqlv2 } from '../../db/BaseModelSqlv2';
import type LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn'; import type LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
import type LookupColumn from '../../models/LookupColumn'; import type LookupColumn from '../../models/LookupColumn';
import type { Request } from 'express';
export interface PathParams { export interface PathParams {
projectName: string; projectName: string;

3
packages/nocodb/src/modules/jobs/jobs/at-import/helpers/readAndProcessData.ts

@ -21,14 +21,12 @@ async function readAllData({
fields, fields,
base, base,
logBasic = (_str) => {}, logBasic = (_str) => {},
services,
}: { }: {
table: { title?: string }; table: { title?: string };
fields?; fields?;
base: AirtableBase; base: AirtableBase;
logBasic?: (string) => void; logBasic?: (string) => void;
logDetailed?: (string) => void; logDetailed?: (string) => void;
services: AirtableImportContext;
}): Promise<EntityMap> { }): Promise<EntityMap> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let data = null; let data = null;
@ -229,7 +227,6 @@ export async function importLTARData({
base, base,
logDetailed, logDetailed,
logBasic, logBasic,
services,
})); }));
const modelMeta: any = const modelMeta: any =

5
packages/nocodb/src/modules/users/users.module.ts

@ -1,9 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { import { GoogleStrategyProvider } from '../../strategies/google.strategy/google.strategy';
GoogleStrategy,
GoogleStrategyProvider,
} from '../../strategies/google.strategy/google.strategy';
import { GlobalModule } from '../global/global.module'; import { GlobalModule } from '../global/global.module';
import { UsersService } from '../../services/users/users.service'; import { UsersService } from '../../services/users/users.service';
import { UsersController } from '../../controllers/users/users.controller'; import { UsersController } from '../../controllers/users/users.controller';

1
packages/nocodb/src/run/docker.ts

@ -1,4 +1,3 @@
import axios from 'axios';
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express from 'express';
import Noco from '../Noco'; import Noco from '../Noco';

1
packages/nocodb/src/run/dockerRunPG_CyQuick.ts

@ -1,7 +1,6 @@
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express from 'express';
import Noco from '../Noco'; import Noco from '../Noco';
import nocobuild from '../nocobuild';
const server = express(); const server = express();
server.enable('trust proxy'); server.enable('trust proxy');

150
packages/nocodb/src/schema/swagger.json

@ -15197,38 +15197,45 @@
}, },
"comparison_op": { "comparison_op": {
"description": "Comparison Operator", "description": "Comparison Operator",
"enum": [ "anyOf": [
"allof", {
"anyof", "enum": [
"blank", "allof",
"btw", "anyof",
"checked", "blank",
"empty", "btw",
"eq", "checked",
"ge", "empty",
"gt", "eq",
"gte", "ge",
"in", "gt",
"is", "gte",
"isWithin", "in",
"isnot", "is",
"le", "isWithin",
"like", "isnot",
"lt", "le",
"lte", "like",
"nallof", "lt",
"nanyof", "lte",
"nbtw", "nallof",
"neq", "nanyof",
"nlike", "nbtw",
"not", "neq",
"notblank", "nlike",
"notchecked", "not",
"notempty", "notblank",
"notnull", "notchecked",
"null" "notempty",
], "notnull",
"type": "string" "null"
],
"type": "string"
},
{
"type": "null"
}
]
}, },
"comparison_sub_op": { "comparison_sub_op": {
"anyOf": [ "anyOf": [
@ -15262,7 +15269,7 @@
"description": "Comparison Sub-Operator" "description": "Comparison Sub-Operator"
}, },
"fk_column_id": { "fk_column_id": {
"$ref": "#/components/schemas/Id", "$ref": "#/components/schemas/StringOrNull",
"description": "Foreign Key to Column" "description": "Foreign Key to Column"
}, },
"fk_hook_id": { "fk_hook_id": {
@ -15518,38 +15525,45 @@
"properties": { "properties": {
"comparison_op": { "comparison_op": {
"description": "Comparison Operator", "description": "Comparison Operator",
"enum": [ "anyOf": [
"allof", {
"anyof", "enum": [
"blank", "allof",
"btw", "anyof",
"checked", "blank",
"empty", "btw",
"eq", "checked",
"ge", "empty",
"gt", "eq",
"gte", "ge",
"in", "gt",
"is", "gte",
"isWithin", "in",
"isnot", "is",
"le", "isWithin",
"like", "isnot",
"lt", "le",
"lte", "like",
"nallof", "lt",
"nanyof", "lte",
"nbtw", "nallof",
"neq", "nanyof",
"nlike", "nbtw",
"not", "neq",
"notblank", "nlike",
"notchecked", "not",
"notempty", "notblank",
"notnull", "notchecked",
"null" "notempty",
], "notnull",
"type": "string" "null"
],
"type": "string"
},
{
"type": "null"
}
]
}, },
"comparison_sub_op": { "comparison_sub_op": {
"anyOf": [ "anyOf": [
@ -15583,7 +15597,7 @@
"description": "Comparison Sub-Operator" "description": "Comparison Sub-Operator"
}, },
"fk_column_id": { "fk_column_id": {
"$ref": "#/components/schemas/Id", "$ref": "#/components/schemas/StringOrNull",
"description": "Foreign Key to Column" "description": "Foreign Key to Column"
}, },
"fk_parent_id": { "fk_parent_id": {
@ -20000,6 +20014,10 @@
"description": "The roles of the user", "description": "The roles of the user",
"example": "org-level-viewer", "example": "org-level-viewer",
"type": "string" "type": "string"
},
"token_version": {
"description": "Access token version",
"type": "string"
} }
}, },
"required": ["email", "email_verified", "firstname", "id", "lastname"], "required": ["email", "email_verified", "firstname", "id", "lastname"],

5
packages/nocodb/src/services/bulk-data-alias.service.ts

@ -1,8 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { isSystemColumn, UITypes } from 'nocodb-sdk'; import { Base, Model } from '../models';
import * as XLSX from 'xlsx';
import { NcError } from '../helpers/catchError';
import { Base, Column, Model, Project, View } from '../models';
import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
import { getViewAndModelByAliasOrId } from '../modules/datas/helpers'; import { getViewAndModelByAliasOrId } from '../modules/datas/helpers';
import type { PathParams } from '../modules/datas/helpers'; import type { PathParams } from '../modules/datas/helpers';

90
packages/nocodb/src/services/public-metas.service.ts

@ -2,8 +2,7 @@ import { Injectable } from '@nestjs/common';
import { ErrorMessages, RelationTypes, UITypes } from 'nocodb-sdk'; import { ErrorMessages, RelationTypes, UITypes } from 'nocodb-sdk';
import { NcError } from '../helpers/catchError'; import { NcError } from '../helpers/catchError';
import { Base, Column, Model, Project, View } from '../models'; import { Base, Column, Model, Project, View } from '../models';
import type { LinkToAnotherRecordColumn } from '../models'; import type { LinkToAnotherRecordColumn, LookupColumn } from '../models';
import type { LinkToAnotherRecordType } from 'nocodb-sdk';
@Injectable() @Injectable()
export class PublicMetasService { export class PublicMetasService {
@ -63,23 +62,88 @@ export class PublicMetasService {
// load related table metas // load related table metas
for (const col of view.model.columns) { for (const col of view.model.columns) {
if (UITypes.LinkToAnotherRecord === col.uidt) { await this.extractRelatedMetas({ col, relatedMetas });
const colOpt = await col.getColOptions<LinkToAnotherRecordType>();
relatedMetas[colOpt.fk_related_model_id] = await Model.getWithInfo({
id: colOpt.fk_related_model_id,
});
if (colOpt.type === 'mm') {
relatedMetas[colOpt.fk_mm_model_id] = await Model.getWithInfo({
id: colOpt.fk_mm_model_id,
});
}
}
} }
view.relatedMetas = relatedMetas; view.relatedMetas = relatedMetas;
return view; return view;
} }
private async extractRelatedMetas({
col,
relatedMetas = {},
}: {
col: Column<any>;
relatedMetas: Record<string, Model>;
}) {
if (UITypes.LinkToAnotherRecord === col.uidt) {
await this.extractLTARRelatedMetas({
ltarColOption: await col.getColOptions<LinkToAnotherRecordColumn>(),
relatedMetas,
});
} else if (UITypes.Lookup === col.uidt) {
await this.extractLookupRelatedMetas({
lookupColOption: await col.getColOptions<LookupColumn>(),
relatedMetas,
});
}
}
private async extractLTARRelatedMetas({
ltarColOption,
relatedMetas = {},
}: {
ltarColOption: LinkToAnotherRecordColumn;
relatedMetas: { [key: string]: Model };
}) {
relatedMetas[ltarColOption.fk_related_model_id] = await Model.getWithInfo({
id: ltarColOption.fk_related_model_id,
});
if (ltarColOption.type === 'mm') {
relatedMetas[ltarColOption.fk_mm_model_id] = await Model.getWithInfo({
id: ltarColOption.fk_mm_model_id,
});
}
}
private async extractLookupRelatedMetas({
lookupColOption,
relatedMetas = {},
}: {
lookupColOption: LookupColumn;
relatedMetas: { [key: string]: Model };
}) {
const relationCol = await Column.get({
colId: lookupColOption.fk_relation_column_id,
});
const lookedUpCol = await Column.get({
colId: lookupColOption.fk_lookup_column_id,
});
// extract meta for table which belongs the relation column
// if not already extracted
if (!relatedMetas[relationCol.fk_model_id]) {
relatedMetas[relationCol.fk_model_id] = await Model.getWithInfo({
id: relationCol.fk_model_id,
});
}
// extract meta for table in which looked up column belongs
// if not already extracted
if (!relatedMetas[lookedUpCol.fk_model_id]) {
relatedMetas[lookedUpCol.fk_model_id] = await Model.getWithInfo({
id: lookedUpCol.fk_model_id,
});
}
// extract metas related to the looked up column
await this.extractRelatedMetas({
col: lookedUpCol,
relatedMetas,
});
}
async publicSharedBaseGet(param: { sharedBaseUuid: string }): Promise<any> { async publicSharedBaseGet(param: { sharedBaseUuid: string }): Promise<any> {
const project = await Project.getByUuid(param.sharedBaseUuid); const project = await Project.getByUuid(param.sharedBaseUuid);

4
packages/nocodb/src/services/users/users.service.ts

@ -486,9 +486,9 @@ export class UsersService {
return this.login(user); return this.login(user);
} }
async login(user: any) { login(user: UserType) {
return { return {
token: genJwt(user, Noco.getConfig()), //this.jwtService.sign(payload), token: genJwt(user, Noco.getConfig()),
}; };
} }

2
packages/nocodb/src/utils/nc-config/NcConfig.ts

@ -126,6 +126,8 @@ export class NcConfig {
if (dashboardPath) { if (dashboardPath) {
ncConfig.dashboardPath = dashboardPath; ncConfig.dashboardPath = dashboardPath;
} else {
ncConfig.dashboardPath = '/dashboard';
} }
try { try {

1
packages/nocodb/src/version-upgrader/v1-legacy/gql/GqlApiBuilder.ts

@ -1,4 +1,3 @@
import debug from 'debug';
import { Router } from 'express'; import { Router } from 'express';
import GqlXcSchemaFactory from '../../../db/sql-mgr/code/gql-schema/xc-ts/GqlXcSchemaFactory'; import GqlXcSchemaFactory from '../../../db/sql-mgr/code/gql-schema/xc-ts/GqlXcSchemaFactory';
import BaseApiBuilder from '../BaseApiBuilder'; import BaseApiBuilder from '../BaseApiBuilder';

5
packages/nocodb/src/version-upgrader/v1-legacy/rest/RestApiBuilder.ts

@ -1,17 +1,14 @@
import debug from 'debug';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import SwaggerXc from '../../../db/sql-mgr/code/routers/xc-ts/SwaggerXc'; import SwaggerXc from '../../../db/sql-mgr/code/routers/xc-ts/SwaggerXc';
import ExpressXcTsRoutes from '../../../db/sql-mgr/code/routes/xc-ts/ExpressXcTsRoutes'; import ExpressXcTsRoutes from '../../../db/sql-mgr/code/routes/xc-ts/ExpressXcTsRoutes';
import NcHelp from '../../../utils/NcHelp'; import NcHelp from '../../../utils/NcHelp';
import BaseApiBuilder, { XcTablesPopulateParams } from '../BaseApiBuilder'; import BaseApiBuilder from '../BaseApiBuilder';
import type { MetaService } from '../../../meta/meta.service'; import type { MetaService } from '../../../meta/meta.service';
import type Noco from '../../../Noco'; import type Noco from '../../../Noco';
import type { Router } from 'express'; import type { Router } from 'express';
import type { DbConfig, NcConfig } from '../../../interface/config'; import type { DbConfig, NcConfig } from '../../../interface/config';
import type NcProjectBuilder from '../NcProjectBuilder'; import type NcProjectBuilder from '../NcProjectBuilder';
const log = debug('nc:api:rest');
export class RestApiBuilder extends BaseApiBuilder<Noco> { export class RestApiBuilder extends BaseApiBuilder<Noco> {
public readonly type = 'rest'; public readonly type = 'rest';

34
tests/playwright/tests/db/timezone.spec.ts

@ -147,9 +147,16 @@ test.describe('Timezone-XCDB : Japan/Tokyo', () => {
await dashboard.treeView.openTable({ title: 'dateTimeTable' }); await dashboard.treeView.openTable({ title: 'dateTimeTable' });
// DateTime inserted using API without timezone is converted to UTC // DateTime inserted using API without timezone is converted to db-timezone (server timezone in case of sqlite)
// Display value is converted to Asia/Tokyo // Display value is converted to Asia/Tokyo
await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 09:00' }); const dateInserted = new Date(`2021-01-01 00:00:00${getBrowserTimezoneOffset()}`);
// convert dateInserted to Japan/Tokyo timezone in YYYY-MM-DD HH:mm format
const dateInsertedInJapan = new Date(dateInserted.getTime() + 9 * 60 * 60 * 1000)
.toISOString()
.slice(0, 16)
.replace('T', ' ');
await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: dateInsertedInJapan });
// DateTime inserted using API with timezone is converted to UTC // DateTime inserted using API with timezone is converted to UTC
// Display value is converted to Asia/Tokyo // Display value is converted to Asia/Tokyo
@ -173,8 +180,16 @@ test.describe('Timezone-XCDB : Japan/Tokyo', () => {
test('API Insert, verify API read response', async () => { test('API Insert, verify API read response', async () => {
if (!isSqlite(context)) return; if (!isSqlite(context)) return;
const dateInserted = new Date(`2021-01-01 00:00:00${getBrowserTimezoneOffset()}`);
// translate dateInserted to UTC in YYYY-MM-DD HH:mm format
const dateInsertedInUTC = dateInserted.toISOString().replace('T', ' ').replace('Z', '');
// UTC expected response // UTC expected response
const dateUTC = ['2021-01-01 00:00:00+00:00', '2021-01-01 00:00:00+00:00', '2021-01-01 00:00:00+00:00']; const dateUTC = [
`${dateInsertedInUTC.slice(0, 19)}+00:00`,
'2021-01-01 00:00:00+00:00',
'2021-01-01 00:00:00+00:00',
];
const readDate = records.list.map(record => record.DateTime); const readDate = records.list.map(record => record.DateTime);
@ -231,7 +246,18 @@ test.describe('Timezone-XCDB : Asia/Hong-kong', () => {
// DateTime inserted using API without timezone is converted to UTC // DateTime inserted using API without timezone is converted to UTC
// Display value is converted to Asia/Hong_Kong // Display value is converted to Asia/Hong_Kong
await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); const dateInserted = new Date(`2021-01-01 00:00:00${getBrowserTimezoneOffset()}`);
// convert dateInserted to Asia/Hong-kong timezone using offset
const dateInsertedInHK = new Date(dateInserted.getTime() + 8 * 60 * 60 * 1000)
.toISOString()
.slice(0, 16)
.replace('T', ' ');
await dashboard.grid.cell.verifyDateCell({
index: 0,
columnHeader: 'DateTime',
value: dateInsertedInHK,
});
// DateTime inserted using API with timezone is converted to UTC // DateTime inserted using API with timezone is converted to UTC
// Display value is converted to Asia/Hong_Kong // Display value is converted to Asia/Hong_Kong

Loading…
Cancel
Save