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. 4
      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. 2
      packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts
  34. 3
      packages/nocodb/src/lib/meta/api/sharedBaseApis.ts
  35. 3
      packages/nocodb/src/lib/meta/api/sortApis.ts
  36. 13
      packages/nocodb/src/lib/meta/api/tableApis.ts
  37. 65
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  38. 1
      packages/nocodb/src/lib/meta/api/viewApis.ts
  39. 384
      packages/nocodb/src/lib/meta/helpers/catchError.ts
  40. 6
      packages/nocodb/src/lib/models/Base.ts
  41. 4
      packages/nocodb/src/lib/models/Filter.ts
  42. 3
      packages/nocodb/src/lib/models/LinkToAnotherRecordColumn.ts
  43. 1
      packages/nocodb/src/lib/models/MapView.ts
  44. 15
      packages/nocodb/src/lib/models/Model.ts
  45. 6
      packages/nocodb/src/lib/models/Project.ts
  46. 1
      packages/nocodb/src/lib/utils/globals.ts
  47. 1220
      packages/nocodb/src/schema/swagger.json

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

@ -4,7 +4,7 @@ on:
push: push:
branches: [ master ] branches: [ master ]
paths: paths:
- "scripts/sdk/swagger.json" - "packages/nocodb/src/schema/swagger.json"
release: release:
types: [ published ] types: [ published ]
@ -22,7 +22,7 @@ jobs:
env: env:
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }} API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
with: with:
source_file: 'scripts/sdk/swagger.json' source_file: 'packages/nocodb/src/schema/swagger.json'
destination_repo: 'nocodb/noco-apis-doc' destination_repo: 'nocodb/noco-apis-doc'
destination_folder: 'src' destination_folder: 'src'
user_email: 'oof1lab@gmail.com' user_email: 'oof1lab@gmail.com'
@ -34,7 +34,7 @@ jobs:
env: env:
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }} API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
with: with:
source_file: 'scripts/sdk/swagger.json' source_file: 'packages/nocodb/src/schema/swagger.json'
destination_repo: 'nocodb/noco-apis-doc' destination_repo: 'nocodb/noco-apis-doc'
destination_folder: 'meta-src' destination_folder: 'meta-src'
user_email: 'oof1lab@gmail.com' 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'] NcIconsRowHeightMedium: typeof import('~icons/nc-icons/row-height-medium')['default']
NcIconsRowHeightShort: typeof import('~icons/nc-icons/row-height-short')['default'] NcIconsRowHeightShort: typeof import('~icons/nc-icons/row-height-short')['default']
NcIconsRowHeightTall: typeof import('~icons/nc-icons/row-height-tall')['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'] 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'] 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'] RiLineHeight: typeof import('~icons/ri/line-height')['default']
RiTeamFill: typeof import('~icons/ri/team-fill')['default'] RiTeamFill: typeof import('~icons/ri/team-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink'] 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 }) { export async function extractSdkResponseErrorMsg(e: Error & { response: any }) {
if (!e || !e.response) return e.message if (!e || !e.response) return e.message
let msg let msg
let errors: any[] | null = null
if (e.response.data instanceof Blob) { if (e.response.data instanceof Blob) {
try { 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 { } catch {
msg = 'Some internal error occurred' msg = 'Some internal error occurred'
} }
} else { } else {
msg = e.response.data.msg || e.response.data.message || 'Some internal error occurred' 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' return msg || 'Some error occurred'
} }

4
packages/nocodb-sdk/package.json

@ -31,7 +31,7 @@
"test:prettier": "prettier \"src/**/*.ts\" --list-different", "test:prettier": "prettier \"src/**/*.ts\" --list-different",
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"", "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
"watch:build": "tsc -p tsconfig.json -w", "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": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -63,4 +63,4 @@
"prettier": { "prettier": {
"singleQuote": true "singleQuote": true
} }
} }

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", "@graphql-tools/merge": "^6.0.12",
"@sentry/node": "^6.3.5", "@sentry/node": "^6.3.5",
"airtable": "^0.11.3", "airtable": "^0.11.3",
"ajv": "^8.12.0",
"archiver": "^5.0.2", "archiver": "^5.0.2",
"auto-bind": "^4.0.0", "auto-bind": "^4.0.0",
"aws-sdk": "^2.829.0", "aws-sdk": "^2.829.0",
@ -658,6 +659,22 @@
"node": "^10.12.0 || >=12.0.0" "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": { "node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "13.15.0", "version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@ -682,6 +699,12 @@
"node": ">= 4" "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": { "node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@ -2215,13 +2238,13 @@
"integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==" "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q=="
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^1.0.0",
"json-schema-traverse": "^0.4.1", "require-from-string": "^2.0.2",
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
}, },
"funding": { "funding": {
@ -2238,15 +2261,6 @@
"ajv": ">=5.0.0" "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": { "node_modules/amdefine": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@ -5957,6 +5971,22 @@
"@babel/highlight": "^7.10.4" "@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": { "node_modules/eslint/node_modules/ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -6042,6 +6072,12 @@
"node": ">= 4" "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": { "node_modules/eslint/node_modules/strip-ansi": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -8002,6 +8038,26 @@
"node": ">=6" "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": { "node_modules/has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -9465,9 +9521,9 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}, },
"node_modules/json-stable-stringify-without-jsonify": { "node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
@ -13898,7 +13954,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "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==", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -14179,6 +14234,37 @@
"url": "https://opencollective.com/webpack" "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": { "node_modules/scmp": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
@ -15573,22 +15659,6 @@
"node": ">=10.0.0" "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": { "node_modules/table/node_modules/ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -15598,12 +15668,6 @@
"node": ">=8" "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": { "node_modules/table/node_modules/slice-ansi": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@ -18168,6 +18232,31 @@
"node": ">=0.4.0" "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": { "node_modules/webpack/node_modules/braces": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@ -18303,6 +18392,12 @@
"node": ">=0.10.0" "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": { "node_modules/webpack/node_modules/json5": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
@ -19500,6 +19595,18 @@
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
}, },
"dependencies": { "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": { "globals": {
"version": "13.15.0", "version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@ -19515,6 +19622,12 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "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": { "type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@ -20789,13 +20902,13 @@
} }
}, },
"ajv": { "ajv": {
"version": "6.12.6", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^1.0.0",
"json-schema-traverse": "^0.4.1", "require-from-string": "^2.0.2",
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
@ -20806,13 +20919,6 @@
"dev": true, "dev": true,
"requires": {} "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": { "amdefine": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@ -23538,6 +23644,18 @@
"@babel/highlight": "^7.10.4" "@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": { "ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "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==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "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": { "strip-ansi": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@ -25352,6 +25476,24 @@
"requires": { "requires": {
"ajv": "^6.12.3", "ajv": "^6.12.3",
"har-schema": "^2.0.0" "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": { "has": {
@ -26420,9 +26562,9 @@
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
}, },
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}, },
"json-stable-stringify-without-jsonify": { "json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
@ -29930,8 +30072,7 @@
"require-from-string": { "require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "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==", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
"dev": true
}, },
"require-main-filename": { "require-main-filename": {
"version": "2.0.0", "version": "2.0.0",
@ -30138,6 +30279,33 @@
"@types/json-schema": "^7.0.8", "@types/json-schema": "^7.0.8",
"ajv": "^6.12.5", "ajv": "^6.12.5",
"ajv-keywords": "^3.5.2" "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": { "scmp": {
@ -31273,30 +31441,12 @@
"strip-ansi": "^6.0.1" "strip-ansi": "^6.0.1"
}, },
"dependencies": { "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": { "ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true "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": { "slice-ansi": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
@ -32978,6 +33128,25 @@
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true "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": { "braces": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "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": { "json5": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "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", "@graphql-tools/merge": "^6.0.12",
"@sentry/node": "^6.3.5", "@sentry/node": "^6.3.5",
"airtable": "^0.11.3", "airtable": "^0.11.3",
"ajv": "^8.12.0",
"archiver": "^5.0.2", "archiver": "^5.0.2",
"auto-bind": "^4.0.0", "auto-bind": "^4.0.0",
"aws-sdk": "^2.829.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 { Knex, knex } from 'knex';
import { SnowflakeClient } from 'nc-help'; import { SnowflakeClient } from 'nc-help';
import { FilterType } from 'nocodb-sdk';
const types = require('pg').types; const types = require('pg').types;
// override parsing date column to Date() // override parsing date column to Date()
@ -1248,7 +1249,13 @@ knex.QueryBuilder.extend('conditionv2', function (conditionObj: Filter) {
return parseConditionv2(conditionObj, this); return parseConditionv2(conditionObj, this);
} as any); } 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) { if (obj.is_group) {
qb = qb.where(function () { qb = qb.where(function () {
const children = obj.children; 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), ...(await obj),
[col.title]: [col.title]:
allowedCols && (!includePkByDefault || !col.pk) allowedCols && (!includePkByDefault || !col.pk)
? (allowedCols[col.id] && ? allowedCols[col.id] &&
(!isSystemColumn(col) || view.show_system_fields) && (!isSystemColumn(col) || view.show_system_fields) &&
(!fields?.length || fields.includes(col.title)) && (!fields?.length || fields.includes(col.title)) &&
value) value
: (fields?.length : fields?.length
? fields.includes(col.title) && value ? fields.includes(col.title) && value
: value), : value,
}; };
}, Promise.resolve({})); }, 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import ApiToken from '../../models/ApiToken'; import ApiToken from '../../models/ApiToken';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function apiTokenList(req: Request, res: Response) { export async function apiTokenList(req: Request, res: Response) {
res.json(await ApiToken.list(req['user'].id)); res.json(await ApiToken.list(req['user'].id));
@ -40,6 +41,7 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/api-tokens', '/api/v1/db/meta/projects/:projectId/api-tokens',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate') ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
); );
router.delete( 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import DOMPurify from 'isomorphic-dompurify'; import DOMPurify from 'isomorphic-dompurify';
import { getAjvValidatorMw } from './helpers';
export async function commentRow(req: Request<any, any>, res) { export async function commentRow(req: Request<any, any>, res) {
res.json( res.json(
@ -69,10 +70,12 @@ router.get(
); );
router.post( router.post(
'/api/v1/db/meta/audits/comments', '/api/v1/db/meta/audits/comments',
getAjvValidatorMw('swagger.json#/components/schemas/CommentReq'),
ncMetaAclMw(commentRow, 'commentRow') ncMetaAclMw(commentRow, 'commentRow')
); );
router.post( router.post(
'/api/v1/db/meta/audits/rows/:rowId/update', '/api/v1/db/meta/audits/rows/:rowId/update',
getAjvValidatorMw('swagger.json#/components/schemas/AuditRowUpdateReq'),
ncMetaAclMw(auditRowUpdate, 'auditRowUpdate') ncMetaAclMw(auditRowUpdate, 'auditRowUpdate')
); );
router.get( 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help'; import { Tele } from 'nc-help';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { populateMeta } from './helpers'; import { getAjvValidatorMw, populateMeta } from './helpers';
export async function baseGet( export async function baseGet(
req: Request<any, any, any>, req: Request<any, any, any>,
@ -107,6 +107,7 @@ export default (router) => {
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/bases/:baseId', '/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseUpdate, 'baseUpdate') ncMetaAclMw(baseUpdate, 'baseUpdate')
); );
router.delete( router.delete(
@ -117,6 +118,7 @@ export default (router) => {
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/bases', '/api/v1/db/meta/projects/:projectId/bases',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseCreate, 'baseCreate') ncMetaAclMw(baseCreate, 'baseCreate')
); );
router.get( 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 { import {
createHmAndBtColumn, createHmAndBtColumn,
generateFkName, generateFkName,
getAjvValidatorMw,
randomID, randomID,
validateLookupPayload, validateLookupPayload,
validateRequiredField, validateRequiredField,
@ -1785,6 +1786,7 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/columns/', '/api/v1/db/meta/tables/:tableId/columns/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ColumnReq'),
ncMetaAclMw(columnAdd, 'columnAdd') 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 Filter from '../../models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore // @ts-ignore
export async function filterGet(req: Request, res: Response, next) { export async function filterGet(req: Request, res: Response, next) {
@ -135,6 +136,7 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/views/:viewId/filters', '/api/v1/db/meta/views/:viewId/filters',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate') ncMetaAclMw(filterCreate, 'filterCreate')
); );
@ -145,6 +147,7 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/hooks/:hookId/filters', '/api/v1/db/meta/hooks/:hookId/filters',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(hookFilterCreate, 'filterCreate') ncMetaAclMw(hookFilterCreate, 'filterCreate')
); );
@ -156,6 +159,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/filters/:filterId', '/api/v1/db/meta/filters/:filterId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate') ncMetaAclMw(filterUpdate, 'filterUpdate')
); );
router.delete( 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 FormView from '../../models/FormView';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore // @ts-ignore
export async function formViewGet(req: Request, res: Response<FormType>) { export async function formViewGet(req: Request, res: Response<FormType>) {
@ -43,6 +44,7 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/forms', '/api/v1/db/meta/tables/:tableId/forms',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormCreateReq'),
ncMetaAclMw(formViewCreate, 'formViewCreate') ncMetaAclMw(formViewCreate, 'formViewCreate')
); );
router.get( router.get(
@ -53,6 +55,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/forms/:formViewId', '/api/v1/db/meta/forms/:formViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormReq'),
ncMetaAclMw(formViewUpdate, 'formViewUpdate') ncMetaAclMw(formViewUpdate, 'formViewUpdate')
); );
router.delete( 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 { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function columnUpdate(req: Request, res: Response) { export async function columnUpdate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'formViewColumn:updated' }); Tele.emit('evt', { evt_type: 'formViewColumn:updated' });
@ -13,6 +14,7 @@ const router = Router({ mergeParams: true });
router.patch( router.patch(
'/api/v1/db/meta/form-columns/:formViewColumnId', '/api/v1/db/meta/form-columns/:formViewColumnId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormColumnReq'),
ncMetaAclMw(columnUpdate, 'columnUpdate') ncMetaAclMw(columnUpdate, 'columnUpdate')
); );
export default router; 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 { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function galleryViewGet(req: Request, res: Response<GalleryType>) { export async function galleryViewGet(req: Request, res: Response<GalleryType>) {
res.json(await GalleryView.get(req.params.galleryViewId)); res.json(await GalleryView.get(req.params.galleryViewId));
} }
@ -29,11 +30,13 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/galleries', '/api/v1/db/meta/tables/:tableId/galleries',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewCreate, 'galleryViewCreate') ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
); );
router.patch( router.patch(
'/api/v1/db/meta/galleries/:galleryViewId', '/api/v1/db/meta/galleries/:galleryViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate') ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
); );
router.get( 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import GridView from '../../models/GridView'; import GridView from '../../models/GridView';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore // @ts-ignore
export async function gridViewCreate(req: Request<any, any>, res) { export async function gridViewCreate(req: Request<any, any>, res) {
@ -35,6 +36,7 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/grids/', '/api/v1/db/meta/tables/:tableId/grids/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridReq'),
ncMetaAclMw(gridViewCreate, 'gridViewCreate') ncMetaAclMw(gridViewCreate, 'gridViewCreate')
); );
router.patch( 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 { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function columnList(req: Request, res: Response) { export async function columnList(req: Request, res: Response) {
res.json(await GridViewColumn.list(req.params.gridViewId)); res.json(await GridViewColumn.list(req.params.gridViewId));
@ -22,6 +23,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/grid-columns/:gridViewColumnId', '/api/v1/db/meta/grid-columns/:gridViewColumnId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridColumnReq'),
ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate') ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate')
); );
export default router; 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) { export function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3); const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return seconds; 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, ColumnReqType,
LinkToAnotherRecordType, LinkToAnotherRecordType,
LookupColumnReqType, LookupColumnReqType,
BoolType,
RelationTypes, RelationTypes,
RollupColumnReqType, RollupColumnReqType,
TableType, TableType,
@ -27,7 +28,7 @@ export async function createHmAndBtColumn(
type?: RelationTypes, type?: RelationTypes,
alias?: string, alias?: string,
fkColName?: string, fkColName?: string,
virtual = false, virtual: BoolType = false,
isSystemCol = false isSystemCol = false
) { ) {
// save bt column // 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 populateSamplePayload from '../helpers/populateSamplePayload';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function hookList( export async function hookList(
req: Request<any, any, any>, req: Request<any, any, any>,
@ -85,11 +86,13 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/hooks/test', '/api/v1/db/meta/tables/:tableId/hooks/test',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookTestReq'),
ncMetaAclMw(hookTest, 'hookTest') ncMetaAclMw(hookTest, 'hookTest')
); );
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/hooks', '/api/v1/db/meta/tables/:tableId/hooks',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookCreate, 'hookCreate') ncMetaAclMw(hookCreate, 'hookCreate')
); );
router.delete( router.delete(
@ -100,6 +103,7 @@ router.delete(
router.patch( router.patch(
'/api/v1/db/meta/hooks/:hookId', '/api/v1/db/meta/hooks/:hookId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookUpdate, 'hookUpdate') ncMetaAclMw(hookUpdate, 'hookUpdate')
); );
router.get( 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 Filter from '../../models/Filter';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore // @ts-ignore
export async function filterGet(req: Request, res: Response, next) { export async function filterGet(req: Request, res: Response, next) {
@ -117,6 +118,7 @@ router.get(
router.post( router.post(
'/hooks/:hookId/filters/', '/hooks/:hookId/filters/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate') ncMetaAclMw(filterCreate, 'filterCreate')
); );
router.get( router.get(
@ -127,6 +129,7 @@ router.get(
router.patch( router.patch(
'/hooks/:hookId/filters/:filterId', '/hooks/:hookId/filters/:filterId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate') ncMetaAclMw(filterUpdate, 'filterUpdate')
); );
router.delete( 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 { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function kanbanViewGet(req: Request, res: Response<KanbanType>) { export async function kanbanViewGet(req: Request, res: Response<KanbanType>) {
res.json(await KanbanView.get(req.params.kanbanViewId)); res.json(await KanbanView.get(req.params.kanbanViewId));
@ -31,11 +32,13 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/kanbans', '/api/v1/db/meta/tables/:tableId/kanbans',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanReq'),
ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate') ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate')
); );
router.patch( router.patch(
'/api/v1/db/meta/kanbans/:kanbanViewId', '/api/v1/db/meta/kanbans/:kanbanViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanUpdateReq'),
ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate') ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate')
); );
router.get( 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 }); const router = Router({ mergeParams: true });
// todo: add schema in swagger and use getAjvValidatorMw
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/maps', '/api/v1/db/meta/tables/:tableId/maps',
metaApiMetrics, metaApiMetrics,

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

@ -4,6 +4,7 @@ import { Router } from 'express';
import { Tele } from 'nc-help'; import { Tele } from 'nc-help';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
async function xcVisibilityMetaSetAll(req, res) { async function xcVisibilityMetaSetAll(req, res) {
Tele.emit('evt', { evt_type: 'uiAcl:updated' }); Tele.emit('evt', { evt_type: 'uiAcl:updated' });
for (const d of req.body) { for (const d of req.body) {
@ -122,6 +123,7 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/visibility-rules', '/api/v1/db/meta/projects/:projectId/visibility-rules',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/VisibilityRuleReq'),
ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet') ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
); );
export default router; 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 Noco from '../../Noco';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { getAjvValidatorMw } from './helpers';
async function licenseGet(_req, res) { async function licenseGet(_req, res) {
const license = await Store.get(NC_LICENSE_KEY); const license = await Store.get(NC_LICENSE_KEY);
@ -30,6 +31,7 @@ router.get(
router.post( router.post(
'/api/v1/license', '/api/v1/license',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/LicenseReq'),
ncMetaAclMw(licenseSet, 'licenseSet', { ncMetaAclMw(licenseSet, 'licenseSet', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { apiTokenListEE } from './ee/orgTokenApis'; import { apiTokenListEE } from './ee/orgTokenApis';
import { getAjvValidatorMw } from './helpers';
async function apiTokenList(req, res) { async function apiTokenList(req, res) {
const fk_user_id = req.user.id; const fk_user_id = req.user.id;
@ -65,6 +66,7 @@ router.get(
router.post( router.post(
'/api/v1/tokens', '/api/v1/tokens',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', { ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', {
// allowedRoles: [OrgUserRoles.SUPER], // allowedRoles: [OrgUserRoles.SUPER],
blockApiTokenAccess: true, 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 ncMetaAclMw from '../helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { randomTokenString } from '../helpers/stringHelpers'; import { randomTokenString } from '../helpers/stringHelpers';
import { getAjvValidatorMw } from './helpers';
import { sendInviteEmail } from './projectUserApis'; import { sendInviteEmail } from './projectUserApis';
async function userList(req, res) { async function userList(req, res) {
@ -266,6 +267,7 @@ router.get(
router.patch( router.patch(
'/api/v1/users/:userId', '/api/v1/users/:userId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userUpdate, 'userUpdate', { ncMetaAclMw(userUpdate, 'userUpdate', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, blockApiTokenAccess: true,
@ -282,6 +284,7 @@ router.delete(
router.post( router.post(
'/api/v1/users', '/api/v1/users',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userAdd, 'userAdd', { ncMetaAclMw(userAdd, 'userAdd', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, 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 NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
export async function pluginList(_req: Request, res: Response) { export async function pluginList(_req: Request, res: Response) {
res.json(new PagedResponseImpl(await Plugin.list())); res.json(new PagedResponseImpl(await Plugin.list()));
@ -43,6 +44,8 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/plugins/test', '/api/v1/db/meta/plugins/test',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginTestReq'),
ncMetaAclMw(pluginTest, 'pluginTest') ncMetaAclMw(pluginTest, 'pluginTest')
); );
router.get( router.get(
@ -53,6 +56,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/plugins/:pluginId', '/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginReq'),
ncMetaAclMw(pluginUpdate, 'pluginUpdate') ncMetaAclMw(pluginUpdate, 'pluginUpdate')
); );
router.get( 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 { extractPropsAndSanitize } from '../helpers/extractProps';
import NcConfigFactory from '../../utils/NcConfigFactory'; import NcConfigFactory from '../../utils/NcConfigFactory';
import { promisify } from 'util'; import { promisify } from 'util';
import { populateMeta } from './helpers'; import { getAjvValidatorMw, populateMeta } from './helpers';
import Filter from '../../models/Filter'; import Filter from '../../models/Filter';
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
@ -272,6 +272,7 @@ export default (router) => {
router.post( router.post(
'/api/v1/db/meta/projects', '/api/v1/db/meta/projects',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectReq'),
ncMetaAclMw(projectCreate, 'projectCreate') ncMetaAclMw(projectCreate, 'projectCreate')
); );
router.get( 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 { PluginCategory } from 'nocodb-sdk';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { randomTokenString } from '../helpers/stringHelpers'; import { randomTokenString } from '../helpers/stringHelpers';
import { getAjvValidatorMw } from './helpers';
async function userList(req, res) { async function userList(req, res) {
res.json({ res.json({
@ -310,11 +311,13 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/users', '/api/v1/db/meta/projects/:projectId/users',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(userInvite, 'userInvite') ncMetaAclMw(userInvite, 'userInvite')
); );
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/users/:userId', '/api/v1/db/meta/projects/:projectId/users/:userId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(projectUserUpdate, 'projectUserUpdate') ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
); );
router.delete( router.delete(

2
packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts

@ -28,7 +28,7 @@ export async function dataList(req: Request, res: Response) {
if ( if (
view.type !== ViewTypes.GRID && view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN && view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY && view.type !== ViewTypes.GALLERY &&
view.type !== ViewTypes.MAP view.type !== ViewTypes.MAP
) { ) {
NcError.notFound('Not found'); NcError.notFound('Not found');

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 { v4 as uuidv4 } from 'uuid';
import Project from '../../models/Project'; import Project from '../../models/Project';
import { NcError } from '../helpers/catchError'; import { NcError } from '../helpers/catchError';
import { getAjvValidatorMw } from './helpers';
// todo: load from config // todo: load from config
const config = { const config = {
dashboardPath: '/nc', dashboardPath: '/nc',
@ -96,10 +97,12 @@ router.get(
); );
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/shared', '/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink') ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink')
); );
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/shared', '/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink') ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink')
); );
router.delete( 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 Sort from '../../models/Sort';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { getAjvValidatorMw } from './helpers';
// @ts-ignore // @ts-ignore
export async function sortGet(req: Request, res: Response<TableType>) {} export async function sortGet(req: Request, res: Response<TableType>) {}
@ -58,6 +59,7 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/views/:viewId/sorts/', '/api/v1/db/meta/views/:viewId/sorts/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortCreate, 'sortCreate') ncMetaAclMw(sortCreate, 'sortCreate')
); );
router.get( router.get(
@ -68,6 +70,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/sorts/:sortId', '/api/v1/db/meta/sorts/:sortId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortUpdate, 'sortUpdate') ncMetaAclMw(sortUpdate, 'sortUpdate')
); );
router.delete( router.delete(

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

@ -8,6 +8,7 @@ import {
AuditOperationTypes, AuditOperationTypes,
isVirtualCol, isVirtualCol,
ModelTypes, ModelTypes,
NormalColumnRequestType,
TableListType, TableListType,
TableReqType, TableReqType,
TableType, TableType,
@ -17,6 +18,7 @@ import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
import Project from '../../models/Project'; import Project from '../../models/Project';
import Audit from '../../models/Audit'; import Audit from '../../models/Audit';
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { getAjvValidatorMw } from './helpers';
import { xcVisibilityMetaGet } from './modelVisibilityApis'; import { xcVisibilityMetaGet } from './modelVisibilityApis';
import View from '../../models/View'; import View from '../../models/View';
import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT'; import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
@ -216,10 +218,13 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
columns: columns.map((c, i) => { columns: columns.map((c, i) => {
const colMetaFromReq = req.body?.columns?.find( const colMetaFromReq = req.body?.columns?.find(
(c1) => c.cn === c1.column_name (c1) => c.cn === c1.column_name
); ) as NormalColumnRequestType;
return { return {
...colMetaFromReq, ...colMetaFromReq,
uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c), uidt:
(colMetaFromReq?.uidt as string) ||
c.uidt ||
getColumnUiType(base, c),
...c, ...c,
dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes( dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
colMetaFromReq.uidt as any 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), title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),
column_name: c.cn, column_name: c.cn,
order: i + 1, order: i + 1,
}; } as NormalColumnRequestType;
}), }),
order: +(tables?.pop()?.order ?? 0) + 1, order: +(tables?.pop()?.order ?? 0) + 1,
}) })
@ -410,11 +415,13 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/tables', '/api/v1/db/meta/projects/:projectId/tables',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate') ncMetaAclMw(tableCreate, 'tableCreate')
); );
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/:baseId/tables', '/api/v1/db/meta/projects/:projectId/:baseId/tables',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate') ncMetaAclMw(tableCreate, 'tableCreate')
); );
router.get( 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 ncMetaAclMw from '../../helpers/ncMetaAclMw';
import { MetaTable } from '../../../utils/globals'; import { MetaTable } from '../../../utils/globals';
import Noco from '../../../Noco'; import Noco from '../../../Noco';
import { getAjvValidatorMw } from '../helpers';
import { genJwt } from './helpers'; import { genJwt } from './helpers';
import { randomTokenString } from '../../helpers/stringHelpers'; import { randomTokenString } from '../../helpers/stringHelpers';
@ -523,15 +524,32 @@ async function renderPasswordReset(req, res): Promise<any> {
const mapRoutes = (router) => { const mapRoutes = (router) => {
// todo: old api - /auth/signup?tool=1 // todo: old api - /auth/signup?tool=1
router.post('/auth/user/signup', catchError(signup)); router.post(
router.post('/auth/user/signin', catchError(signin)); '/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.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/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('/auth/email/validate/:tokenId', catchError(emailVerification));
router.post( router.post(
'/user/password/change', '/user/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/auth/token/refresh', catchError(refreshToken)); router.post('/auth/token/refresh', catchError(refreshToken));
@ -549,20 +567,33 @@ const mapRoutes = (router) => {
); );
// deprecated APIs // deprecated APIs
router.post('/api/v1/db/auth/user/signup', catchError(signup)); router.post(
router.post('/api/v1/db/auth/user/signin', catchError(signin)); '/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( router.get(
'/api/v1/db/auth/user/me', '/api/v1/db/auth/user/me',
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) 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( router.post(
'/api/v1/db/auth/token/validate/:tokenId', '/api/v1/db/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
); );
router.post( router.post(
'/api/v1/db/auth/password/reset/:tokenId', '/api/v1/db/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset) catchError(passwordReset)
); );
router.post( router.post(
@ -571,6 +602,7 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/db/auth/password/change', '/api/v1/db/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken)); router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken));
@ -580,14 +612,26 @@ const mapRoutes = (router) => {
); );
// new API // new API
router.post('/api/v1/auth/user/signup', catchError(signup)); router.post(
router.post('/api/v1/auth/user/signin', catchError(signin)); '/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( router.get(
'/api/v1/auth/user/me', '/api/v1/auth/user/me',
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) 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( router.post(
'/api/v1/auth/token/validate/:tokenId', '/api/v1/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
@ -602,6 +646,7 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/auth/password/change', '/api/v1/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/auth/token/refresh', catchError(refreshToken)); 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) { async function hideAllColumns(req: Request<any, any>, res) {
res.json( res.json(
await View.hideAllColumns( await View.hideAllColumns(
req.params.viewId, 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 ( export default function (
requestHandler: (req: any, res: any, next?: any) => any requestHandler: (req: any, res: any, next?: any) => any
) { ) {
@ -8,6 +376,12 @@ export default function (
// todo: error log // todo: error log
console.log(requestHandler.name ? `${requestHandler.name} ::` : '', e); console.log(requestHandler.name ? `${requestHandler.name} ::` : '', e);
const dbError = extractDBError(e);
if (dbError) {
return res.status(400).json(dbError);
}
if (e instanceof BadRequest) { if (e instanceof BadRequest) {
return res.status(400).json({ msg: e.message }); return res.status(400).json({ msg: e.message });
} else if (e instanceof Unauthorized) { } else if (e instanceof Unauthorized) {
@ -27,28 +401,38 @@ export default function (
} }
class BadRequest extends Error {} class BadRequest extends Error {}
class Unauthorized extends Error {} class Unauthorized extends Error {}
class Forbidden extends Error {} class Forbidden extends Error {}
class NotFound extends Error {} class NotFound extends Error {}
class InternalServerError extends Error {} class InternalServerError extends Error {}
class NotImplemented extends Error {} class NotImplemented extends Error {}
export class NcError { export class NcError {
static notFound(message = 'Not found') { static notFound(message = 'Not found') {
throw new NotFound(message); throw new NotFound(message);
} }
static badRequest(message) { static badRequest(message) {
throw new BadRequest(message); throw new BadRequest(message);
} }
static unauthorized(message) { static unauthorized(message) {
throw new Unauthorized(message); throw new Unauthorized(message);
} }
static forbidden(message) { static forbidden(message) {
throw new Forbidden(message); throw new Forbidden(message);
} }
static internalServerError(message = 'Internal server error') { static internalServerError(message = 'Internal server error') {
throw new InternalServerError(message); throw new InternalServerError(message);
} }
static notImplemented(message = 'Not implemented') { static notImplemented(message = 'Not implemented') {
throw new NotImplemented(message); throw new NotImplemented(message);
} }

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

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

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

@ -9,7 +9,7 @@ import {
MetaTable, MetaTable,
} from '../utils/globals'; } from '../utils/globals';
import View from './View'; import View from './View';
import { FilterType, UITypes } from 'nocodb-sdk'; import { BoolType, FilterType, UITypes } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import { NcError } from '../meta/helpers/catchError'; import { NcError } from '../meta/helpers/catchError';
import { extractProps } from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
@ -55,7 +55,7 @@ export default class Filter {
value?: string; value?: string;
logical_op?: string; logical_op?: string;
is_group?: boolean; is_group?: BoolType;
children?: Filter[]; children?: Filter[];
project_id?: string; project_id?: string;
base_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 Noco from '../Noco';
import Column from './Column'; import Column from './Column';
import Model from './Model'; import Model from './Model';
@ -19,7 +20,7 @@ export default class LinkToAnotherRecordColumn {
fk_index_name?: string; fk_index_name?: string;
type: 'hm' | 'bt' | 'mm'; type: 'hm' | 'bt' | 'mm';
virtual = false; virtual: BoolType = false;
mmModel?: Model; mmModel?: Model;
relatedTable?: 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, { await View.updateColumn(body.fk_view_id, mapViewMappedByColumn.id, {
show: true, show: true,
}); });
} }
// update meta // 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 { import {
isVirtualCol, isVirtualCol,
ModelTypes, ModelTypes,
BoolType,
TableReqType, TableReqType,
TableType, TableType,
UITypes, UITypes,
@ -25,17 +26,17 @@ import { sanitize } from '../db/sql-data-mapper/lib/sql/helpers/sanitize';
import { extractProps } from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
export default class Model implements TableType { export default class Model implements TableType {
copy_enabled: boolean; copy_enabled: BoolType;
created_at: Date | number | string; created_at: Date | number | string;
base_id: 'db' | string; base_id: 'db' | string;
deleted: boolean; deleted: BoolType;
enabled: boolean; enabled: BoolType;
export_enabled: boolean; export_enabled: BoolType;
id: string; id: string;
order: number; order: number;
parent_id: string; parent_id: string;
password: string; password: string;
pin: boolean; pin: BoolType;
project_id: string; project_id: string;
schema: any; schema: any;
show_all_fields: boolean; show_all_fields: boolean;
@ -46,7 +47,7 @@ export default class Model implements TableType {
table_name: string; table_name: string;
title: string; title: string;
mm: boolean; mm: BoolType;
uuid: string; uuid: string;
@ -98,7 +99,7 @@ export default class Model implements TableType {
projectId, projectId,
baseId, baseId,
model: Partial<TableReqType> & { model: Partial<TableReqType> & {
mm?: boolean; mm?: BoolType;
created_at?: any; created_at?: any;
updated_at?: any; updated_at?: any;
}, },

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

@ -1,6 +1,6 @@
import Base from './/Base'; import Base from './/Base';
import Noco from '../Noco'; import Noco from '../Noco';
import { ProjectType } from 'nocodb-sdk'; import { BoolType, MetaType, ProjectType } from 'nocodb-sdk';
import { import {
CacheDelDirection, CacheDelDirection,
CacheGetType, CacheGetType,
@ -16,9 +16,9 @@ export default class Project implements ProjectType {
public prefix: string; public prefix: string;
public status: string; public status: string;
public description: string; public description: string;
public meta: string; public meta: MetaType;
public color: string; public color: string;
public deleted: string; public deleted: BoolType;
public order: number; public order: number;
public is_meta = false; public is_meta = false;
public bases?: Base[]; 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 = 'nc_map_view_v2',
MAP_VIEW_COLUMNS = 'nc_map_view_columns_v2', MAP_VIEW_COLUMNS = 'nc_map_view_columns_v2',
STORE = 'nc_store', STORE = 'nc_store',
} }
export const orderedMetaTables = [ export const orderedMetaTables = [

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

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