Browse Source

Merge branch 'develop' into refactor/timezone-locale

pull/5601/head
Wing-Kam Wong 2 years ago
parent
commit
88a9802147
  1. 3
      .github/workflows/release-docker.yml
  2. 4
      .github/workflows/release-npm.yml
  3. 1
      .github/workflows/release-pr.yml
  4. 6
      packages/nc-gui/components/cell/Currency.vue
  5. 6
      packages/nc-gui/components/cell/Decimal.vue
  6. 5
      packages/nc-gui/components/cell/Duration.vue
  7. 6
      packages/nc-gui/components/cell/Email.vue
  8. 6
      packages/nc-gui/components/cell/Float.vue
  9. 6
      packages/nc-gui/components/cell/Integer.vue
  10. 8
      packages/nc-gui/components/cell/Percent.vue
  11. 8
      packages/nc-gui/components/cell/Text.vue
  12. 6
      packages/nc-gui/components/cell/TextArea.vue
  13. 5
      packages/nc-gui/components/cell/Url.vue
  14. 9
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  15. 8
      packages/nc-gui/components/webhook/Editor.vue
  16. 1
      packages/nc-gui/context/index.ts
  17. 6
      packages/nocodb-nest/Dockerfile
  18. 100
      packages/nocodb-nest/litestream/Dockerfile
  19. 8
      packages/nocodb-nest/package.json
  20. 49
      packages/nocodb-nest/src/db/BaseModelSqlv2.ts
  21. 7
      packages/nocodb-nest/src/guards/global/global.guard.ts
  22. 3
      packages/nocodb-nest/src/helpers/webhookHelpers.ts
  23. 4
      packages/nocodb-nest/src/meta/migrations/XcMigrationSourcev2.ts
  24. 34
      packages/nocodb-nest/src/meta/migrations/v2/nc_030_add_description_field.ts
  25. 1
      packages/nocodb-nest/src/schema/swagger.json
  26. 3
      packages/nocodb-nest/src/services/hooks.service.ts
  27. 18
      packages/nocodb-nest/src/services/public-datas.service.ts
  28. 33
      packages/nocodb-nest/src/strategies/authtoken.strategy/authtoken.strategy.ts
  29. 4
      packages/nocodb-nest/src/strategies/jwt.strategy.ts
  30. 2
      packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts
  31. 6
      packages/nocodb/src/lib/migrations/v2/nc_030_add_description_field.ts
  32. 15
      scripts/upgradeNcGui.js
  33. 6
      scripts/upgradeNocodbSdk.js
  34. 6
      tests/playwright/setup/index.ts

3
.github/workflows/release-docker.yml

@ -43,7 +43,7 @@ jobs:
buildx: buildx:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
working-directory: ./packages/nocodb working-directory: ./packages/nocodb-nest
steps: steps:
- name: Get Docker Repository - name: Get Docker Repository
id: get-docker-repository id: get-docker-repository
@ -104,7 +104,6 @@ jobs:
- name: Build nocodb and docker files - name: Build nocodb and docker files
run: | run: |
npm run build
npm run docker:build npm run docker:build
working-directory: ${{ env.working-directory }} working-directory: ${{ env.working-directory }}

4
.github/workflows/release-npm.yml

@ -35,7 +35,7 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
working-directory: ./packages/nocodb working-directory: ./packages/nocodb-nest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -62,7 +62,7 @@ jobs:
targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} npm run build:copy:publish && targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} targetVersion=${{ github.event.inputs.tag || inputs.tag }} npm run build:copy:publish &&
cd ../.. && cd ../.. &&
sleep 60 && sleep 60 &&
targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} node scripts/upgradeNcGui.js && cd packages/nocodb && npm install && npm run obfuscate:build:publish targetEnv=${{ github.event.inputs.targetEnv || inputs.targetEnv }} node scripts/upgradeNcGui.js && cd packages/nocodb-nest && npm install && npm run obfuscate:build:publish
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create Pull Request - name: Create Pull Request

1
.github/workflows/release-pr.yml

@ -13,6 +13,7 @@ on:
- "packages/nc-gui/**" - "packages/nc-gui/**"
- "packages/nc-plugin/**" - "packages/nc-plugin/**"
- "packages/nocodb/**" - "packages/nocodb/**"
- "packages/nocodb-nest/**"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}

6
packages/nc-gui/components/cell/Currency.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { ColumnInj, EditModeInj, computed, inject, parseProp, useVModel } from '#imports' import { ColumnInj, EditModeInj, IsExpandedFormOpenInj, computed, inject, parseProp, useVModel } from '#imports'
interface Props { interface Props {
modelValue: number | null | undefined modelValue: number | null | undefined
@ -52,7 +52,9 @@ const currency = computed(() => {
} }
}) })
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
const submitCurrency = () => { const submitCurrency = () => {
if (lastSaved.value !== vModel.value) { if (lastSaved.value !== vModel.value) {

6
packages/nc-gui/components/cell/Decimal.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, inject, useVModel } from '#imports'
interface Props { interface Props {
// when we set a number, then it is number type // when we set a number, then it is number type
@ -36,7 +36,9 @@ const vModel = computed({
}, },
}) })
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

5
packages/nc-gui/components/cell/Duration.vue

@ -3,6 +3,7 @@ import type { VNodeRef } from '@vue/runtime-core'
import { import {
ColumnInj, ColumnInj,
EditModeInj, EditModeInj,
IsExpandedFormOpenInj,
computed, computed,
convertDurationToSeconds, convertDurationToSeconds,
convertMS2Duration, convertMS2Duration,
@ -73,7 +74,9 @@ const submitDuration = () => {
isEdited.value = false isEdited.value = false
} }
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

6
packages/nc-gui/components/cell/Email.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, IsSurveyFormInj, computed, inject, useI18n, validateEmail } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, IsSurveyFormInj, computed, inject, useI18n, validateEmail } from '#imports'
interface Props { interface Props {
modelValue: string | null | undefined modelValue: string | null | undefined
@ -35,7 +35,9 @@ const vModel = computed({
const validEmail = computed(() => vModel.value && validateEmail(vModel.value)) const validEmail = computed(() => vModel.value && validateEmail(vModel.value))
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
watch( watch(
() => editEnabled.value, () => editEnabled.value,

6
packages/nc-gui/components/cell/Float.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, inject, useVModel } from '#imports'
interface Props { interface Props {
// when we set a number, then it is number type // when we set a number, then it is number type
@ -36,7 +36,9 @@ const vModel = computed({
}, },
}) })
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

6
packages/nc-gui/components/cell/Integer.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, inject, useVModel } from '#imports'
interface Props { interface Props {
// when we set a number, then it is number type // when we set a number, then it is number type
@ -36,7 +36,9 @@ const vModel = computed({
}, },
}) })
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
function onKeyDown(evt: KeyboardEvent) { function onKeyDown(evt: KeyboardEvent) {
return evt.key === '.' && evt.preventDefault() return evt.key === '.' && evt.preventDefault()

8
packages/nc-gui/components/cell/Percent.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, inject, useVModel } from '#imports'
interface Props { interface Props {
modelValue?: number | string | null modelValue?: number | string | null
@ -27,9 +27,9 @@ const vModel = computed({
}, },
}) })
const focus: VNodeRef = (el) => { const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
;(el as HTMLInputElement)?.focus()
} const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

8
packages/nc-gui/components/cell/Text.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, ReadonlyInj, inject, ref, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, ReadonlyInj, inject, ref, useVModel } from '#imports'
interface Props { interface Props {
modelValue?: string | null modelValue?: string | null
@ -23,9 +23,9 @@ const readonly = inject(ReadonlyInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
const focus: VNodeRef = (el) => { const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
;(el as HTMLInputElement)?.focus()
} const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

6
packages/nc-gui/components/cell/TextArea.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, RowHeightInj, inject, useVModel } from '#imports' import { EditModeInj, IsExpandedFormOpenInj, RowHeightInj, inject, useVModel } from '#imports'
const props = defineProps<{ const props = defineProps<{
modelValue?: string | number modelValue?: string | number
@ -19,7 +19,9 @@ const { showNull } = useGlobal()
const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' }) const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
const focus: VNodeRef = (el) => (el as HTMLTextAreaElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLTextAreaElement)?.focus()
</script> </script>
<template> <template>

5
packages/nc-gui/components/cell/Url.vue

@ -4,6 +4,7 @@ import {
CellUrlDisableOverlayInj, CellUrlDisableOverlayInj,
ColumnInj, ColumnInj,
EditModeInj, EditModeInj,
IsExpandedFormOpenInj,
IsSurveyFormInj, IsSurveyFormInj,
computed, computed,
inject, inject,
@ -62,7 +63,9 @@ const url = computed(() => {
const { cellUrlOptions } = useCellUrlConfig(url) const { cellUrlOptions } = useCellUrlConfig(url)
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const isExpandedFormOpen = inject(IsExpandedFormOpenInj)!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus()
watch( watch(
() => editEnabled.value, () => editEnabled.value,

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

@ -5,6 +5,7 @@ import type { Ref } from 'vue'
import { import {
CellClickHookInj, CellClickHookInj,
FieldsInj, FieldsInj,
IsExpandedFormOpenInj,
IsFormInj, IsFormInj,
IsKanbanInj, IsKanbanInj,
MetaInj, MetaInj,
@ -180,10 +181,14 @@ if (isKanban.value) {
} }
} }
const cellWrapperEl = ref<HTMLElement>() provide(IsExpandedFormOpenInj, isExpanded)
const cellWrapperEl = ref()
onMounted(() => { onMounted(() => {
setTimeout(() => (cellWrapperEl.value?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()) setTimeout(() => {
cellWrapperEl.value?.$el?.querySelector('input,select,textarea')?.focus()
}, 300)
}) })
const addNewRow = () => { const addNewRow = () => {

8
packages/nc-gui/components/webhook/Editor.vue

@ -66,7 +66,9 @@ const hook = reactive<
version: 'v2', version: 'v2',
}) })
const urlTabKey = ref('params') const isBodyShown = ref(hook.version === 'v1' || (hook.version === 'v2' && appInfo.ee))
const urlTabKey = ref(isBodyShown.value ? 'body' : 'params')
const apps: Record<string, any> = ref() const apps: Record<string, any> = ref()
@ -596,7 +598,7 @@ onMounted(async () => {
<a-col :span="24"> <a-col :span="24">
<a-tabs v-model:activeKey="urlTabKey" type="card" closeable="false" class="shadow-sm"> <a-tabs v-model:activeKey="urlTabKey" type="card" closeable="false" class="shadow-sm">
<a-tab-pane v-if="hook.version === 'v1'" key="body" tab="Body"> <a-tab-pane v-if="isBodyShown" key="body" tab="Body">
<LazyMonacoEditor <LazyMonacoEditor
v-model="hook.notification.payload.body" v-model="hook.notification.payload.body"
disable-deep-compare disable-deep-compare
@ -716,7 +718,7 @@ onMounted(async () => {
<a-row> <a-row>
<a-col :span="24"> <a-col :span="24">
<div v-if="!(hook.version === 'v2' && hook.notification.type === 'URL')" class="text-gray-600"> <div v-if="isBodyShown" class="text-gray-600">
<div class="flex items-center"> <div class="flex items-center">
<em>Use context variable <strong>data</strong> to refer the record under consideration</em> <em>Use context variable <strong>data</strong> to refer the record under consideration</em>

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

@ -19,6 +19,7 @@ export const IsGridInj: InjectionKey<Ref<boolean>> = Symbol('is-grid-injection')
export const IsGalleryInj: InjectionKey<Ref<boolean>> = Symbol('is-gallery-injection') export const IsGalleryInj: InjectionKey<Ref<boolean>> = Symbol('is-gallery-injection')
export const IsKanbanInj: InjectionKey<Ref<boolean>> = Symbol('is-kanban-injection') export const IsKanbanInj: InjectionKey<Ref<boolean>> = Symbol('is-kanban-injection')
export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection') export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection')
export const IsExpandedFormOpenInj: InjectionKey<Ref<boolean>> = Symbol('is-expanded-form-open-injection')
export const CellValueInj: InjectionKey<Ref<any>> = Symbol('cell-value-injection') export const CellValueInj: InjectionKey<Ref<any>> = Symbol('cell-value-injection')
export const ActiveViewInj: InjectionKey<Ref<ViewType>> = Symbol('active-view-injection') export const ActiveViewInj: InjectionKey<Ref<ViewType>> = Symbol('active-view-injection')
export const ReadonlyInj: InjectionKey<Ref<boolean>> = Symbol('readonly-injection') export const ReadonlyInj: InjectionKey<Ref<boolean>> = Symbol('readonly-injection')

6
packages/nocodb-nest/Dockerfile

@ -31,9 +31,9 @@ COPY ./package*.json ./
COPY ./docker/main.js ./docker/main.js COPY ./docker/main.js ./docker/main.js
#COPY ./docker/start.sh /usr/src/appEntry/start.sh #COPY ./docker/start.sh /usr/src/appEntry/start.sh
COPY ./docker/start-litestream.sh /usr/src/appEntry/start.sh COPY ./docker/start-litestream.sh /usr/src/appEntry/start.sh
COPY ./src/lib/public/css/*.css ./docker/public/css/ COPY ./public/css/*.css ./docker/public/css/
COPY ./src/lib/public/js/*.js ./docker/public/js/ COPY ./public/js/*.js ./docker/public/js/
COPY ./src/lib/public/favicon.ico ./docker/public/ COPY ./public/favicon.ico ./docker/public/
# install production dependencies, # install production dependencies,
# reduce node_module size with modclean & removing sqlite deps, # reduce node_module size with modclean & removing sqlite deps,

100
packages/nocodb-nest/litestream/Dockerfile

@ -0,0 +1,100 @@
FROM golang:alpine3.14 as lt
WORKDIR /usr/src/
RUN apk add --no-cache git make musl-dev gcc
# build litestream
RUN git clone https://github.com/benbjohnson/litestream.git litestream
RUN cd litestream ; go install ./cmd/litestream
RUN cp $GOPATH/bin/litestream /usr/src/lt
FROM node:12 as builder
WORKDIR /usr/src/app
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm ci on every code change.
COPY ./package*.json ./
COPY ./docker/main.js ./docker/main.js
#COPY ./docker/start.sh /usr/src/appEntry/start.sh
COPY ./docker/start-litestream.sh /usr/src/appEntry/start.sh
# install production dependencies,
# reduce node_module size with modclean & removing sqlite deps,
# package built code into app.tar.gz & add execute permission to start.sh
RUN npm ci --production --quiet
RUN npx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**" --run
RUN rm -rf ./node_modules/sqlite3/deps
RUN tar -czf ../appEntry/app.tar.gz ./*
RUN chmod +x /usr/src/appEntry/start.sh
FROM alpine:3.14
#ENV AWS_ACCESS_KEY_ID=
#ENV AWS_SECRET_ACCESS_KEY=
#ENV AWS_BUCKET=
#WORKDIR /usr/src/
#
## Install go lang
#RUN apk add --no-cache git make musl-dev go
#
## Configure Go
#ENV GOROOT /usr/lib/go
#ENV GOPATH /go
#ENV PATH /go/bin:$PATH
#
#RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
#
## build litestream
#
#RUN git clone https://github.com/benbjohnson/litestream.git litestream
#RUN cd litestream ; go install ./cmd/litestream
# Bug fix for segfault ( Convert PT_GNU_STACK program header into PT_PAX_FLAGS )
#RUN apk --update --no-cache add paxctl \
# && paxctl -cm $(which node)
WORKDIR /usr/src/app
ENV NC_DOCKER 0.6
ENV PORT 8080
ENV NC_TOOL_DIR=/usr/app/data/
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
#COPY ./build/ ./build/
#COPY ./docker/main.js ./docker/main.js
#COPY ./package.json ./
RUN apk --update --no-cache add \
nodejs \
tar
# Copy litestream binary build
COPY --from=lt /usr/src/lt /usr/src/appEntry/litestream
# Copy packaged production code & main entry file
COPY --from=builder /usr/src/appEntry/ /usr/src/appEntry/
# Run the web service on container startup.
#CMD [ "node", "docker/index.js" ]
ENTRYPOINT ["sh", "/usr/src/appEntry/start.sh"]

8
packages/nocodb-nest/package.json

@ -1,7 +1,7 @@
{ {
"name": "nocodb-nest", "name": "nocodb",
"version": "0.0.1", "version": "0.106.1",
"description": "NocoDB Backend (Nest)", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
"name": "NocoDB Inc", "name": "NocoDB Inc",
@ -15,11 +15,11 @@
"bugs": { "bugs": {
"url": "https://github.com/nocodb/nocodb/issues" "url": "https://github.com/nocodb/nocodb/issues"
}, },
"private": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"build:obfuscate": "EE=true webpack --config webpack.config.js", "build:obfuscate": "EE=true webpack --config webpack.config.js",
"obfuscate:build:publish": "npm run build:obfuscate && npm publish .",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",

49
packages/nocodb-nest/src/db/BaseModelSqlv2.ts

@ -1602,17 +1602,16 @@ class BaseModelSqlv2 {
// hide if column marked as hidden in view // hide if column marked as hidden in view
// of if column is system field and system field is hidden // of if column is system field and system field is hidden
if ( if (
fieldsSet shouldSkipField(
? !fieldsSet.has(column.title) fieldsSet,
: !extractPkAndPv && viewOrTableColumn,
!(viewOrTableColumn instanceof Column) && view,
(!(viewOrTableColumn as GridViewColumn)?.show || column,
(!view?.show_system_fields && extractPkAndPv,
column.uidt !== UITypes.ForeignKey &&
!column.pk &&
isSystemColumn(column)))
) )
) {
continue; continue;
}
if (!checkColumnRequired(column, fields, extractPkAndPv)) continue; if (!checkColumnRequired(column, fields, extractPkAndPv)) continue;
@ -3385,4 +3384,36 @@ function haveFormulaColumn(columns: Column[]) {
return columns.some((c) => c.uidt === UITypes.Formula); return columns.some((c) => c.uidt === UITypes.Formula);
} }
function shouldSkipField(
fieldsSet,
viewOrTableColumn,
view,
column,
extractPkAndPv,
) {
if (fieldsSet) {
return !fieldsSet.has(column.title);
} else {
if (!extractPkAndPv) {
if (!(viewOrTableColumn instanceof Column)) {
if (
!(viewOrTableColumn as GridViewColumn)?.show &&
!(column.rqd && !column.cdf && !column.ai) &&
!column.pk &&
column.uidt !== UITypes.ForeignKey
)
return true;
if (
!view?.show_system_fields &&
column.uidt !== UITypes.ForeignKey &&
!column.pk &&
isSystemColumn(column)
)
return true;
}
}
return false;
}
}
export { BaseModelSqlv2 }; export { BaseModelSqlv2 };

7
packages/nocodb-nest/src/guards/global/global.guard.ts

@ -13,13 +13,16 @@ export class GlobalGuard extends AuthGuard(['jwt']) {
async canActivate(context: ExecutionContext) { async canActivate(context: ExecutionContext) {
let result; let result;
const req = context.switchToHttp().getRequest();
if (req.headers?.['xc-auth']) {
try { try {
result = await this.extractBoolVal(super.canActivate(context)); result = await this.extractBoolVal(super.canActivate(context));
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
}
const req = context.switchToHttp().getRequest();
if (result && !req.headers['xc-shared-base-id']) { if (result && !req.headers['xc-shared-base-id']) {
if ( if (

3
packages/nocodb-nest/src/helpers/webhookHelpers.ts

@ -1,6 +1,7 @@
import Handlebars from 'handlebars'; import Handlebars from 'handlebars';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { Filter, HookLog } from '../models'; import { Filter, HookLog } from '../models';
import Noco from '../Noco';
import NcPluginMgrv2 from './NcPluginMgrv2'; import NcPluginMgrv2 from './NcPluginMgrv2';
import type { Column, FormView, Hook, Model, View } from '../models'; import type { Column, FormView, Hook, Model, View } from '../models';
import type { HookLogType } from 'nocodb-sdk'; import type { HookLogType } from 'nocodb-sdk';
@ -135,7 +136,7 @@ export async function validateCondition(filters: Filter[], data: any) {
} }
export function constructWebHookData(hook, model, view, prevData, newData) { export function constructWebHookData(hook, model, view, prevData, newData) {
if (hook.version === 'v2') { if (hook.version === 'v2' && !Noco.isEE()) {
// extend in the future - currently only support records // extend in the future - currently only support records
const scope = 'records'; const scope = 'records';

4
packages/nocodb-nest/src/meta/migrations/XcMigrationSourcev2.ts

@ -17,6 +17,7 @@ import * as nc_026_map_view from './v2/nc_026_map_view';
import * as nc_027_add_comparison_sub_op from './v2/nc_027_add_comparison_sub_op'; import * as nc_027_add_comparison_sub_op from './v2/nc_027_add_comparison_sub_op';
import * as nc_028_add_enable_scanner_in_form_columns_meta_table from './v2/nc_028_add_enable_scanner_in_form_columns_meta_table'; import * as nc_028_add_enable_scanner_in_form_columns_meta_table from './v2/nc_028_add_enable_scanner_in_form_columns_meta_table';
import * as nc_029_webhook from './v2/nc_029_webhook'; import * as nc_029_webhook from './v2/nc_029_webhook';
import * as nc_030_add_description_field from './v2/nc_030_add_description_field';
// Create a custom migration source class // Create a custom migration source class
export default class XcMigrationSourcev2 { export default class XcMigrationSourcev2 {
@ -45,6 +46,7 @@ export default class XcMigrationSourcev2 {
'nc_027_add_comparison_sub_op', 'nc_027_add_comparison_sub_op',
'nc_028_add_enable_scanner_in_form_columns_meta_table', 'nc_028_add_enable_scanner_in_form_columns_meta_table',
'nc_029_webhook', 'nc_029_webhook',
'nc_030_add_description_field',
]); ]);
} }
@ -92,6 +94,8 @@ export default class XcMigrationSourcev2 {
return nc_028_add_enable_scanner_in_form_columns_meta_table; return nc_028_add_enable_scanner_in_form_columns_meta_table;
case 'nc_029_webhook': case 'nc_029_webhook':
return nc_029_webhook; return nc_029_webhook;
case 'nc_030_add_description_field':
return nc_030_add_description_field;
} }
} }
} }

34
packages/nocodb-nest/src/meta/migrations/v2/nc_030_add_description_field.ts

@ -0,0 +1,34 @@
import type { Knex } from 'knex';
import { MetaTable } from '../../meta.service'
const up = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.string('description', 255);
});
await knex.schema.alterTable(MetaTable.MODELS, (table) => {
table.string('description', 255);
});
await knex.schema.alterTable(MetaTable.VIEWS, (table) => {
table.string('description', 255);
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.string('description', 255);
});
};
const down = async (knex) => {
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.dropColumn('description');
});
await knex.schema.alterTable(MetaTable.MODELS, (table) => {
table.dropColumn('description');
});
await knex.schema.alterTable(MetaTable.VIEWS, (table) => {
table.dropColumn('description');
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.dropColumn('description');
});
};
export { up, down };

1
packages/nocodb-nest/src/schema/swagger.json

@ -17087,7 +17087,6 @@
"description": "The license key", "description": "The license key",
"example": "1234567890", "example": "1234567890",
"maxLength": 255, "maxLength": 255,
"minLength": 10,
"type": "string" "type": "string"
} }
}, },

3
packages/nocodb-nest/src/services/hooks.service.ts

@ -8,6 +8,7 @@ import {
} from '../helpers/populateSamplePayload'; } from '../helpers/populateSamplePayload';
import { invokeWebhook } from '../helpers/webhookHelpers'; import { invokeWebhook } from '../helpers/webhookHelpers';
import { Hook, HookLog, Model } from '../models'; import { Hook, HookLog, Model } from '../models';
import Noco from '../Noco';
import type { HookReqType, HookTestReqType, HookType } from 'nocodb-sdk'; import type { HookReqType, HookTestReqType, HookType } from 'nocodb-sdk';
@Injectable() @Injectable()
@ -107,7 +108,7 @@ export class HooksService {
}) { }) {
const model = await Model.getByIdOrName({ id: param.tableId }); const model = await Model.getByIdOrName({ id: param.tableId });
if (param.version === 'v1') { if (param.version === 'v1' || (param.version === 'v2' && Noco.isEE())) {
return await populateSamplePayload(model, false, param.operation); return await populateSamplePayload(model, false, param.operation);
} }
return await populateSamplePayloadV2(model, false, param.operation); return await populateSamplePayloadV2(model, false, param.operation);

18
packages/nocodb-nest/src/services/public-datas.service.ts

@ -305,7 +305,9 @@ export class PublicDatasService {
if (!view) NcError.notFound('Not found'); if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.FORM) NcError.notFound('Not found'); if (view.type !== ViewTypes.FORM && view.type !== ViewTypes.GALLERY) {
NcError.notFound('Not found');
}
if (view.password && view.password !== param.password) { if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
@ -360,8 +362,13 @@ export class PublicDatasService {
const view = await View.getByUUID(param.sharedViewUuid); const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found'); if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.GRID && view.type !== ViewTypes.KANBAN) if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY
) {
NcError.notFound('Not found'); NcError.notFound('Not found');
}
if (view.password && view.password !== param.password) { if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
@ -426,8 +433,13 @@ export class PublicDatasService {
const view = await View.getByUUID(param.sharedViewUuid); const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found'); if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.GRID && view.type !== ViewTypes.KANBAN) if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY
) {
NcError.notFound('Not found'); NcError.notFound('Not found');
}
if (view.password && view.password !== param.password) { if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);

33
packages/nocodb-nest/src/strategies/authtoken.strategy/authtoken.strategy.ts

@ -2,34 +2,28 @@ import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-custom'; import { Strategy } from 'passport-custom';
import { ApiToken, ProjectUser, User } from '../../models'; import { ApiToken, ProjectUser, User } from '../../models';
import type { Request } from 'express';
@Injectable() @Injectable()
export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') { export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
constructor() {
super({
headerFields: ['xc-token'],
passReqToCallback: true,
});
}
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
async validate(req: Request, token: string, done: Function) { async validate(req: any, callback: Function) {
try { try {
const apiToken = await ApiToken.getByToken(token); let user;
if (req.headers['xc-token']) {
const apiToken = await ApiToken.getByToken(req.headers['xc-token']);
if (!apiToken) { if (!apiToken) {
return done({ msg: 'Invalid token' }); return callback({ msg: 'Invalid token' });
} }
const user: any = {}; user = {};
if (!apiToken.fk_user_id) { if (!apiToken.fk_user_id) {
user.roles = 'editor'; user.roles = 'editor';
return done(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.get(apiToken.fk_user_id);
if (!dbUser) { if (!dbUser) {
return done({ msg: 'User not found' }); return callback({ msg: 'User not found' });
} }
dbUser.is_api_token = true; dbUser.is_api_token = true;
@ -40,15 +34,12 @@ export class AuthTokenStrategy extends PassportStrategy(Strategy, 'authtoken') {
); );
user.roles = projectUser?.roles || dbUser.roles; user.roles = projectUser?.roles || dbUser.roles;
user.roles = user.roles === 'owner' ? 'owner,creator' : user.roles; user.roles = user.roles === 'owner' ? 'owner,creator' : user.roles;
// + (user.roles ? `,${user.roles}` : ''); return callback(null, user);
// todo : cache
// await NocoCache.set(`${CacheScope.USER}:${key}`, user);
return done(null, user);
} }
}
return done(null, dbUser); return callback(null, user);
} catch (error) { } catch (error) {
return done(error); return callback(error);
} }
} }
} }

4
packages/nocodb-nest/src/strategies/jwt.strategy.ts

@ -1,6 +1,6 @@
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { Strategy } from 'passport-jwt';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import { ProjectUser, User } from '../models'; import { ProjectUser, User } from '../models';

2
packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts

@ -46,7 +46,7 @@ export default class XcMigrationSourcev2 {
'nc_027_add_comparison_sub_op', 'nc_027_add_comparison_sub_op',
'nc_028_add_enable_scanner_in_form_columns_meta_table', 'nc_028_add_enable_scanner_in_form_columns_meta_table',
'nc_029_webhook', 'nc_029_webhook',
'nc_030_add_description_field' 'nc_030_add_description_field',
]); ]);
} }

6
packages/nocodb/src/lib/migrations/v2/nc_030_add_description_field.ts

@ -2,9 +2,6 @@ import { MetaTable } from '../../utils/globals';
import type { Knex } from 'knex'; import type { Knex } from 'knex';
const up = async (knex: Knex) => { const up = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.PROJECT, (table) => {
table.string('description', 255);
});
await knex.schema.alterTable(MetaTable.BASES, (table) => { await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.string('description', 255); table.string('description', 255);
}); });
@ -20,9 +17,6 @@ const up = async (knex: Knex) => {
}; };
const down = async (knex) => { const down = async (knex) => {
await knex.schema.alterTable(MetaTable.PROJECT, (table) => {
table.dropColumn('description');
});
await knex.schema.alterTable(MetaTable.BASES, (table) => { await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.dropColumn('description'); table.dropColumn('description');
}); });

15
scripts/upgradeNcGui.js

@ -22,8 +22,8 @@ const replacePackageName = (filePath) => {
const bumbVersionAndSave = () => { const bumbVersionAndSave = () => {
// upgrade nc-lib-gui version in nocodb // upgrade nc-lib-gui version in nocodb
execSync(`cd packages/nocodb && npm install --save --save-exact ${ncLibPackage.name}@${ncLibPackage.version}`, {}); execSync(`cd packages/nocodb-nest && npm install --save --save-exact ${ncLibPackage.name}@${ncLibPackage.version}`, {});
const nocodbPackageFilePath = path.join(__dirname, '..', 'packages', 'nocodb', 'package.json') const nocodbPackageFilePath = path.join(__dirname, '..', 'packages', 'nocodb-nest', 'package.json')
const nocoLibPackage = JSON.parse(fs.readFileSync(nocodbPackageFilePath)) const nocoLibPackage = JSON.parse(fs.readFileSync(nocodbPackageFilePath))
if (process.env.targetEnv === 'DEV') { if (process.env.targetEnv === 'DEV') {
nocoLibPackage.name = `${nocoLibPackage.name}-daily` nocoLibPackage.name = `${nocoLibPackage.name}-daily`
@ -35,11 +35,12 @@ const bumbVersionAndSave = () => {
if (process.env.targetEnv === 'DEV') { if (process.env.targetEnv === 'DEV') {
// replace nc-lib-gui by nc-lib-gui-daily if it is nightly build / pr release // replace nc-lib-gui by nc-lib-gui-daily if it is nightly build / pr release
const filePaths = [ const filePaths = [
path.join(__dirname, '..', 'packages', 'nocodb', 'Dockerfile'), path.join(__dirname, '..', 'packages', 'nocodb-nest', 'Dockerfile'),
path.join(__dirname, '..', 'packages', 'nocodb', 'litestream', 'Dockerfile'), path.join(__dirname, '..', 'packages', 'nocodb-nest', 'litestream', 'Dockerfile'),
path.join(__dirname, '..', 'packages', 'nocodb', 'package.json'), path.join(__dirname, '..', 'packages', 'nocodb-nest', 'package.json'),
path.join(__dirname, '..', 'packages', 'nocodb', 'README.md'), path.join(__dirname, '..', 'packages', 'nocodb-nest', 'src', 'Noco.ts'),
path.join(__dirname, '..', 'packages', 'nocodb', 'src', 'lib', 'Noco.ts'), path.join(__dirname, '..', 'packages', 'nocodb-nest', 'src', 'nocobuild.ts'),
path.join(__dirname, '..', 'packages', 'nocodb-nest', 'src', 'middlewares', 'gui', 'gui.middleware.ts'),
] ]
Promise.all(filePaths.map(filePath => { return replacePackageName(filePath) })).then(() => { Promise.all(filePaths.map(filePath => { return replacePackageName(filePath) })).then(() => {
bumbVersionAndSave(); bumbVersionAndSave();

6
scripts/upgradeNocodbSdk.js

@ -20,7 +20,7 @@ const replacePackageName = (filePath) => {
const bumbVersionAndSave = () => { const bumbVersionAndSave = () => {
// upgrade nocodb-sdk version in nocodb // upgrade nocodb-sdk version in nocodb
execSync(`cd packages/nocodb && npm install --save --save-exact ${nocodbSdkPackage.name}@${nocodbSdkPackage.version}`, {}); execSync(`cd packages/nocodb-nest && npm install --save --save-exact ${nocodbSdkPackage.name}@${nocodbSdkPackage.version}`, {});
// upgrade nocodb-sdk version in nc-gui // upgrade nocodb-sdk version in nc-gui
execSync(`cd packages/nc-gui && npm install --save --save-exact ${nocodbSdkPackage.name}@${nocodbSdkPackage.version}`, {}); execSync(`cd packages/nc-gui && npm install --save --save-exact ${nocodbSdkPackage.name}@${nocodbSdkPackage.version}`, {});
} }
@ -47,9 +47,9 @@ const dfs = function(dir) {
const searchAndReplace = (target) => { const searchAndReplace = (target) => {
let list = [ let list = [
...dfs(path.resolve(path.join(__dirname, '..', 'packages', 'nc-gui'))), ...dfs(path.resolve(path.join(__dirname, '..', 'packages', 'nc-gui'))),
...dfs(path.resolve(path.join(__dirname, '..', 'packages', 'nocodb'))), ...dfs(path.resolve(path.join(__dirname, '..', 'packages', 'nocodb-nest'))),
path.join(__dirname, '..', 'packages', 'nc-gui', 'package.json'), path.join(__dirname, '..', 'packages', 'nc-gui', 'package.json'),
path.join(__dirname, '..', 'packages', 'nocodb', 'package.json') path.join(__dirname, '..', 'packages', 'nocodb-nest', 'package.json')
] ]
return Promise.all(list.map(d => { return Promise.all(list.map(d => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

6
tests/playwright/setup/index.ts

@ -48,6 +48,12 @@ const setup = async ({ page, isEmptyProject }: { page: Page; isEmptyProject?: bo
} }
const token = response.data.token; const token = response.data.token;
try {
await axios.post(`http://localhost:8080/api/v1/license`, { key: '' }, { headers: { 'xc-auth': token } });
} catch (e) {
console.error(`Error resetting project: ${process.env.TEST_PARALLEL_INDEX}`, e);
}
await page.addInitScript( await page.addInitScript(
async ({ token }) => { async ({ token }) => {
try { try {

Loading…
Cancel
Save