Browse Source

Merge pull request #5174 from nocodb/feat/ajv-validation-and-human-readable-error

feat: API payload validation and error handling
pull/5193/head
Pranav C 2 years ago committed by GitHub
parent
commit
c4a78fddf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .github/workflows/publish-api-docs.yml
  2. 4
      packages/nc-gui/components.d.ts
  3. 11
      packages/nc-gui/utils/errorUtils.ts
  4. 2
      packages/nocodb-sdk/package.json
  5. 588
      packages/nocodb-sdk/src/lib/Api.ts
  6. 325
      packages/nocodb/package-lock.json
  7. 1
      packages/nocodb/package.json
  8. 9
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/CustomKnex.ts
  9. 8
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/getAst.ts
  10. 2
      packages/nocodb/src/lib/meta/api/apiTokenApis.ts
  11. 3
      packages/nocodb/src/lib/meta/api/auditApis.ts
  12. 4
      packages/nocodb/src/lib/meta/api/baseApis.ts
  13. 2
      packages/nocodb/src/lib/meta/api/columnApis.ts
  14. 4
      packages/nocodb/src/lib/meta/api/filterApis.ts
  15. 3
      packages/nocodb/src/lib/meta/api/formViewApis.ts
  16. 2
      packages/nocodb/src/lib/meta/api/formViewColumnApis.ts
  17. 3
      packages/nocodb/src/lib/meta/api/galleryViewApis.ts
  18. 2
      packages/nocodb/src/lib/meta/api/gridViewApis.ts
  19. 2
      packages/nocodb/src/lib/meta/api/gridViewColumnApis.ts
  20. 34
      packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts
  21. 3
      packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts
  22. 4
      packages/nocodb/src/lib/meta/api/hookApis.ts
  23. 3
      packages/nocodb/src/lib/meta/api/hookFilterApis.ts
  24. 3
      packages/nocodb/src/lib/meta/api/kanbanViewApis.ts
  25. 1
      packages/nocodb/src/lib/meta/api/mapViewApis.ts
  26. 2
      packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts
  27. 2
      packages/nocodb/src/lib/meta/api/orgLicenseApis.ts
  28. 2
      packages/nocodb/src/lib/meta/api/orgTokenApis.ts
  29. 3
      packages/nocodb/src/lib/meta/api/orgUserApis.ts
  30. 4
      packages/nocodb/src/lib/meta/api/pluginApis.ts
  31. 3
      packages/nocodb/src/lib/meta/api/projectApis.ts
  32. 3
      packages/nocodb/src/lib/meta/api/projectUserApis.ts
  33. 3
      packages/nocodb/src/lib/meta/api/sharedBaseApis.ts
  34. 3
      packages/nocodb/src/lib/meta/api/sortApis.ts
  35. 13
      packages/nocodb/src/lib/meta/api/tableApis.ts
  36. 65
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  37. 1
      packages/nocodb/src/lib/meta/api/viewApis.ts
  38. 384
      packages/nocodb/src/lib/meta/helpers/catchError.ts
  39. 6
      packages/nocodb/src/lib/models/Base.ts
  40. 4
      packages/nocodb/src/lib/models/Filter.ts
  41. 3
      packages/nocodb/src/lib/models/LinkToAnotherRecordColumn.ts
  42. 1
      packages/nocodb/src/lib/models/MapView.ts
  43. 15
      packages/nocodb/src/lib/models/Model.ts
  44. 6
      packages/nocodb/src/lib/models/Project.ts
  45. 1
      packages/nocodb/src/lib/utils/globals.ts
  46. 1218
      packages/nocodb/src/schema/swagger.json

6
.github/workflows/publish-api-docs.yml

@ -4,7 +4,7 @@ on:
push:
branches: [ master ]
paths:
- "scripts/sdk/swagger.json"
- "packages/nocodb/src/schema/swagger.json"
release:
types: [ published ]
@ -22,7 +22,7 @@ jobs:
env:
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
with:
source_file: 'scripts/sdk/swagger.json'
source_file: 'packages/nocodb/src/schema/swagger.json'
destination_repo: 'nocodb/noco-apis-doc'
destination_folder: 'src'
user_email: 'oof1lab@gmail.com'
@ -34,7 +34,7 @@ jobs:
env:
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
with:
source_file: 'scripts/sdk/swagger.json'
source_file: 'packages/nocodb/src/schema/swagger.json'
destination_repo: 'nocodb/noco-apis-doc'
destination_folder: 'meta-src'
user_email: 'oof1lab@gmail.com'

4
packages/nc-gui/components.d.ts vendored

@ -251,8 +251,12 @@ declare module '@vue/runtime-core' {
NcIconsRowHeightMedium: typeof import('~icons/nc-icons/row-height-medium')['default']
NcIconsRowHeightShort: typeof import('~icons/nc-icons/row-height-short')['default']
NcIconsRowHeightTall: typeof import('~icons/nc-icons/row-height-tall')['default']
PhChatTextThin: typeof import('~icons/ph/chat-text-thin')['default']
PhCloudLightningDuotone: typeof import('~icons/ph/cloud-lightning-duotone')['default']
PhCloudLightningThin: typeof import('~icons/ph/cloud-lightning-thin')['default']
PhFileCsv: typeof import('~icons/ph/file-csv')['default']
PhUserPlusThin: typeof import('~icons/ph/user-plus-thin')['default']
PhUsersThreeThin: typeof import('~icons/ph/users-three-thin')['default']
RiLineHeight: typeof import('~icons/ri/line-height')['default']
RiTeamFill: typeof import('~icons/ri/team-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']

11
packages/nc-gui/utils/errorUtils.ts

@ -1,14 +1,23 @@
export async function extractSdkResponseErrorMsg(e: Error & { response: any }) {
if (!e || !e.response) return e.message
let msg
let errors: any[] | null = null
if (e.response.data instanceof Blob) {
try {
msg = JSON.parse(await e.response.data.text()).msg
const parsedData = JSON.parse(await e.response.data.text())
msg = parsedData.msg
errors = parsedData.errors
} catch {
msg = 'Some internal error occurred'
}
} else {
msg = e.response.data.msg || e.response.data.message || 'Some internal error occurred'
errors = e.response.data.errors
}
if (Array.isArray(errors) && errors.length) {
return errors.map((e: any) => (e.instancePath ? `${e.instancePath} - ` : '') + e.message).join(', ')
}
return msg || 'Some error occurred'
}

2
packages/nocodb-sdk/package.json

@ -31,7 +31,7 @@
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
"watch:build": "tsc -p tsconfig.json -w",
"generate:sdk": "npx --yes swagger-typescript-api@10.0.3 -r -p ../../scripts/sdk/swagger.json -o ./src/lib/ --axios --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates"
"generate:sdk": "npx --yes swagger-typescript-api@10.0.3 -r -p ../nocodb/src/schema/swagger.json -o ./src/lib/ --axios --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates"
},
"dependencies": {
"axios": "^0.21.1",

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

File diff suppressed because it is too large Load Diff

325
packages/nocodb/package-lock.json generated

@ -13,6 +13,7 @@
"@graphql-tools/merge": "^6.0.12",
"@sentry/node": "^6.3.5",
"airtable": "^0.11.3",
"ajv": "^8.12.0",
"archiver": "^5.0.2",
"auto-bind": "^4.0.0",
"aws-sdk": "^2.829.0",
@ -658,6 +659,22 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@ -682,6 +699,12 @@
"node": ">= 4"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@ -2215,13 +2238,13 @@
"integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q=="
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
@ -2238,15 +2261,6 @@
"ajv": ">=5.0.0"
}
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@ -5957,6 +5971,22 @@
"@babel/highlight": "^7.10.4"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -6042,6 +6072,12 @@
"node": ">= 4"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/eslint/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -8002,6 +8038,26 @@
"node": ">=6"
}
},
"node_modules/har-validator/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/har-validator/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -9465,9 +9521,9 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@ -13898,7 +13954,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -14179,6 +14234,37 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/scmp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
@ -15573,22 +15659,6 @@
"node": ">=10.0.0"
}
},
"node_modules/table/node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/table/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -15598,12 +15668,6 @@
"node": ">=8"
}
},
"node_modules/table/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/table/node_modules/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@ -18168,6 +18232,31 @@
"node": ">=0.4.0"
}
},
"node_modules/webpack/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/webpack/node_modules/braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@ -18303,6 +18392,12 @@
"node": ">=0.10.0"
}
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/webpack/node_modules/json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
@ -19500,6 +19595,18 @@
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"globals": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@ -19515,6 +19622,12 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@ -20789,13 +20902,13 @@
}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
@ -20806,13 +20919,6 @@
"dev": true,
"requires": {}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@ -23538,6 +23644,18 @@
"@babel/highlight": "^7.10.4"
}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -23592,6 +23710,12 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -25352,6 +25476,24 @@
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
}
}
},
"has": {
@ -26420,9 +26562,9 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@ -29930,8 +30072,7 @@
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"require-main-filename": {
"version": "2.0.0",
@ -30138,6 +30279,33 @@
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
}
},
"scmp": {
@ -31273,30 +31441,12 @@
"strip-ansi": "^6.0.1"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@ -32978,6 +33128,25 @@
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
},
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@ -33095,6 +33264,12 @@
}
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",

1
packages/nocodb/package.json

@ -53,6 +53,7 @@
"@graphql-tools/merge": "^6.0.12",
"@sentry/node": "^6.3.5",
"airtable": "^0.11.3",
"ajv": "^8.12.0",
"archiver": "^5.0.2",
"auto-bind": "^4.0.0",
"aws-sdk": "^2.829.0",

9
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/CustomKnex.ts

@ -1,5 +1,6 @@
import { Knex, knex } from 'knex';
import { SnowflakeClient } from 'nc-help';
import { FilterType } from 'nocodb-sdk';
const types = require('pg').types;
// override parsing date column to Date()
@ -1248,7 +1249,13 @@ knex.QueryBuilder.extend('conditionv2', function (conditionObj: Filter) {
return parseConditionv2(conditionObj, this);
} as any);
const parseConditionv2 = (obj: Filter, qb: Knex.QueryBuilder) => {
const parseConditionv2 = (_obj: Filter | FilterType, qb: Knex.QueryBuilder) => {
let obj: Filter;
if (_obj instanceof Filter) {
obj = _obj;
} else {
obj = new Filter(_obj);
}
if (obj.is_group) {
qb = qb.where(function () {
const children = obj.children;

8
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/getAst.ts

@ -81,13 +81,13 @@ const getAst = async ({
...(await obj),
[col.title]:
allowedCols && (!includePkByDefault || !col.pk)
? (allowedCols[col.id] &&
? allowedCols[col.id] &&
(!isSystemColumn(col) || view.show_system_fields) &&
(!fields?.length || fields.includes(col.title)) &&
value)
: (fields?.length
value
: fields?.length
? fields.includes(col.title) && value
: value),
: value,
};
}, Promise.resolve({}));
};

2
packages/nocodb/src/lib/meta/api/apiTokenApis.ts

@ -5,6 +5,7 @@ import { NcError } from '../helpers/catchError';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import ApiToken from '../../models/ApiToken';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function apiTokenList(req: Request, res: Response) {
res.json(await ApiToken.list(req['user'].id));
@ -40,6 +41,7 @@ router.get(
router.post(
'/api/v1/db/meta/projects/:projectId/api-tokens',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
);
router.delete(

3
packages/nocodb/src/lib/meta/api/auditApis.ts

@ -6,6 +6,7 @@ import { PagedResponseImpl } from '../helpers/PagedResponse';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import DOMPurify from 'isomorphic-dompurify';
import { getAjvValidatorMw } from './helpers';
export async function commentRow(req: Request<any, any>, res) {
res.json(
@ -69,10 +70,12 @@ router.get(
);
router.post(
'/api/v1/db/meta/audits/comments',
getAjvValidatorMw('swagger.json#/components/schemas/CommentReq'),
ncMetaAclMw(commentRow, 'commentRow')
);
router.post(
'/api/v1/db/meta/audits/rows/:rowId/update',
getAjvValidatorMw('swagger.json#/components/schemas/AuditRowUpdateReq'),
ncMetaAclMw(auditRowUpdate, 'auditRowUpdate')
);
router.get(

4
packages/nocodb/src/lib/meta/api/baseApis.ts

@ -7,7 +7,7 @@ import Base from '../../models/Base';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { populateMeta } from './helpers';
import { getAjvValidatorMw, populateMeta } from './helpers';
export async function baseGet(
req: Request<any, any, any>,
@ -107,6 +107,7 @@ export default (router) => {
router.patch(
'/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseUpdate, 'baseUpdate')
);
router.delete(
@ -117,6 +118,7 @@ export default (router) => {
router.post(
'/api/v1/db/meta/projects/:projectId/bases',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseCreate, 'baseCreate')
);
router.get(

2
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -41,6 +41,7 @@ import formulaQueryBuilderv2 from '../../db/sql-data-mapper/lib/sql/formulav2/fo
import {
createHmAndBtColumn,
generateFkName,
getAjvValidatorMw,
randomID,
validateLookupPayload,
validateRequiredField,
@ -1785,6 +1786,7 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/columns/',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ColumnReq'),
ncMetaAclMw(columnAdd, 'columnAdd')
);

4
packages/nocodb/src/lib/meta/api/filterApis.ts

@ -13,6 +13,7 @@ import Project from '../../models/Project';
import Filter from '../../models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore
export async function filterGet(req: Request, res: Response, next) {
@ -135,6 +136,7 @@ router.get(
router.post(
'/api/v1/db/meta/views/:viewId/filters',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate')
);
@ -145,6 +147,7 @@ router.get(
router.post(
'/api/v1/db/meta/hooks/:hookId/filters',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(hookFilterCreate, 'filterCreate')
);
@ -156,6 +159,7 @@ router.get(
router.patch(
'/api/v1/db/meta/filters/:filterId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate')
);
router.delete(

3
packages/nocodb/src/lib/meta/api/formViewApis.ts

@ -13,6 +13,7 @@ import View from '../../models/View';
import FormView from '../../models/FormView';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore
export async function formViewGet(req: Request, res: Response<FormType>) {
@ -43,6 +44,7 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/forms',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormCreateReq'),
ncMetaAclMw(formViewCreate, 'formViewCreate')
);
router.get(
@ -53,6 +55,7 @@ router.get(
router.patch(
'/api/v1/db/meta/forms/:formViewId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormReq'),
ncMetaAclMw(formViewUpdate, 'formViewUpdate')
);
router.delete(

2
packages/nocodb/src/lib/meta/api/formViewColumnApis.ts

@ -3,6 +3,7 @@ import FormViewColumn from '../../models/FormViewColumn';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function columnUpdate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'formViewColumn:updated' });
@ -13,6 +14,7 @@ const router = Router({ mergeParams: true });
router.patch(
'/api/v1/db/meta/form-columns/:formViewColumnId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormColumnReq'),
ncMetaAclMw(columnUpdate, 'columnUpdate')
);
export default router;

3
packages/nocodb/src/lib/meta/api/galleryViewApis.ts

@ -5,6 +5,7 @@ import GalleryView from '../../models/GalleryView';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function galleryViewGet(req: Request, res: Response<GalleryType>) {
res.json(await GalleryView.get(req.params.galleryViewId));
}
@ -29,11 +30,13 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/galleries',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
);
router.patch(
'/api/v1/db/meta/galleries/:galleryViewId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
);
router.get(

2
packages/nocodb/src/lib/meta/api/gridViewApis.ts

@ -13,6 +13,7 @@ import View from '../../models/View';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import GridView from '../../models/GridView';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore
export async function gridViewCreate(req: Request<any, any>, res) {
@ -35,6 +36,7 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/grids/',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridReq'),
ncMetaAclMw(gridViewCreate, 'gridViewCreate')
);
router.patch(

2
packages/nocodb/src/lib/meta/api/gridViewColumnApis.ts

@ -3,6 +3,7 @@ import GridViewColumn from '../../models/GridViewColumn';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function columnList(req: Request, res: Response) {
res.json(await GridViewColumn.list(req.params.gridViewId));
@ -22,6 +23,7 @@ router.get(
router.patch(
'/api/v1/db/meta/grid-columns/:gridViewColumnId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridColumnReq'),
ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate')
);
export default router;

34
packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts

@ -1,4 +1,38 @@
import { NextFunction, Request, Response } from 'express';
import Ajv, { ErrorObject } from 'ajv';
// @ts-ignore
import swagger from '../../../../schema/swagger.json';
export function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return seconds;
}
const ajv = new Ajv({ strictSchema: false, strict: false }); // Initialize AJV
ajv.addSchema(swagger, 'swagger.json');
// A middleware generator to validate the request body
export const getAjvValidatorMw = (schema) => {
return (req: Request, res: Response, next: NextFunction) => {
// Validate the request body against the schema
const valid = ajv.validate(
typeof schema === 'string' ? { $ref: schema } : schema,
req.body
);
// If the request body is valid, call the next middleware
if (valid) {
next();
} else {
const errors: ErrorObject[] | null | undefined = ajv.errors;
// If the request body is invalid, send a response with an error message
res.status(400).json({
status: 'error',
message: 'Invalid request body',
errors,
});
}
};
};

3
packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts

@ -3,6 +3,7 @@ import {
ColumnReqType,
LinkToAnotherRecordType,
LookupColumnReqType,
BoolType,
RelationTypes,
RollupColumnReqType,
TableType,
@ -27,7 +28,7 @@ export async function createHmAndBtColumn(
type?: RelationTypes,
alias?: string,
fkColName?: string,
virtual = false,
virtual: BoolType = false,
isSystemCol = false
) {
// save bt column

4
packages/nocodb/src/lib/meta/api/hookApis.ts

@ -9,6 +9,7 @@ import Model from '../../models/Model';
import populateSamplePayload from '../helpers/populateSamplePayload';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function hookList(
req: Request<any, any, any>,
@ -85,11 +86,13 @@ router.get(
router.post(
'/api/v1/db/meta/tables/:tableId/hooks/test',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookTestReq'),
ncMetaAclMw(hookTest, 'hookTest')
);
router.post(
'/api/v1/db/meta/tables/:tableId/hooks',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookCreate, 'hookCreate')
);
router.delete(
@ -100,6 +103,7 @@ router.delete(
router.patch(
'/api/v1/db/meta/hooks/:hookId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookUpdate, 'hookUpdate')
);
router.get(

3
packages/nocodb/src/lib/meta/api/hookFilterApis.ts

@ -13,6 +13,7 @@ import Project from '../../models/Project';
import Filter from '../../models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore
export async function filterGet(req: Request, res: Response, next) {
@ -117,6 +118,7 @@ router.get(
router.post(
'/hooks/:hookId/filters/',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate')
);
router.get(
@ -127,6 +129,7 @@ router.get(
router.patch(
'/hooks/:hookId/filters/:filterId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate')
);
router.delete(

3
packages/nocodb/src/lib/meta/api/kanbanViewApis.ts

@ -5,6 +5,7 @@ import KanbanView from '../../models/KanbanView';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function kanbanViewGet(req: Request, res: Response<KanbanType>) {
res.json(await KanbanView.get(req.params.kanbanViewId));
@ -31,11 +32,13 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/kanbans',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanReq'),
ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate')
);
router.patch(
'/api/v1/db/meta/kanbans/:kanbanViewId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanUpdateReq'),
ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate')
);
router.get(

1
packages/nocodb/src/lib/meta/api/mapViewApis.ts

@ -28,6 +28,7 @@ export async function mapViewUpdate(req, res) {
const router = Router({ mergeParams: true });
// todo: add schema in swagger and use getAjvValidatorMw
router.post(
'/api/v1/db/meta/tables/:tableId/maps',
metaApiMetrics,

2
packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts

@ -4,6 +4,7 @@ import { Router } from 'express';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
async function xcVisibilityMetaSetAll(req, res) {
Tele.emit('evt', { evt_type: 'uiAcl:updated' });
for (const d of req.body) {
@ -122,6 +123,7 @@ router.get(
router.post(
'/api/v1/db/meta/projects/:projectId/visibility-rules',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/VisibilityRuleReq'),
ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
);
export default router;

2
packages/nocodb/src/lib/meta/api/orgLicenseApis.ts

@ -5,6 +5,7 @@ import Store from '../../models/Store';
import Noco from '../../Noco';
import { metaApiMetrics } from '../helpers/apiMetrics';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { getAjvValidatorMw } from './helpers';
async function licenseGet(_req, res) {
const license = await Store.get(NC_LICENSE_KEY);
@ -30,6 +31,7 @@ router.get(
router.post(
'/api/v1/license',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/LicenseReq'),
ncMetaAclMw(licenseSet, 'licenseSet', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true,

2
packages/nocodb/src/lib/meta/api/orgTokenApis.ts

@ -8,6 +8,7 @@ import getHandler from '../helpers/getHandler';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../helpers/PagedResponse';
import { apiTokenListEE } from './ee/orgTokenApis';
import { getAjvValidatorMw } from './helpers';
async function apiTokenList(req, res) {
const fk_user_id = req.user.id;
@ -65,6 +66,7 @@ router.get(
router.post(
'/api/v1/tokens',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', {
// allowedRoles: [OrgUserRoles.SUPER],
blockApiTokenAccess: true,

3
packages/nocodb/src/lib/meta/api/orgUserApis.ts

@ -22,6 +22,7 @@ import { extractProps } from '../helpers/extractProps';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../helpers/PagedResponse';
import { randomTokenString } from '../helpers/stringHelpers';
import { getAjvValidatorMw } from './helpers';
import { sendInviteEmail } from './projectUserApis';
async function userList(req, res) {
@ -266,6 +267,7 @@ router.get(
router.patch(
'/api/v1/users/:userId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userUpdate, 'userUpdate', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true,
@ -282,6 +284,7 @@ router.delete(
router.post(
'/api/v1/users',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userAdd, 'userAdd', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true,

4
packages/nocodb/src/lib/meta/api/pluginApis.ts

@ -6,6 +6,7 @@ import { PluginType } from 'nocodb-sdk';
import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function pluginList(_req: Request, res: Response) {
res.json(new PagedResponseImpl(await Plugin.list()));
@ -43,6 +44,8 @@ router.get(
router.post(
'/api/v1/db/meta/plugins/test',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginTestReq'),
ncMetaAclMw(pluginTest, 'pluginTest')
);
router.get(
@ -53,6 +56,7 @@ router.get(
router.patch(
'/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginReq'),
ncMetaAclMw(pluginUpdate, 'pluginUpdate')
);
router.get(

3
packages/nocodb/src/lib/meta/api/projectApis.ts

@ -18,7 +18,7 @@ import { metaApiMetrics } from '../helpers/apiMetrics';
import { extractPropsAndSanitize } from '../helpers/extractProps';
import NcConfigFactory from '../../utils/NcConfigFactory';
import { promisify } from 'util';
import { populateMeta } from './helpers';
import { getAjvValidatorMw, populateMeta } from './helpers';
import Filter from '../../models/Filter';
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
@ -272,6 +272,7 @@ export default (router) => {
router.post(
'/api/v1/db/meta/projects',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectReq'),
ncMetaAclMw(projectCreate, 'projectCreate')
);
router.get(

3
packages/nocodb/src/lib/meta/api/projectUserApis.ts

@ -17,6 +17,7 @@ import Noco from '../../Noco';
import { PluginCategory } from 'nocodb-sdk';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { randomTokenString } from '../helpers/stringHelpers';
import { getAjvValidatorMw } from './helpers';
async function userList(req, res) {
res.json({
@ -310,11 +311,13 @@ router.get(
router.post(
'/api/v1/db/meta/projects/:projectId/users',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(userInvite, 'userInvite')
);
router.patch(
'/api/v1/db/meta/projects/:projectId/users/:userId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
);
router.delete(

3
packages/nocodb/src/lib/meta/api/sharedBaseApis.ts

@ -4,6 +4,7 @@ import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { v4 as uuidv4 } from 'uuid';
import Project from '../../models/Project';
import { NcError } from '../helpers/catchError';
import { getAjvValidatorMw } from './helpers';
// todo: load from config
const config = {
dashboardPath: '/nc',
@ -96,10 +97,12 @@ router.get(
);
router.post(
'/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink')
);
router.patch(
'/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink')
);
router.delete(

3
packages/nocodb/src/lib/meta/api/sortApis.ts

@ -12,6 +12,7 @@ import Project from '../../models/Project';
import Sort from '../../models/Sort';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore
export async function sortGet(req: Request, res: Response<TableType>) {}
@ -58,6 +59,7 @@ router.get(
router.post(
'/api/v1/db/meta/views/:viewId/sorts/',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortCreate, 'sortCreate')
);
router.get(
@ -68,6 +70,7 @@ router.get(
router.patch(
'/api/v1/db/meta/sorts/:sortId',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortUpdate, 'sortUpdate')
);
router.delete(

13
packages/nocodb/src/lib/meta/api/tableApis.ts

@ -8,6 +8,7 @@ import {
AuditOperationTypes,
isVirtualCol,
ModelTypes,
NormalColumnRequestType,
TableListType,
TableReqType,
TableType,
@ -17,6 +18,7 @@ import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
import Project from '../../models/Project';
import Audit from '../../models/Audit';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { getAjvValidatorMw } from './helpers';
import { xcVisibilityMetaGet } from './modelVisibilityApis';
import View from '../../models/View';
import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
@ -216,10 +218,13 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
columns: columns.map((c, i) => {
const colMetaFromReq = req.body?.columns?.find(
(c1) => c.cn === c1.column_name
);
) as NormalColumnRequestType;
return {
...colMetaFromReq,
uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c),
uidt:
(colMetaFromReq?.uidt as string) ||
c.uidt ||
getColumnUiType(base, c),
...c,
dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
colMetaFromReq.uidt as any
@ -229,7 +234,7 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),
column_name: c.cn,
order: i + 1,
};
} as NormalColumnRequestType;
}),
order: +(tables?.pop()?.order ?? 0) + 1,
})
@ -410,11 +415,13 @@ router.get(
router.post(
'/api/v1/db/meta/projects/:projectId/tables',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate')
);
router.post(
'/api/v1/db/meta/projects/:projectId/:baseId/tables',
metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate')
);
router.get(

65
packages/nocodb/src/lib/meta/api/userApi/userApis.ts

@ -22,6 +22,7 @@ import extractProjectIdAndAuthenticate from '../../helpers/extractProjectIdAndAu
import ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { MetaTable } from '../../../utils/globals';
import Noco from '../../../Noco';
import { getAjvValidatorMw } from '../helpers';
import { genJwt } from './helpers';
import { randomTokenString } from '../../helpers/stringHelpers';
@ -523,15 +524,32 @@ async function renderPasswordReset(req, res): Promise<any> {
const mapRoutes = (router) => {
// todo: old api - /auth/signup?tool=1
router.post('/auth/user/signup', catchError(signup));
router.post('/auth/user/signin', catchError(signin));
router.post(
'/auth/user/signup',
getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
catchError(signup)
);
router.post(
'/auth/user/signin',
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin)
);
router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
router.post('/auth/password/forgot', catchError(passwordForgot));
router.post(
'/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post('/auth/token/validate/:tokenId', catchError(tokenValidate));
router.post('/auth/password/reset/:tokenId', catchError(passwordReset));
router.post(
'/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset)
);
router.post('/auth/email/validate/:tokenId', catchError(emailVerification));
router.post(
'/user/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange')
);
router.post('/auth/token/refresh', catchError(refreshToken));
@ -549,20 +567,33 @@ const mapRoutes = (router) => {
);
// deprecated APIs
router.post('/api/v1/db/auth/user/signup', catchError(signup));
router.post('/api/v1/db/auth/user/signin', catchError(signin));
router.post(
'/api/v1/db/auth/user/signup',
getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
catchError(signup)
);
router.post(
'/api/v1/db/auth/user/signin',
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin)
);
router.get(
'/api/v1/db/auth/user/me',
extractProjectIdAndAuthenticate,
catchError(me)
);
router.post('/api/v1/db/auth/password/forgot', catchError(passwordForgot));
router.post(
'/api/v1/db/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post(
'/api/v1/db/auth/token/validate/:tokenId',
catchError(tokenValidate)
);
router.post(
'/api/v1/db/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset)
);
router.post(
@ -571,6 +602,7 @@ const mapRoutes = (router) => {
);
router.post(
'/api/v1/db/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange')
);
router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken));
@ -580,14 +612,26 @@ const mapRoutes = (router) => {
);
// new API
router.post('/api/v1/auth/user/signup', catchError(signup));
router.post('/api/v1/auth/user/signin', catchError(signin));
router.post(
'/api/v1/auth/user/signup',
getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
catchError(signup)
);
router.post(
'/api/v1/auth/user/signin',
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin)
);
router.get(
'/api/v1/auth/user/me',
extractProjectIdAndAuthenticate,
catchError(me)
);
router.post('/api/v1/auth/password/forgot', catchError(passwordForgot));
router.post(
'/api/v1/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post(
'/api/v1/auth/token/validate/:tokenId',
catchError(tokenValidate)
@ -602,6 +646,7 @@ const mapRoutes = (router) => {
);
router.post(
'/api/v1/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange')
);
router.post('/api/v1/auth/token/refresh', catchError(refreshToken));

1
packages/nocodb/src/lib/meta/api/viewApis.ts

@ -89,7 +89,6 @@ async function showAllColumns(req: Request<any, any>, res) {
}
async function hideAllColumns(req: Request<any, any>, res) {
res.json(
await View.hideAllColumns(
req.params.viewId,

384
packages/nocodb/src/lib/meta/helpers/catchError.ts

@ -1,3 +1,371 @@
enum DBError {
TABLE_EXIST = 'TABLE_EXIST',
TABLE_NOT_EXIST = 'TABLE_NOT_EXIST',
COLUMN_EXIST = 'COLUMN_EXIST',
COLUMN_NOT_EXIST = 'COLUMN_NOT_EXIST',
CONSTRAINT_EXIST = 'CONSTRAINT_EXIST',
CONSTRAINT_NOT_EXIST = 'CONSTRAINT_NOT_EXIST',
}
// extract db errors using database error code
function extractDBError(error): {
type: DBError;
message: string;
info: any;
extra?: Record<string, any>;
} | void {
if (!error.code) return;
let message: string;
let extra: Record<string, any>;
let type: DBError;
switch (error.code) {
// sqlite errors
case 'SQLITE_BUSY':
message = 'The database is locked by another process or transaction.';
break;
case 'SQLITE_CONSTRAINT':
{
const constraint = /FOREIGN KEY|UNIQUE/.test(error.message)
? error.message.match(/FOREIGN KEY|UNIQUE/gi)?.join(' ')
: 'constraint';
message = `A ${constraint} constraint was violated: ${error.message}`;
extra = {
constraint,
};
}
break;
case 'SQLITE_CORRUPT':
message = 'The database file is corrupt.';
break;
case 'SQLITE_ERROR':
message = 'A SQL error occurred.';
if (error.message) {
const noSuchTableMatch = error.message.match(/no such table: (\w+)/);
const tableAlreadyExistsMatch = error.message.match(
/SQLITE_ERROR: table `?(\w+)`? already exists/
);
const duplicateColumnExistsMatch = error.message.match(
/SQLITE_ERROR: duplicate column name: (\w+)/
);
const unrecognizedTokenMatch = error.message.match(
/SQLITE_ERROR: unrecognized token: "(\w+)"/
);
const columnDoesNotExistMatch = error.message.match(
/SQLITE_ERROR: no such column: (\w+)/
);
const constraintFailedMatch = error.message.match(
/SQLITE_ERROR: constraint failed: (\w+)/
);
if (noSuchTableMatch && noSuchTableMatch[1]) {
message = `The table '${noSuchTableMatch[1]}' does not exist.`;
type = DBError.TABLE_NOT_EXIST;
extra = {
table: noSuchTableMatch[1],
};
} else if (tableAlreadyExistsMatch && tableAlreadyExistsMatch[1]) {
message = `The table '${tableAlreadyExistsMatch[1]}' already exists.`;
type = DBError.TABLE_EXIST;
extra = {
table: tableAlreadyExistsMatch[1],
};
} else if (unrecognizedTokenMatch && unrecognizedTokenMatch[1]) {
message = `Unrecognized token: ${unrecognizedTokenMatch[1]}`;
extra = {
token: unrecognizedTokenMatch[1],
};
} else if (columnDoesNotExistMatch && columnDoesNotExistMatch[1]) {
message = `The column ${columnDoesNotExistMatch[1]} does not exist.`;
type = DBError.COLUMN_NOT_EXIST;
extra = {
column: columnDoesNotExistMatch[1],
};
} else if (constraintFailedMatch && constraintFailedMatch[1]) {
message = `A constraint failed: ${constraintFailedMatch[1]}`;
} else if (
duplicateColumnExistsMatch &&
duplicateColumnExistsMatch[1]
) {
message = `The column '${duplicateColumnExistsMatch[1]}' already exists.`;
type = DBError.COLUMN_EXIST;
extra = {
column: duplicateColumnExistsMatch[1],
};
} else {
const match = error.message.match(/SQLITE_ERROR:\s*(\w+)/);
if (match && match[1]) {
message = match[1];
}
}
}
break;
case 'SQLITE_RANGE':
message = 'A column index is out of range.';
break;
case 'SQLITE_SCHEMA':
message = 'The database schema has changed.';
break;
// mysql errors
case 'ER_TABLE_EXISTS_ERROR':
message = 'The table already exists.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ Table '?(\w+)'? already exists/i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The table '${extractTableNameMatch[1]}' already exists.`;
type = DBError.TABLE_EXIST;
extra = {
table: extractTableNameMatch[1],
};
}
}
break;
case 'ER_DUP_FIELDNAME':
message = 'The column already exists.';
if (error.message) {
const extractColumnNameMatch = error.message.match(
/ Duplicate column name '(\w+)'/i
);
if (extractColumnNameMatch && extractColumnNameMatch[1]) {
message = `The column '${extractColumnNameMatch[1]}' already exists.`;
type = DBError.COLUMN_EXIST;
extra = {
column: extractColumnNameMatch[1],
};
}
}
break;
case 'ER_NO_SUCH_TABLE':
message = 'The table does not exist.';
if (error.message) {
const missingTableMatch = error.message.match(
/ Table '(?:\w+\.)?(\w+)' doesn't exist/i
);
if (missingTableMatch && missingTableMatch[1]) {
message = `The table '${missingTableMatch[1]}' does not exist`;
type = DBError.TABLE_NOT_EXIST;
extra = {
table: missingTableMatch[1],
};
}
}
break;
case 'ER_DUP_ENTRY':
message = 'This record already exists.';
break;
case 'ER_PARSE_ERROR':
message = 'There was a syntax error in your SQL query.';
break;
case 'ER_NO_DEFAULT_FOR_FIELD':
message = 'A value is required for this field.';
break;
case 'ER_BAD_NULL_ERROR':
message = 'A null value is not allowed for this field.';
break;
case 'ER_DATA_TOO_LONG':
message = 'The data entered is too long for this field.';
break;
case 'ER_BAD_FIELD_ERROR':
{
message = 'The field you are trying to access does not exist.';
const extractColNameMatch = error.message.match(
/ Unknown column '(\w+)' in 'field list'/i
);
if (extractColNameMatch && extractColNameMatch[1]) {
message = `The column '${extractColNameMatch[1]}' does not exist.`;
type = DBError.COLUMN_NOT_EXIST;
extra = {
column: extractColNameMatch[1],
};
}
}
break;
case 'ER_ACCESS_DENIED_ERROR':
message = 'You do not have permission to perform this action.';
break;
case 'ER_LOCK_WAIT_TIMEOUT':
message = 'A timeout occurred while waiting for a table lock.';
break;
case 'ER_NO_REFERENCED_ROW':
message = 'The referenced row does not exist.';
break;
case 'ER_ROW_IS_REFERENCED':
message = 'This record is being referenced by other records.';
break;
// postgres errors
case '23505':
message = 'This record already exists.';
break;
case '42601':
message = 'There was a syntax error in your SQL query.';
break;
case '23502':
message = 'A value is required for this field.';
break;
case '23503':
message = 'The referenced row does not exist.';
break;
case '23514':
message = 'A null value is not allowed for this field.';
break;
case '22001':
message = 'The data entered is too long for this field.';
break;
case '28000':
message = 'You do not have permission to perform this action.';
break;
case '40P01':
message = 'A timeout occurred while waiting for a table lock.';
break;
case '23506':
message = 'This record is being referenced by other records.';
break;
case '42P07':
message = 'The table already exists.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ relation "?(\w+)"? already exists/i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The table '${extractTableNameMatch[1]}' already exists.`;
type = DBError.TABLE_EXIST;
extra = {
table: extractTableNameMatch[1],
};
}
}
break;
case '42701':
message = 'The column already exists.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ column "(\w+)" of relation "(\w+)" already exists/i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The column '${extractTableNameMatch[1]}' already exists.`;
type = DBError.COLUMN_EXIST;
extra = {
column: extractTableNameMatch[1],
};
}
}
break;
case '42P01':
message = 'The table does not exist.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ relation "(\w+)" does not exist/i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The table '${extractTableNameMatch[1]}' does not exist.`;
type = DBError.TABLE_NOT_EXIST;
extra = {
table: extractTableNameMatch[1],
};
}
}
break;
case '42703':
message = 'The column does not exist.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ column "(\w+)" does not exist/i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The column '${extractTableNameMatch[1]}' does not exist.`;
type = DBError.COLUMN_NOT_EXIST;
extra = {
column: extractTableNameMatch[1],
};
}
}
break;
// mssql errors
case 'EREQUEST':
message = 'There was a syntax error in your SQL query.';
if (error.message) {
const extractTableNameMatch = error.message.match(
/ There is already an object named '(\w+)' in the database/i
);
const extractDupColMatch = error.message.match(
/ Column name '(\w+)' in table '(\w+)' is specified more than once/i
);
const extractMissingTableMatch = error.message.match(
/ Invalid object name '(\w+)'./i
);
const extractMissingColMatch = error.message.match(
/ Invalid column name '(\w+)'./i
);
if (extractTableNameMatch && extractTableNameMatch[1]) {
message = `The table '${extractTableNameMatch[1]}' already exists.`;
type = DBError.TABLE_EXIST;
extra = {
table: extractTableNameMatch[1],
};
} else if (extractDupColMatch && extractDupColMatch[1]) {
message = `The column '${extractDupColMatch[1]}' already exists.`;
type = DBError.COLUMN_EXIST;
extra = {
column: extractDupColMatch[1],
};
} else if (extractMissingTableMatch && extractMissingTableMatch[1]) {
message = `The table '${extractMissingTableMatch[1]}' does not exist`;
type = DBError.TABLE_NOT_EXIST;
extra = {
table: extractMissingTableMatch[1],
};
} else if (extractMissingColMatch && extractMissingColMatch[1]) {
message = `The column '${extractMissingColMatch[1]}' does not exist`;
type = DBError.COLUMN_NOT_EXIST;
extra = {
column: extractMissingColMatch[1],
};
}
}
break;
case 'ELOGIN':
message = 'You do not have permission to perform this action.';
break;
case 'ETIMEOUT':
message = 'A timeout occurred while waiting for a table lock.';
break;
case 'ECONNRESET':
message = 'The connection was reset.';
break;
case 'ECONNREFUSED':
message = 'The connection was refused.';
break;
case 'EHOSTUNREACH':
message = 'The host is unreachable.';
break;
case 'EHOSTDOWN':
message = 'The host is down.';
break;
}
if (message) {
return {
message,
type,
extra,
info: { message: error.message, code: error.code },
};
}
}
export default function (
requestHandler: (req: any, res: any, next?: any) => any
) {
@ -8,6 +376,12 @@ export default function (
// todo: error log
console.log(requestHandler.name ? `${requestHandler.name} ::` : '', e);
const dbError = extractDBError(e);
if (dbError) {
return res.status(400).json(dbError);
}
if (e instanceof BadRequest) {
return res.status(400).json({ msg: e.message });
} else if (e instanceof Unauthorized) {
@ -27,28 +401,38 @@ export default function (
}
class BadRequest extends Error {}
class Unauthorized extends Error {}
class Forbidden extends Error {}
class NotFound extends Error {}
class InternalServerError extends Error {}
class NotImplemented extends Error {}
export class NcError {
static notFound(message = 'Not found') {
throw new NotFound(message);
}
static badRequest(message) {
throw new BadRequest(message);
}
static unauthorized(message) {
throw new Unauthorized(message);
}
static forbidden(message) {
throw new Forbidden(message);
}
static internalServerError(message = 'Internal server error') {
throw new InternalServerError(message);
}
static notImplemented(message = 'Not implemented') {
throw new NotImplemented(message);
}

6
packages/nocodb/src/lib/models/Base.ts

@ -7,7 +7,7 @@ import {
MetaTable,
} from '../utils/globals';
import Model from './Model';
import { BaseType, UITypes } from 'nocodb-sdk';
import { BaseType, BoolType, UITypes } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache';
import CryptoJS from 'crypto-js';
import { extractProps } from '../meta/helpers/extractProps';
@ -20,14 +20,14 @@ export default class Base implements BaseType {
project_id?: string;
alias?: string;
type?: string;
is_meta?: boolean;
is_meta?: BoolType;
config?: any;
created_at?: any;
updated_at?: any;
inflection_column?: string;
inflection_table?: string;
order?: number;
enabled?: boolean;
enabled?: BoolType;
constructor(base: Partial<Base>) {
Object.assign(this, base);

4
packages/nocodb/src/lib/models/Filter.ts

@ -9,7 +9,7 @@ import {
MetaTable,
} from '../utils/globals';
import View from './View';
import { FilterType, UITypes } from 'nocodb-sdk';
import { BoolType, FilterType, UITypes } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache';
import { NcError } from '../meta/helpers/catchError';
import { extractProps } from '../meta/helpers/extractProps';
@ -55,7 +55,7 @@ export default class Filter {
value?: string;
logical_op?: string;
is_group?: boolean;
is_group?: BoolType;
children?: Filter[];
project_id?: string;
base_id?: string;

3
packages/nocodb/src/lib/models/LinkToAnotherRecordColumn.ts

@ -1,3 +1,4 @@
import { BoolType } from 'nocodb-sdk';
import Noco from '../Noco';
import Column from './Column';
import Model from './Model';
@ -19,7 +20,7 @@ export default class LinkToAnotherRecordColumn {
fk_index_name?: string;
type: 'hm' | 'bt' | 'mm';
virtual = false;
virtual: BoolType = false;
mmModel?: Model;
relatedTable?: Model;

1
packages/nocodb/src/lib/models/MapView.ts

@ -93,7 +93,6 @@ export default class MapView implements MapType {
await View.updateColumn(body.fk_view_id, mapViewMappedByColumn.id, {
show: true,
});
}
// update meta

15
packages/nocodb/src/lib/models/Model.ts

@ -7,6 +7,7 @@ import { BaseModelSqlv2 } from '../db/sql-data-mapper/lib/sql/BaseModelSqlv2';
import {
isVirtualCol,
ModelTypes,
BoolType,
TableReqType,
TableType,
UITypes,
@ -25,17 +26,17 @@ import { sanitize } from '../db/sql-data-mapper/lib/sql/helpers/sanitize';
import { extractProps } from '../meta/helpers/extractProps';
export default class Model implements TableType {
copy_enabled: boolean;
copy_enabled: BoolType;
created_at: Date | number | string;
base_id: 'db' | string;
deleted: boolean;
enabled: boolean;
export_enabled: boolean;
deleted: BoolType;
enabled: BoolType;
export_enabled: BoolType;
id: string;
order: number;
parent_id: string;
password: string;
pin: boolean;
pin: BoolType;
project_id: string;
schema: any;
show_all_fields: boolean;
@ -46,7 +47,7 @@ export default class Model implements TableType {
table_name: string;
title: string;
mm: boolean;
mm: BoolType;
uuid: string;
@ -98,7 +99,7 @@ export default class Model implements TableType {
projectId,
baseId,
model: Partial<TableReqType> & {
mm?: boolean;
mm?: BoolType;
created_at?: any;
updated_at?: any;
},

6
packages/nocodb/src/lib/models/Project.ts

@ -1,6 +1,6 @@
import Base from './/Base';
import Noco from '../Noco';
import { ProjectType } from 'nocodb-sdk';
import { BoolType, MetaType, ProjectType } from 'nocodb-sdk';
import {
CacheDelDirection,
CacheGetType,
@ -16,9 +16,9 @@ export default class Project implements ProjectType {
public prefix: string;
public status: string;
public description: string;
public meta: string;
public meta: MetaType;
public color: string;
public deleted: string;
public deleted: BoolType;
public order: number;
public is_meta = false;
public bases?: Base[];

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

@ -42,7 +42,6 @@ export enum MetaTable {
MAP_VIEW = 'nc_map_view_v2',
MAP_VIEW_COLUMNS = 'nc_map_view_columns_v2',
STORE = 'nc_store',
}
export const orderedMetaTables = [

1218
scripts/sdk/swagger.json → packages/nocodb/src/schema/swagger.json

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save