diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 02d321f178..25793d026f 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -49,7 +49,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
@@ -94,7 +93,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
@@ -139,7 +137,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
@@ -184,7 +181,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
@@ -229,7 +225,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -274,7 +269,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -319,7 +313,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -364,7 +357,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -409,7 +401,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:xcdb-api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -454,7 +445,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:xcdb-api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -499,7 +489,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:xcdb-api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
@@ -544,7 +533,6 @@ jobs:
uses: cypress-io/github-action@v2
with:
start: |
- npm run build:common
npm run start:xcdb-api:cache
npm run start:web
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
diff --git a/package.json b/package.json
index 579c3db173..602192d780 100644
--- a/package.json
+++ b/package.json
@@ -15,12 +15,12 @@
"scripts": {
"build:common": "cd ./packages/nocodb-sdk; npm install; npm run build",
"install:common": "cd ./packages/nocodb; npm install ../nocodb-sdk; cd ../nc-gui; npm install ../nocodb-sdk",
- "start:api": "cd ./packages/nocodb; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress",
- "start:xcdb-api": "cd ./packages/nocodb; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
- "start:api:cache": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress",
- "start:api:cache:pg": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg",
- "start:xcdb-api:cache": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
- "start:web": "cd ./packages/nc-gui; npm install; npm run dev",
+ "start:api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress",
+ "start:xcdb-api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
+ "start:api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_DISABLE_TELE=true npm run watch:run:cypress",
+ "start:api:cache:pg": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg",
+ "start:xcdb-api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
+ "start:web": "npm run build:common ; cd ./packages/nc-gui; npm install ../nocodb-sdk; npm install; npm run dev",
"cypress:run": "cypress run --config-file ./scripts/cypress/cypress.json",
"cypress:open": "cypress open --config-file ./scripts/cypress/cypress.json",
"cypress:clear": "cypress cache clear",
diff --git a/packages/nc-gui/components/ProjectTabs.vue b/packages/nc-gui/components/ProjectTabs.vue
index a3fd6392ec..0a69cd3360 100644
--- a/packages/nc-gui/components/ProjectTabs.vue
+++ b/packages/nc-gui/components/ProjectTabs.vue
@@ -315,6 +315,21 @@
+
+
+
+ mdi-code-json
+
+
+
+ JSON file
+
+
+
+
+
+
@@ -404,9 +426,11 @@ import GlobalAcl from '~/components/GlobalAcl'
import AuditTab from '~/components/project/AuditTab'
import QuickImport from '~/components/import/QuickImport'
import ImportFromAirtable from '~/components/import/ImportFromAirtable'
+import JsonImport from '~/components/import/JSONImport'
export default {
components: {
+ JsonImport,
ImportFromAirtable,
SwaggerClient,
// Screensaver,
@@ -447,7 +471,8 @@ export default {
showScreensaver: false,
quickImportModal: false,
quickImportType: '',
- airtableImportModal: false
+ airtableImportModal: false,
+ jsonImportModal: false
}
},
methods: {
diff --git a/packages/nc-gui/components/import/ImportFromAirtable.vue b/packages/nc-gui/components/import/ImportFromAirtable.vue
index d22031d35f..a3264182a3 100644
--- a/packages/nc-gui/components/import/ImportFromAirtable.vue
+++ b/packages/nc-gui/components/import/ImportFromAirtable.vue
@@ -8,7 +8,6 @@
🚀
@@ -86,6 +85,13 @@
hide-details
dense
/>
+
+
+
+
+
+
+
+ mdi-file-upload-outline
+
+ Upload
+
+
+
+
+
+ mdi-link-variant
+
+ String
+
+
+
+
+
+
+ mdi-file-plus-outline
+
+
+
+ {{ $t('msg.info.upload') }}
+
+
+
+ {{ $t('msg.info.upload_sub') }}
+
+
+
+
+ {{ $t('msg.info.excelSupport') }}
+
+
+
+
+
+
+
+
+
+
+
+ Format
+
+
+
+
+
+
+
+ {{ $t('general.load') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ showMore ? $t('general.hideAll') : $t('general.showMore') }}
+ mdi-menu-{{ showMore ? 'up' : 'down' }}
+
+
+
+
+
+
+
+
+
+ Flatten nested
+
+
+
+ mdi-information-outline
+
+
+
+ If flatten nested option is set it will flatten nested object as root level property. In normal case nested object will treat as JSON column.
+
+
+ For example the following input: {
+ "prop1": {
+ "prop2": "value"
+ },
+ "prop3": "value",
+ "prop4": 1
+ }
will treat as:
+ {
+ "prop1_prop2": "value",
+ "prop3": "value",
+ "prop4": 1
+ }
+
+
+
+
+
+
+ Import data
+
+
+
+
+
+
+
+
+
+
+
+
+ mdi-file-excel-outline
+
+
+ {{ $t('activity.import') }}
+
+
+ Create template from JSON
+
+
+
+
+
+
+
+
+ JSON Import
+
+
+
+
+
+
+
+ {{ $t('activity.importExcel') }}
+
+
+
+ {{ $t('activity.importCSV') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js
index 9a3be0d4de..c608a79f85 100644
--- a/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js
+++ b/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js
@@ -1,6 +1,5 @@
import Papaparse from 'papaparse'
import TemplateGenerator from '~/components/import/templateParsers/TemplateGenerator'
-
export default class CSVTemplateAdapter extends TemplateGenerator {
constructor(name, data) {
super()
diff --git a/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js
new file mode 100644
index 0000000000..b53230b789
--- /dev/null
+++ b/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js
@@ -0,0 +1,150 @@
+import { TemplateGenerator, UITypes } from 'nocodb-sdk'
+import {
+ extractMultiOrSingleSelectProps,
+ getCheckboxValue,
+ isCheckboxType, isDecimalType, isEmailType,
+ isMultiLineTextType, isUrlType
+} from '~/components/import/templateParsers/parserHelpers'
+
+const jsonTypeToUidt = {
+ number: UITypes.Number,
+ string: UITypes.SingleLineText,
+ date: UITypes.DateTime,
+ boolean: UITypes.Checkbox,
+ object: UITypes.JSON
+}
+
+const extractNestedData = (obj, path) => path.reduce((val, key) => val && val[key], obj)
+
+export default class JSONTemplateAdapter extends TemplateGenerator {
+ constructor(name = 'test', data, parserConfig = {}) {
+ super()
+ this.config = {
+ maxRowsToParse: 500,
+ ...parserConfig
+ }
+ this.name = name
+ this._jsonData = typeof data === 'string' ? JSON.parse(data) : data
+ this.project = {
+ title: this.name,
+ tables: []
+ }
+ this.data = {}
+ }
+
+ async init() {
+ }
+
+ parseData() {
+ this.columns = this.csv.meta.fields
+ this.data = this.csv.data
+ }
+
+ getColumns() {
+ return this.columns
+ }
+
+ getData() {
+ return this.data
+ }
+
+ get jsonData() {
+ return Array.isArray(this._jsonData) ? this._jsonData : [this._jsonData]
+ }
+
+ parse() {
+ const jsonData = this.jsonData
+ const tn = 'table'
+ const table = { table_name: tn, ref_table_name: tn, columns: [] }
+
+ this.data[tn] = []
+
+ for (const col of Object.keys(jsonData[0])) {
+ const columns = this._parseColumn([col], jsonData)
+ table.columns.push(...columns)
+ }
+
+ if (this.config.importData) { this._parseTableData(table) }
+
+ this.project.tables.push(table)
+ }
+
+ getTemplate() {
+ return this.project
+ }
+
+ _parseColumn(path = [], jsonData = this.jsonData, firstRowVal = path.reduce((val, k) => val && val[k], this.jsonData[0])) {
+ const columns = []
+ // parse nested
+ if (firstRowVal && typeof firstRowVal === 'object' && !Array.isArray(firstRowVal) && this.config.normalizeNested) {
+ for (const key of Object.keys(firstRowVal)) {
+ const normalizedNestedColumns = this._parseColumn([...path, key], this.jsonData, firstRowVal[key])
+ columns.push(...normalizedNestedColumns)
+ }
+ } else {
+ const cn = path.join('_').replace(/\W/g, '_').trim()
+
+ const column = {
+ column_name: cn,
+ ref_column_name: cn,
+ path
+ }
+
+ column.uidt = jsonTypeToUidt[typeof firstRowVal] || UITypes.SingleLineText
+
+ const colData = jsonData.map(r => extractNestedData(r, path))
+ Object.assign(column, this._getColumnUIDTAndMetas(colData, column.uidt))
+ columns.push(column)
+ }
+
+ return columns
+ }
+
+ _getColumnUIDTAndMetas(colData, defaultType) {
+ const colProps = { uidt: defaultType }
+ // todo: optimize
+ if (colProps.uidt === UITypes.SingleLineText) {
+ // check for long text
+ if (isMultiLineTextType(colData)) {
+ colProps.uidt = UITypes.LongText
+ } if (isEmailType(colData)) {
+ colProps.uidt = UITypes.Email
+ } if (isUrlType(colData)) {
+ colProps.uidt = UITypes.URL
+ } else {
+ const checkboxType = isCheckboxType(colData)
+ if (checkboxType.length === 1) {
+ colProps.uidt = UITypes.Checkbox
+ } else {
+ Object.assign(colProps, extractMultiOrSingleSelectProps(colData))
+ }
+ }
+ } else if (colProps.uidt === UITypes.Number) {
+ if (isDecimalType(colData)) {
+ colProps.uidt = UITypes.Decimal
+ }
+ }
+ return colProps
+ }
+
+ _parseTableData(tableMeta) {
+ for (const row of this.jsonData) {
+ const rowData = {}
+ for (let i = 0; i < tableMeta.columns.length; i++) {
+ const value = extractNestedData(row, tableMeta.columns[i].path || [])
+ if (tableMeta.columns[i].uidt === UITypes.Checkbox) {
+ rowData[tableMeta.columns[i].ref_column_name] = getCheckboxValue(value)
+ } else if (tableMeta.columns[i].uidt === UITypes.SingleSelect || tableMeta.columns[i].uidt === UITypes.MultiSelect) {
+ rowData[tableMeta.columns[i].ref_column_name] = (value || '').toString().trim() || null
+ } else if (tableMeta.columns[i].uidt === UITypes.JSON) {
+ rowData[tableMeta.columns[i].ref_column_name] = JSON.stringify(value)
+ } else {
+ // toto: do parsing if necessary based on type
+ rowData[tableMeta.columns[i].column_name] = value
+ }
+ }
+ this.data[tableMeta.ref_table_name].push(rowData)
+ // rowIndex++
+ }
+ }
+}
diff --git a/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js
new file mode 100644
index 0000000000..34494e00fc
--- /dev/null
+++ b/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js
@@ -0,0 +1,21 @@
+import JSONTemplateAdapter from '~/components/import/templateParsers/JSONTemplateAdapter'
+
+export default class JSONUrlTemplateAdapter extends JSONTemplateAdapter {
+ constructor(url, $store, parserConfig, $api) {
+ const name = url.split('/').pop()
+ super(name, null, parserConfig)
+ this.url = url
+ this.$api = $api
+ this.$store = $store
+ }
+
+ async init() {
+ const data = await this.$api.utils.axiosRequestMake({
+ apiMeta: {
+ url: this.url
+ }
+ })
+ this._jsonData = data
+ await super.init()
+ }
+}
diff --git a/packages/nc-gui/components/import/templateParsers/parserHelpers.js b/packages/nc-gui/components/import/templateParsers/parserHelpers.js
index 75fd80ead7..43156d3bea 100644
--- a/packages/nc-gui/components/import/templateParsers/parserHelpers.js
+++ b/packages/nc-gui/components/import/templateParsers/parserHelpers.js
@@ -1,3 +1,6 @@
+import { UITypes } from 'nocodb-sdk'
+import { isEmail, isValidURL } from '~/helpers'
+
const booleanOptions = [
{ checked: true, unchecked: false },
{ x: true, '': false },
@@ -11,14 +14,24 @@ const booleanOptions = [
{ '✔': true, '': false },
{ enabled: true, disabled: false },
{ on: true, off: false },
- { done: true, '': false }
+ { done: true, '': false },
+ { true: true, false: false }
]
const aggBooleanOptions = booleanOptions.reduce((obj, o) => ({ ...obj, ...o }), {})
-export const isCheckboxType = (values, col = '') => {
+
+const getColVal = (row, col = null) => {
+ return row && col ? row[col] : row
+}
+
+export const isCheckboxType = (values, col = null) => {
let options = booleanOptions
for (let i = 0; i < values.length; i++) {
- let val = col ? values[i][col] : values[i]
- val = val === null || val === undefined ? '' : val
+ const val = getColVal(values[i], col)
+
+ if (val === null || val === undefined || val.toString().trim() === '') {
+ continue
+ }
+
options = options.filter(v => val in v)
if (!options.length) {
return false
@@ -29,3 +42,40 @@ export const isCheckboxType = (values, col = '') => {
export const getCheckboxValue = (value) => {
return value && aggBooleanOptions[value]
}
+
+export const isMultiLineTextType = (values, col = null) => {
+ return values.some(r =>
+ (getColVal(r, col) || '').toString().match(/[\r\n]/) ||
+ (getColVal(r, col) || '').toString().length > 255)
+}
+
+export const extractMultiOrSingleSelectProps = (colData) => {
+ const colProps = {}
+ if (colData.some(v => v && (v || '').toString().includes(','))) {
+ let flattenedVals = colData.flatMap(v => v ? v.toString().trim().split(/\s*,\s*/) : [])
+ const uniqueVals = flattenedVals = flattenedVals
+ .filter((v, i, arr) => i === arr.findIndex(v1 => v.toLowerCase() === v1.toLowerCase()))
+ if (flattenedVals.length > uniqueVals.length && uniqueVals.length <= Math.ceil(flattenedVals.length / 2)) {
+ colProps.uidt = UITypes.MultiSelect
+ colProps.dtxp = `'${uniqueVals.join("','")}'`
+ }
+ } else {
+ const uniqueVals = colData.map(v => (v || '').toString().trim()).filter((v, i, arr) => i === arr.findIndex(v1 => v.toLowerCase() === v1.toLowerCase()))
+ if (colData.length > uniqueVals.length && uniqueVals.length <= Math.ceil(colData.length / 2)) {
+ colProps.uidt = UITypes.SingleSelect
+ colProps.dtxp = `'${uniqueVals.join("','")}'`
+ }
+ }
+ return colProps
+}
+
+export const isDecimalType = colData => colData.some((v) => {
+ return v && parseInt(+v) !== +v
+})
+
+export const isEmailType = colData => !colData.some((v) => {
+ return v && !isEmail(v)
+})
+export const isUrlType = colData => !colData.some((v) => {
+ return v && !isValidURL(v)
+})
diff --git a/packages/nc-gui/components/monaco/MonacoJsonEditor.js b/packages/nc-gui/components/monaco/MonacoJsonEditor.js
index 1362ac4f1c..aa05a99738 100644
--- a/packages/nc-gui/components/monaco/MonacoJsonEditor.js
+++ b/packages/nc-gui/components/monaco/MonacoJsonEditor.js
@@ -83,6 +83,9 @@ export default {
},
methods: {
+ format() {
+ this.editor.getAction('editor.action.formatDocument').run()
+ },
resizeLayout() {
this.editor.layout();
},
diff --git a/packages/nc-plugin/package-lock.json b/packages/nc-plugin/package-lock.json
index dfecb2107a..2fba7f3aba 100644
--- a/packages/nc-plugin/package-lock.json
+++ b/packages/nc-plugin/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "nc-plugin",
- "version": "0.1.1",
+ "version": "0.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nc-plugin",
- "version": "0.1.1",
+ "version": "0.1.3",
"license": "MIT",
"dependencies": {
"@bitauth/libauth": "^1.17.1",
@@ -10298,9 +10298,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"node_modules/shelljs": {
@@ -19881,9 +19881,9 @@
"dev": true
},
"shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"shelljs": {
diff --git a/packages/noco-blog/package-lock.json b/packages/noco-blog/package-lock.json
index 05a95ff26b..ef5c75c2ed 100644
--- a/packages/noco-blog/package-lock.json
+++ b/packages/noco-blog/package-lock.json
@@ -5836,16 +5836,16 @@
}
},
"got": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
- "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "version": "11.8.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
- "cacheable-request": "^7.0.1",
+ "cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -10797,9 +10797,9 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw=="
},
"side-channel": {
"version": "1.0.4",
diff --git a/packages/noco-docs-prev/package-lock.json b/packages/noco-docs-prev/package-lock.json
index 0b4965758b..72f1beb650 100644
--- a/packages/noco-docs-prev/package-lock.json
+++ b/packages/noco-docs-prev/package-lock.json
@@ -7390,16 +7390,16 @@
}
},
"node_modules/got": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
- "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "version": "11.8.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
- "cacheable-request": "^7.0.1",
+ "cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -13804,9 +13804,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw=="
},
"node_modules/side-channel": {
"version": "1.0.4",
@@ -22650,16 +22650,16 @@
}
},
"got": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
- "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "version": "11.8.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
- "cacheable-request": "^7.0.1",
+ "cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -27611,9 +27611,9 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw=="
},
"side-channel": {
"version": "1.0.4",
diff --git a/packages/noco-docs/content/en/developer-resources/rest-apis.md b/packages/noco-docs/content/en/developer-resources/rest-apis.md
index 22cd7fe419..72bdeb470c 100644
--- a/packages/noco-docs/content/en/developer-resources/rest-apis.md
+++ b/packages/noco-docs/content/en/developer-resources/rest-apis.md
@@ -166,7 +166,8 @@ Currently, the default value for {orgs} is noco . Users will be able to ch
| Meta | Delete| utils | cacheDelete | /api/v1/db/meta/cache |
| Meta | Post | utils | testConnection | /api/v1/db/meta/projects/connection/test |
| Meta | Get | utils | appInfo | /api/v1/db/meta/nocodb/info |
-| Meta | Get | utils | appVersion | /api/v1/db/meta/nocodb/version |
+| Meta | Get | utils | appVersion | /api/v1/version |
+| Meta | Get | utils | appHealth | /api/v1/health |
## Query params
diff --git a/packages/noco-docs/package-lock.json b/packages/noco-docs/package-lock.json
index 6647d34ac9..b30ce6478d 100644
--- a/packages/noco-docs/package-lock.json
+++ b/packages/noco-docs/package-lock.json
@@ -7390,16 +7390,16 @@
}
},
"node_modules/got": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
- "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "version": "11.8.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
- "cacheable-request": "^7.0.1",
+ "cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -13804,9 +13804,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw=="
},
"node_modules/side-channel": {
"version": "1.0.4",
@@ -22650,16 +22650,16 @@
}
},
"got": {
- "version": "11.8.2",
- "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
- "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
+ "version": "11.8.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz",
+ "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
- "cacheable-request": "^7.0.1",
+ "cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -27611,9 +27611,9 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw=="
},
"side-channel": {
"version": "1.0.4",
diff --git a/packages/noco-i18n/package-lock.json b/packages/noco-i18n/package-lock.json
index 0534c88b81..db60a0be16 100644
--- a/packages/noco-i18n/package-lock.json
+++ b/packages/noco-i18n/package-lock.json
@@ -9793,9 +9793,9 @@
"dev": true
},
"shell-quote": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
- "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+ "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"side-channel": {
diff --git a/packages/nocodb-sdk/src/index.ts b/packages/nocodb-sdk/src/index.ts
index 805152d812..561b841c36 100644
--- a/packages/nocodb-sdk/src/index.ts
+++ b/packages/nocodb-sdk/src/index.ts
@@ -5,6 +5,7 @@ export * from './lib/sqlUi';
export * from './lib/globals';
export * from './lib/helperFunctions';
export * from './lib/formulaHelpers';
-export * from './lib/passwordHelpers';
export { default as UITypes, isVirtualCol } from './lib/UITypes';
export { default as CustomAPI } from './lib/CustomAPI';
+export { default as TemplateGenerator } from './lib/TemplateGenerator';
+export * from './lib/passwordHelpers';
diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts
index b9ab9d3e20..950123a8ad 100644
--- a/packages/nocodb-sdk/src/lib/Api.ts
+++ b/packages/nocodb-sdk/src/lib/Api.ts
@@ -3197,12 +3197,28 @@ export class Api<
*
* @tags Utils
* @name AppVersion
- * @request GET:/api/v1/db/meta/nocodb/version
+ * @request GET:/api/v1/version
* @response `200` `any` OK
*/
appVersion: (params: RequestParams = {}) =>
this.request({
- path: `/api/v1/db/meta/nocodb/version`,
+ path: `/api/v1/version`,
+ method: 'GET',
+ format: 'json',
+ ...params,
+ }),
+
+ /**
+ * No description
+ *
+ * @tags Utils
+ * @name AppHealth
+ * @request GET:/api/v1/health
+ * @response `200` `any` OK
+ */
+ appHealth: (params: RequestParams = {}) =>
+ this.request({
+ path: `/api/v1/health`,
method: 'GET',
format: 'json',
...params,
diff --git a/packages/nocodb-sdk/src/lib/TemplateGenerator.ts b/packages/nocodb-sdk/src/lib/TemplateGenerator.ts
new file mode 100644
index 0000000000..ad74741b65
--- /dev/null
+++ b/packages/nocodb-sdk/src/lib/TemplateGenerator.ts
@@ -0,0 +1,30 @@
+import UITypes from './UITypes';
+
+export interface Column {
+ column_name: string;
+ ref_column_name: string;
+ uidt?: UITypes;
+ dtxp?: any;
+ dt?: any;
+}
+export interface Table {
+ table_name: string;
+ ref_table_name: string;
+ columns: Array;
+}
+export interface Template {
+ title: string;
+ tables: Array;
+}
+
+export default abstract class TemplateGenerator {
+ abstract parse(): Promise;
+ abstract parseTemplate(): Promise;
+ abstract getColumns(): Promise;
+ abstract parseData(): Promise;
+ abstract getData(): Promise<{
+ [table_name: string]: Array<{
+ [key: string]: any;
+ }>;
+ }>;
+}
diff --git a/packages/nocodb/src/__tests__/restv2.test.ts b/packages/nocodb/src/__tests__/restv2.test.ts
index 8030f98883..ffb6b97794 100644
--- a/packages/nocodb/src/__tests__/restv2.test.ts
+++ b/packages/nocodb/src/__tests__/restv2.test.ts
@@ -209,7 +209,7 @@ describe('Noco v2 Tests', () => {
type: UITypes.Rollup,
alias: 'filmCount',
rollupColumn: 'FilmId',
- relationColumn: 'FilmMMList',
+ relationColumn: 'Film List',
rollupFunction: 'count'
}
];
@@ -413,7 +413,7 @@ describe('Noco v2 Tests', () => {
type: UITypes.Lookup,
alias: 'filmNames',
lookupColumn: 'Title',
- relationColumn: 'FilmMMList'
+ relationColumn: 'Film List'
};
request(app)
.post(`/nc/${projectId}/generate`)
@@ -1335,7 +1335,7 @@ describe('Noco v2 Tests', () => {
type: UITypes.Lookup,
alias: 'filmIds',
lookupColumn: 'FilmId',
- relationColumn: 'FilmMMList'
+ relationColumn: 'Film List'
},
{
table: 'actor',
@@ -1398,7 +1398,7 @@ describe('Noco v2 Tests', () => {
type: UITypes.Rollup,
alias: 'actorsCount',
rollupColumn: 'ActorId',
- relationColumn: 'ActorMMList',
+ relationColumn: 'ActorList',
rollupFunction: 'count'
},
{
@@ -1406,7 +1406,7 @@ describe('Noco v2 Tests', () => {
type: UITypes.Lookup,
alias: 'actorsCountList',
lookupColumn: 'actorsCount',
- relationColumn: 'FilmMMList'
+ relationColumn: 'Film List'
},
{
table: 'actor',
diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts
index e68aff846a..ea3aa7c17b 100644
--- a/packages/nocodb/src/lib/meta/api/columnApis.ts
+++ b/packages/nocodb/src/lib/meta/api/columnApis.ts
@@ -57,7 +57,7 @@ async function createHmAndBtColumn(
{
const title = getUniqueColumnAliasName(
await child.getColumns(),
- type === 'bt' ? alias : `${parent.title}Read`
+ type === 'bt' ? alias : `${parent.title}`
);
await Column.insert({
title,
@@ -79,7 +79,7 @@ async function createHmAndBtColumn(
{
const title = getUniqueColumnAliasName(
await parent.getColumns(),
- type === 'hm' ? alias : `${child.title}List`
+ type === 'hm' ? alias : `${child.title} List`
);
await Column.insert({
title,
@@ -427,7 +427,7 @@ export async function columnAdd(req: Request, res: Response) {
await Column.insert({
title: getUniqueColumnAliasName(
await child.getColumns(),
- `${child.title}MMList`
+ `${parent.title} List`
),
uidt: UITypes.LinkToAnotherRecord,
type: 'mm',
@@ -447,7 +447,7 @@ export async function columnAdd(req: Request, res: Response) {
await Column.insert({
title: getUniqueColumnAliasName(
await parent.getColumns(),
- req.body.title ?? `${parent.title}MMList`
+ req.body.title ?? `${child.title} List`
),
uidt: UITypes.LinkToAnotherRecord,
diff --git a/packages/nocodb/src/lib/meta/api/metaDiffApis.ts b/packages/nocodb/src/lib/meta/api/metaDiffApis.ts
index 9df02372a6..cdcce50557 100644
--- a/packages/nocodb/src/lib/meta/api/metaDiffApis.ts
+++ b/packages/nocodb/src/lib/meta/api/metaDiffApis.ts
@@ -671,7 +671,7 @@ export async function metaDiffSync(req, res) {
if (change.relationType === RelationTypes.BELONGS_TO) {
const title = getUniqueColumnAliasName(
childModel.columns,
- `${parentModel.title || parentModel.table_name}Read`
+ `${parentModel.title || parentModel.table_name}`
);
await Column.insert({
uidt: UITypes.LinkToAnotherRecord,
@@ -686,7 +686,7 @@ export async function metaDiffSync(req, res) {
} else if (change.relationType === RelationTypes.HAS_MANY) {
const title = getUniqueColumnAliasName(
childModel.columns,
- `${childModel.title || childModel.table_name}List`
+ `${childModel.title || childModel.table_name} List`
);
await Column.insert({
uidt: UITypes.LinkToAnotherRecord,
@@ -785,7 +785,7 @@ export async function extractAndGenerateManyToManyRelations(
await Column.insert({
title: getUniqueColumnAliasName(
modelA.columns,
- `${modelB.title}MMList`
+ `${modelB.title} List`
),
fk_model_id: modelA.id,
fk_related_model_id: modelB.id,
@@ -803,7 +803,7 @@ export async function extractAndGenerateManyToManyRelations(
await Column.insert({
title: getUniqueColumnAliasName(
modelB.columns,
- `${modelA.title}MMList`
+ `${modelA.title} List`
),
fk_model_id: modelB.id,
fk_related_model_id: modelA.id,
diff --git a/packages/nocodb/src/lib/meta/api/projectApis.ts b/packages/nocodb/src/lib/meta/api/projectApis.ts
index 1e8899a04b..69cc6797e0 100644
--- a/packages/nocodb/src/lib/meta/api/projectApis.ts
+++ b/packages/nocodb/src/lib/meta/api/projectApis.ts
@@ -215,7 +215,7 @@ async function populateMeta(base: Base, project: Project): Promise {
uidt: UITypes.LinkToAnotherRecord,
type: 'hm',
hm,
- title: `${hm.title}List`
+ title: `${hm.title} List`
};
}),
...belongsTo.map(bt => {
@@ -230,7 +230,7 @@ async function populateMeta(base: Base, project: Project): Promise {
uidt: UITypes.LinkToAnotherRecord,
type: 'bt',
bt,
- title: `${bt.rtitle}Read`
+ title: `${bt.rtitle}`
};
})
];
diff --git a/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts b/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
index 81885ed12d..86a1a30e9a 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
+++ b/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
@@ -5,10 +5,11 @@ import { Tele } from 'nc-help';
import bcrypt from 'bcryptjs';
import Noco from '../../../Noco';
-import { MetaTable } from '../../../utils/globals';
+import { CacheScope, MetaTable } from '../../../utils/globals';
import ProjectUser from '../../../models/ProjectUser';
import { validatePassword } from 'nocodb-sdk';
import boxen from 'boxen';
+import NocoCache from '../../../cache/NocoCache';
const { isEmail } = require('validator');
const rolesLevel = { owner: 0, creator: 1, editor: 2, commenter: 3, viewer: 4 };
@@ -103,7 +104,7 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
// check user account already present with the new admin email
const existingUserWithNewEmail = await User.getByEmail(email, ncMeta);
- if (existingUserWithNewEmail) {
+ if (existingUserWithNewEmail?.id) {
// get all project access belongs to the existing account
// and migrate to the admin account
const existingUserProjects = await ncMeta.metaList2(
@@ -155,13 +156,25 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
}
// delete existing user
- ncMeta.metaDelete(
+ await ncMeta.metaDelete(
null,
null,
MetaTable.USERS,
existingUserWithNewEmail.id
);
+ // clear cache
+ await NocoCache.delAll(
+ CacheScope.USER,
+ `${existingUserWithNewEmail.email}___*`
+ );
+ await NocoCache.del(
+ `${CacheScope.USER}:${existingUserWithNewEmail.id}`
+ );
+ await NocoCache.del(
+ `${CacheScope.USER}:${existingUserWithNewEmail.email}`
+ );
+
// Update email and password of super admin account
await User.update(
superUser.id,
@@ -169,7 +182,9 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
salt,
email,
password,
- email_verification_token
+ email_verification_token,
+ token_version: null,
+ refresh_token: null
},
ncMeta
);
@@ -181,22 +196,34 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
salt,
email,
password,
- email_verification_token
+ email_verification_token,
+ token_version: null,
+ refresh_token: null
},
ncMeta
);
}
} else {
- // if email's are not different update the password and hash
- await User.update(
- superUser.id,
- {
- salt,
- password,
- email_verification_token
- },
- ncMeta
+ const newPasswordHash = await promisify(bcrypt.hash)(
+ process.env.NC_ADMIN_PASSWORD,
+ superUser.salt
);
+
+ if (newPasswordHash !== superUser.password) {
+ // if email's are same and passwords are different
+ // then update the password and token version
+ await User.update(
+ superUser.id,
+ {
+ salt,
+ password,
+ email_verification_token,
+ token_version: null,
+ refresh_token: null
+ },
+ ncMeta
+ );
+ }
}
}
await ncMeta.commit();
diff --git a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
index e6c99869ac..b7d8599f7a 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
+++ b/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
@@ -103,8 +103,8 @@ export function initStrategies(router): void {
if (cachedVal) {
if (
- cachedVal.token_version &&
- jwtPayload.token_version &&
+ !cachedVal.token_version ||
+ !jwtPayload.token_version ||
cachedVal.token_version !== jwtPayload.token_version
) {
return done(new Error('Token Expired. Please login again.'));
@@ -115,8 +115,8 @@ export function initStrategies(router): void {
User.getByEmail(jwtPayload?.email)
.then(async user => {
if (
- user.token_version &&
- jwtPayload.token_version &&
+ !user.token_version ||
+ !jwtPayload.token_version ||
user.token_version !== jwtPayload.token_version
) {
return done(new Error('Token Expired. Please login again.'));
diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
index 320b4850e9..596de0cec4 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
+++ b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
@@ -179,15 +179,14 @@ async function successfulSignIn({
await promisify((req as any).login.bind(req))(user);
const refreshToken = randomTokenString();
- let token_version = user.token_version;
- if (!token_version) {
- token_version = randomTokenString();
+ if (!user.token_version) {
+ user.token_version = randomTokenString();
}
await User.update(user.id, {
refresh_token: refreshToken,
email: user.email,
- token_version
+ token_version: user.token_version
});
setTokenCookie(res, refreshToken);
diff --git a/packages/nocodb/src/lib/meta/api/utilApis.ts b/packages/nocodb/src/lib/meta/api/utilApis.ts
index 89ebef8cb6..ff04fb9d24 100644
--- a/packages/nocodb/src/lib/meta/api/utilApis.ts
+++ b/packages/nocodb/src/lib/meta/api/utilApis.ts
@@ -60,6 +60,14 @@ export async function releaseVersion(_req: Request, res: Response) {
res.json(result);
}
+export async function appHealth(_: Request, res: Response) {
+ res.json({
+ message: 'OK',
+ timestamp: Date.now(),
+ uptime: process.uptime()
+ });
+}
+
async function _axiosRequestMake(req: Request, res: Response) {
const { apiMeta } = req.body;
@@ -132,6 +140,7 @@ export default router => {
ncMetaAclMw(testConnection, 'testConnection')
);
router.get('/api/v1/db/meta/nocodb/info', catchError(appInfo));
- router.get('/api/v1/db/meta/nocodb/version', catchError(releaseVersion));
router.post('/api/v1/db/meta/axiosRequestMake', catchError(axiosRequestMake));
+ router.get('/api/v1/version', catchError(releaseVersion));
+ router.get('/api/v1/health', catchError(appHealth));
};
diff --git a/packages/nocodb/src/lib/models/User.ts b/packages/nocodb/src/lib/models/User.ts
index a1a91aada5..fe7743e1ca 100644
--- a/packages/nocodb/src/lib/models/User.ts
+++ b/packages/nocodb/src/lib/models/User.ts
@@ -84,6 +84,9 @@ export default class User implements UserType {
if (updateObj.email) {
updateObj.email = updateObj.email.toLowerCase();
+ } else {
+ // set email prop to avoid generation of invalid cache key
+ updateObj.email = (await this.get(id, ncMeta))?.email?.toLowerCase();
}
// get existing cache
const keys = [
diff --git a/scripts/cypress/integration/common/1a_table_operations.js b/scripts/cypress/integration/common/1a_table_operations.js
index ce208bbc05..3e5b2793bc 100644
--- a/scripts/cypress/integration/common/1a_table_operations.js
+++ b/scripts/cypress/integration/common/1a_table_operations.js
@@ -80,9 +80,9 @@ export const genTest = (apiType, dbType) => {
// 4a. Address table, has many field
cy.openTableTab("Address", 25);
- mainPage.getCell("CityRead", 1).scrollIntoView();
+ mainPage.getCell("City", 1).scrollIntoView();
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.find(".name")
.contains("Lethbridge")
.should("exist");
@@ -92,7 +92,7 @@ export const genTest = (apiType, dbType) => {
cy.openTableTab("Country", 25);
mainPage
- .getCell("CityList", 1)
+ .getCell("City List", 1)
.find(".name")
.contains("Kabul")
.should("exist");
diff --git a/scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js b/scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js
index 9f99519791..f37be89209 100644
--- a/scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js
+++ b/scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js
@@ -23,7 +23,7 @@ export const genTest = (apiType, dbType) => {
/*
Original order of list items
Actor, Address, Category, City, Country, Customer, FIlm, FilmText, Language, Payment, Rental Staff
- ActorInfo, CustomerList, FilmList, NiceButSlowerFilmList, SalesByFilmCategory, SalesByStore, StaffList
+ ActorInfo, Customer List, Film List, NiceButSlowerFilm List, SalesByFilmCategory, SalesByStore, Staff List
*/
it(`Table & SQL View list, Drag/drop`, () => {
diff --git a/scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js b/scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js
index a76f8cba7e..2aa191b0ab 100644
--- a/scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js
+++ b/scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js
@@ -18,7 +18,7 @@ export const genTest = (apiType, dbType) => {
/*
Original order of list items
Actor, Address, Category, City, Country, Customer, FIlm, FilmText, Language, Payment, Rental Staff
- ActorInfo, CustomerList, FilmList, NiceButSlowerFilmList, SalesByFilmCategory, SalesByStore, StaffList
+ ActorInfo, Customer List, Film List, NiceButSlowerFilm List, SalesByFilmCategory, SalesByStore, Staff List
*/
before(() => {
diff --git a/scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js b/scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js
index d8e046d6c1..8924e9ca8e 100644
--- a/scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js
+++ b/scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js
@@ -23,12 +23,12 @@ export const genTest = (apiType, dbType) => {
it("Expand belongs-to column", () => {
// expand first row
- cy.get('td[data-col="CityList"] div:visible', {
+ cy.get('td[data-col="City List"] div:visible', {
timeout: 12000,
})
.first()
.click();
- cy.get('td[data-col="CityList"] div .mdi-arrow-expand:visible')
+ cy.get('td[data-col="City List"] div .mdi-arrow-expand:visible')
.first()
.click();
diff --git a/scripts/cypress/integration/common/2b_table_with_m2m_column.js b/scripts/cypress/integration/common/2b_table_with_m2m_column.js
index c1d9a0f7d4..de4beeb246 100644
--- a/scripts/cypress/integration/common/2b_table_with_m2m_column.js
+++ b/scripts/cypress/integration/common/2b_table_with_m2m_column.js
@@ -23,10 +23,10 @@ export const genTest = (apiType, dbType) => {
it("Expand m2m column", () => {
// expand first row
- cy.get('td[data-col="FilmMMList"] div', { timeout: 12000 })
+ cy.get('td[data-col="Film List"] div', { timeout: 12000 })
.first()
.click({ force: true });
- cy.get('td[data-col="FilmMMList"] div .mdi-arrow-expand')
+ cy.get('td[data-col="Film List"] div .mdi-arrow-expand')
.first()
.click({ force: true });
diff --git a/scripts/cypress/integration/common/4c_form_view_detailed.js b/scripts/cypress/integration/common/4c_form_view_detailed.js
index e81ad93100..dbcbd4d585 100644
--- a/scripts/cypress/integration/common/4c_form_view_detailed.js
+++ b/scripts/cypress/integration/common/4c_form_view_detailed.js
@@ -119,7 +119,7 @@ export const genTest = (apiType, dbType) => {
.should("exist");
cy.get(".nc-field-wrapper")
.eq(1)
- .contains("CityList")
+ .contains("City List")
.should("exist");
cy.get(".nc-field-wrapper")
.eq(2)
diff --git a/scripts/cypress/integration/common/4d_table_view_grid_locked.js b/scripts/cypress/integration/common/4d_table_view_grid_locked.js
index 94630fc2e4..b702cfcdcb 100644
--- a/scripts/cypress/integration/common/4d_table_view_grid_locked.js
+++ b/scripts/cypress/integration/common/4d_table_view_grid_locked.js
@@ -77,18 +77,18 @@ export const genTest = (apiType, dbType) => {
// check if add/ expand options available for 'has many' column type
mainPage
- .getCell("CityList", 1)
+ .getCell("City List", 1)
.click()
.find("button.mdi-plus")
.should(`${vString}exist`);
mainPage
- .getCell("CityList", 1)
+ .getCell("City List", 1)
.click()
.find("button.mdi-arrow-expand")
.should(`${vString}exist`);
// update row option (right click) - should not be available for Lock view
- mainPage.getCell("CityList", 1).rightclick();
+ mainPage.getCell("City List", 1).rightclick();
cy.get(".menuable__content__active").should(
`${vString}be.visible`
);
diff --git a/scripts/cypress/integration/common/4e_form_view_share.js b/scripts/cypress/integration/common/4e_form_view_share.js
index b2e16ba956..c661775c3b 100644
--- a/scripts/cypress/integration/common/4e_form_view_share.js
+++ b/scripts/cypress/integration/common/4e_form_view_share.js
@@ -72,7 +72,7 @@ export const genTest = (apiType, dbType) => {
// "#data-table-form-City"
// );
- cy.get('[title="AddressList"]').drag(".nc-drag-n-drop-to-hide");
+ cy.get('[title="Address List"]').drag(".nc-drag-n-drop-to-hide");
cy.get(".nc-form > .mx-auto")
.find('[type="checkbox"]')
@@ -131,8 +131,8 @@ export const genTest = (apiType, dbType) => {
// all fields, barring removed field should exist
cy.get('[title="City"]').should("exist");
cy.get('[title="LastUpdate"]').should("exist");
- cy.get('[title="CountryRead"]').should("exist");
- cy.get('[title="AddressList"]').should("not.exist");
+ cy.get('[title="Country"]').should("exist");
+ cy.get('[title="Address List"]').should("not.exist");
// order of LastUpdate & City field is retained
cy.get(".nc-field-wrapper")
diff --git a/scripts/cypress/integration/common/4f_grid_view_share.js b/scripts/cypress/integration/common/4f_grid_view_share.js
index 5c5bda992d..c625951b58 100644
--- a/scripts/cypress/integration/common/4f_grid_view_share.js
+++ b/scripts/cypress/integration/common/4f_grid_view_share.js
@@ -170,7 +170,7 @@ export const genTest = (apiType, dbType) => {
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
- `Address,District,PostalCode,Phone,Location,CustomerList,StaffList,CityRead,StaffMMList`,
+ `Address,District,PostalCode,Phone,Location,Customer List,Staff List,City,Staff List`,
`1013 Tabuk Boulevard,West Bengali,96203,158399646978,[object Object],2,,Kanchrapara,`,
`1892 Nabereznyje Telny Lane,Tutuila,28396,478229987054,[object Object],2,,Tafuna,`,
`1993 Tabuk Lane,Tamil Nadu,64221,648482415405,[object Object],2,,Tambaram,`,
@@ -231,7 +231,7 @@ export const genTest = (apiType, dbType) => {
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
- `Address,District,PostalCode,Phone,Location,CustomerList,StaffList,CityRead,StaffMMList`,
+ `Address,District,PostalCode,Phone,Location,Customer List,Staff List,City,Staff List`,
`1993 Tabuk Lane,Tamil Nadu,64221,648482415405,[object Object],2,,Tambaram,`,
`1661 Abha Drive,Tamil Nadu,14400,270456873752,[object Object],1,,Pudukkottai,`,
];
@@ -267,24 +267,24 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > has many`, () => {
// verify column headers
- cy.get('[data-col="CustomerList"]').should("exist");
- cy.get('[data-col="StaffList"]').should("exist");
- cy.get('[data-col="CityRead"]').should("exist");
- cy.get('[data-col="StaffMMList"]').should("exist");
+ cy.get('[data-col="Customer List"]').should("exist");
+ cy.get('[data-col="Staff List"]').should("exist");
+ cy.get('[data-col="City"]').should("exist");
+ cy.get('[data-col="Staff List"]').should("exist");
// has many field validation
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-plus")
.should("not.exist");
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-arrow-expand")
.click();
@@ -308,17 +308,17 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > belongs to`, () => {
// belongs to field validation
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.click()
.find("button.mdi-arrow-expand")
.should("not.exist");
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.find(".v-chip")
.contains("Kanchrapara")
.should("exist");
@@ -327,17 +327,17 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > many to many`, () => {
// many-to-many field validation
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-plus")
.should("not.exist");
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-arrow-expand")
.click();
diff --git a/scripts/cypress/integration/common/4f_pg_grid_view_share.js b/scripts/cypress/integration/common/4f_pg_grid_view_share.js
index 7e450dcbab..e049477f05 100644
--- a/scripts/cypress/integration/common/4f_pg_grid_view_share.js
+++ b/scripts/cypress/integration/common/4f_pg_grid_view_share.js
@@ -228,7 +228,7 @@ export const genTest = (apiType, dbType) => {
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
- `Address,District,PostalCode,Phone,Location,CustomerList,StaffList,CityRead,StaffMMList`,
+ `Address,District,PostalCode,Phone,Location,Customer List,Staff List,City,Staff List`,
`1888 Kabul Drive,,20936,,1,,Ife,,`,
`1661 Abha Drive,,14400,,1,,Pudukkottai,,`,
];
@@ -262,24 +262,24 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > has many`, () => {
// verify column headers
- cy.get('[data-col="CustomerList"]').should("exist");
- cy.get('[data-col="StaffList"]').should("exist");
- cy.get('[data-col="CityRead"]').should("exist");
- cy.get('[data-col="StaffMMList"]').should("exist");
+ cy.get('[data-col="Customer List"]').should("exist");
+ cy.get('[data-col="Staff List"]').should("exist");
+ cy.get('[data-col="City"]').should("exist");
+ cy.get('[data-col="Staff List"]').should("exist");
// has many field validation
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-plus")
.should("not.exist");
mainPage
- .getCell("CustomerList", 3)
+ .getCell("Customer List", 3)
.click()
.find("button.mdi-arrow-expand")
.click();
@@ -303,17 +303,17 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > belongs to`, () => {
// belongs to field validation
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.click()
.find("button.mdi-arrow-expand")
.should("not.exist");
mainPage
- .getCell("CityRead", 1)
+ .getCell("City", 1)
.find(".v-chip")
.contains("al-Ayn")
.should("exist");
@@ -322,17 +322,17 @@ export const genTest = (apiType, dbType) => {
it(`Share GRID view : Virtual column validation > many to many`, () => {
// many-to-many field validation
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-close-thick")
.should("not.exist");
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-plus")
.should("not.exist");
mainPage
- .getCell("StaffMMList", 1)
+ .getCell("Staff List", 1)
.click()
.find("button.mdi-arrow-expand")
.click();
diff --git a/scripts/cypress/integration/common/5a_user_role.js b/scripts/cypress/integration/common/5a_user_role.js
index 0c46ef67a7..232f13f7c6 100644
--- a/scripts/cypress/integration/common/5a_user_role.js
+++ b/scripts/cypress/integration/common/5a_user_role.js
@@ -209,7 +209,7 @@ export const genTest = (apiType, dbType) => {
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
- `City,AddressList,CountryRead`,
+ `City,Address List,Country`,
`A Corua (La Corua),939 Probolinggo Loop,Spain`,
`Abha,733 Mandaluyong Place,Saudi Arabia`,
`Abu Dhabi,535 Ahmadnagar Manor,United Arab Emirates`,
diff --git a/scripts/cypress/integration/common/6b_downloadCsv.js b/scripts/cypress/integration/common/6b_downloadCsv.js
index 1fe80dced5..fc920ba8cb 100644
--- a/scripts/cypress/integration/common/6b_downloadCsv.js
+++ b/scripts/cypress/integration/common/6b_downloadCsv.js
@@ -31,7 +31,7 @@ export const genTest = (apiType, dbType) => {
// `Angola,"Benguela, Namibe"`,
// ];
let storedRecords = [
- ['Country','CityList'],
+ ['Country','City List'],
['Afghanistan','Kabul'],
['Algeria','Skikda', 'Bchar', 'Batna'],
['American Samoa','Tafuna'],
@@ -41,7 +41,7 @@ export const genTest = (apiType, dbType) => {
// if (isPostgres()) {
// // order of second entry is different
// storedRecords = [
- // `Country,CityList`,
+ // `Country,City List`,
// `Afghanistan,Kabul`,
// `Algeria,"Skikda, Bchar, Batna"`,
// `American Samoa,Tafuna`,
diff --git a/scripts/cypress/integration/common/6f_attachments.js b/scripts/cypress/integration/common/6f_attachments.js
index 4d3e8ef036..b7aab2dc0d 100644
--- a/scripts/cypress/integration/common/6f_attachments.js
+++ b/scripts/cypress/integration/common/6f_attachments.js
@@ -113,7 +113,7 @@ export const genTest = (apiType, dbType) => {
const verifyCsv = (retrievedRecords) => {
let storedRecords = [
- `Country,CityList,testAttach`,
+ `Country,City List,testAttach`,
`Afghanistan,Kabul,1.json(http://localhost:8080/download/p_h0wxjx5kgoq3w4/vw_skyvc7hsp9i34a/2HvU8R.json)`,
];
diff --git a/scripts/cypress/integration/spec/roleValidation.spec.js b/scripts/cypress/integration/spec/roleValidation.spec.js
index e88c6af853..f9156c7a9f 100644
--- a/scripts/cypress/integration/spec/roleValidation.spec.js
+++ b/scripts/cypress/integration/spec/roleValidation.spec.js
@@ -236,7 +236,7 @@ export function _viewMenu(roleType, previewMode, navDrawListCnt) {
// Download CSV / Upload CSV / Shared View List / Webhook
actionsMenuItemsCnt = 4;
} else if (roleType == "editor") {
- // Download CSV / Upload CSV
+ // Download CSV / Upload CSV
actionsMenuItemsCnt = 2;
}
diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json
index 670b1a57b3..2f68462f6d 100644
--- a/scripts/sdk/swagger.json
+++ b/scripts/sdk/swagger.json
@@ -5107,7 +5107,7 @@
]
}
},
- "/api/v1/db/meta/nocodb/version": {
+ "/api/v1/version": {
"parameters": [],
"get": {
"summary": "",
@@ -5128,6 +5128,27 @@
"description": ""
}
},
+ "/api/v1/health": {
+ "parameters": [],
+ "get": {
+ "summary": "",
+ "operationId": "utils-app-health",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ },
+ "tags": [
+ "Utils"
+ ],
+ "description": ""
+ }
+ },
"/api/v1/db/meta/cache": {
"get": {
"summary": "Your GET endpoint",