Merge branch 'develop' into docs/upgrade-docusaurus

This commit is contained in:
Aman Regu 2024-07-17 01:02:38 +05:30
commit a42e90a7e5
18 changed files with 2359 additions and 93 deletions

30
docs/build-latest-version.sh Normal file → Executable file
View file

@ -1,9 +1,33 @@
#!/bin/bash
set -e
jq '[.[0]]' versions.json > tmp_versions.json
mv tmp_versions.json versions.json
CONFIG_FILE="docusaurus.config.js"
# Extract lastVersion from docusaurus.config.js using sed
LAST_VERSION=$(sed -n "s/.*lastVersion: *'\\([^']*\\)'.*/\\1/p" "$CONFIG_FILE")
if [ -z "$LAST_VERSION" ]; then
echo "Error: lastVersion not found in $CONFIG_FILE"
exit 1
fi
echo "Found lastVersion: $LAST_VERSION"
# Extract all version numbers from the entire file
ALL_VERSIONS=$(grep -oE "'[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z]+)?'" "$CONFIG_FILE" | sed "s/'//g" | sort -u -V -r)
if [ -z "$ALL_VERSIONS" ]; then
echo "Error: No versions found in $CONFIG_FILE"
exit 1
fi
echo "Found raw versions:"
echo "$ALL_VERSIONS"
# Convert the extracted versions into a JSON array format
VERSION_ARRAY=$(echo "$ALL_VERSIONS" | jq -R -s -c 'split("\n")[:-1] + ["'"$LAST_VERSION"'"] | unique')
echo "Updating versions.json with: $VERSION_ARRAY"
# Update versions.json with combined data
echo $VERSION_ARRAY | jq . > versions.json
# Install dependencies and build the project
npm i && npm run build
exec "$@"
exec "$@"

View file

@ -1,22 +1,29 @@
module.exports = async () => {
return {
verbose: true,
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testEnvironment: 'node',
testRegex: '.spec.ts$',
testPathIgnorePatterns: ['.e2e-spec.ts$'],
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
moduleNameMapper: {
'dist/src/entities/(.*)': '<rootDir>/dist/src/entities/$1',
'^src/(.*)': '<rootDir>/src/$1',
'@dto/(.*)': '<rootDir>/src/dto/$1',
'@plugins/(.*)': '<rootDir>/plugins/$1',
'@services/(.*)': '<rootDir>/src/services/$1',
'@controllers/(.*)': '<rootDir>/src/controllers/$1',
'@ee/(.*)': '<rootDir>/ee/$1',
},
};
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
verbose: true,
moduleFileExtensions: ['js', 'json', 'ts', 'node'],
rootDir: '.',
testEnvironment: 'node',
testRegex: '.spec.ts$',
transform: {
'^.+\\.(t|j)s$': [
'ts-jest',
{
tsconfig: 'tsconfig.json',
},
],
},
moduleNameMapper: {
'^src/(.*)': '<rootDir>/src/$1',
'@dto/(.*)': '<rootDir>/src/dto/$1',
'@plugins/(.*)': '<rootDir>/plugins/$1',
'@services/(.*)': '<rootDir>/src/services/$1',
'@entities/(.*)': '<rootDir>/src/entities/$1',
'@controllers/(.*)': '<rootDir>/src/controllers/$1',
'@ee/(.*)': '<rootDir>/ee/$1',
},
testTimeout: 30000,
};
export default config;

330
server/package-lock.json generated
View file

@ -67,13 +67,16 @@
"@golevelup/ts-jest": "^0.3.2",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@pollyjs/adapter-node-http": "^6.0.6",
"@pollyjs/core": "^6.0.6",
"@pollyjs/persister-fs": "^6.0.6",
"@types/compression": "^1.7.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/express-http-proxy": "^1.6.3",
"@types/got": "^9.6.12",
"@types/humps": "^2.0.1",
"@types/jest": "^27.0.0",
"@types/jest": "^27.5.2",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/nodemailer": "^6.4.4",
@ -93,8 +96,9 @@
"prettier": "^2.3.2",
"preview-email": "^3.0.19",
"rimraf": "^3.0.2",
"setup-polly-jest": "^0.11.0",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-jest": "^29.1.5",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5"
},
@ -2776,6 +2780,95 @@
"node": ">=14"
}
},
"node_modules/@pollyjs/adapter": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/adapter/-/adapter-6.0.6.tgz",
"integrity": "sha512-szhys0NiFQqCJDMC0kpDyjhLqSI7aWc6m6iATCRKgcMcN/7QN85pb3GmRzvnNV8+/Bi2AUSCwxZljcsKhbYVWQ==",
"dev": true,
"dependencies": {
"@pollyjs/utils": "^6.0.6"
}
},
"node_modules/@pollyjs/adapter-node-http": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/adapter-node-http/-/adapter-node-http-6.0.6.tgz",
"integrity": "sha512-jdJG7oncmSHZAtVMmRgOxh5A56b7G8H9ULlk/ZaVJ+jNrlFXhLmPpx8OQoSF4Cuq2ugdiWmwmAjFXHStcpY3Mw==",
"dev": true,
"dependencies": {
"@pollyjs/adapter": "^6.0.6",
"@pollyjs/utils": "^6.0.6",
"lodash-es": "^4.17.21",
"nock": "^13.2.1"
}
},
"node_modules/@pollyjs/core": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/core/-/core-6.0.6.tgz",
"integrity": "sha512-1ZZcmojW8iSFmvHGeLlvuudM3WiDV842FsVvtPAo3HoAYE6jCNveLHJ+X4qvonL4enj1SyTF3hXA107UkQFQrA==",
"dev": true,
"dependencies": {
"@pollyjs/utils": "^6.0.6",
"@sindresorhus/fnv1a": "^2.0.1",
"blueimp-md5": "^2.19.0",
"fast-json-stable-stringify": "^2.1.0",
"is-absolute-url": "^3.0.3",
"lodash-es": "^4.17.21",
"loglevel": "^1.8.0",
"route-recognizer": "^0.3.4",
"slugify": "^1.6.3"
}
},
"node_modules/@pollyjs/node-server": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/node-server/-/node-server-6.0.6.tgz",
"integrity": "sha512-nkP1+hdNoVOlrRz9R84haXVsaSmo8Xmq7uYK9GeUMSLQy4Fs55ZZ9o2KI6vRA8F6ZqJSbC31xxwwIoTkjyP7Vg==",
"dev": true,
"dependencies": {
"@pollyjs/utils": "^6.0.6",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"fs-extra": "^10.0.0",
"http-graceful-shutdown": "^3.1.5",
"morgan": "^1.10.0",
"nocache": "^3.0.1"
}
},
"node_modules/@pollyjs/persister": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/persister/-/persister-6.0.6.tgz",
"integrity": "sha512-9KB1p+frvYvFGur4ifzLnFKFLXAMXrhAhCnVhTnkG2WIqqQPT7y+mKBV/DKCmYFx8GPA9FiNGqt2pB53uJpIdw==",
"dev": true,
"dependencies": {
"@pollyjs/utils": "^6.0.6",
"@types/set-cookie-parser": "^2.4.1",
"bowser": "^2.4.0",
"fast-json-stable-stringify": "^2.1.0",
"lodash-es": "^4.17.21",
"set-cookie-parser": "^2.4.8",
"utf8-byte-length": "^1.0.4"
}
},
"node_modules/@pollyjs/persister-fs": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/persister-fs/-/persister-fs-6.0.6.tgz",
"integrity": "sha512-/ALVgZiH2zGqwLkW0Mntc0Oq1v7tR8LS8JD2SAyIsHpnSXeBUnfPWwjAuYw0vqORHFVEbwned6MBRFfvU/3qng==",
"dev": true,
"dependencies": {
"@pollyjs/node-server": "^6.0.6",
"@pollyjs/persister": "^6.0.6"
}
},
"node_modules/@pollyjs/utils": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/@pollyjs/utils/-/utils-6.0.6.tgz",
"integrity": "sha512-nhVJoI3nRgRimE0V2DVSvsXXNROUH6iyJbroDu4IdsOIOFC1Ds0w+ANMB4NMwFaqE+AisWOmXFzwAGdAfyiQVg==",
"dev": true,
"dependencies": {
"qs": "^6.10.1",
"url-parse": "^1.5.3"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -2991,6 +3084,15 @@
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
"dev": true
},
"node_modules/@sindresorhus/fnv1a": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-2.0.1.tgz",
"integrity": "sha512-suq9tRQ6bkpMukTG5K5z0sPWB7t0zExMzZCdmYm6xTSSIm/yCKNm7VCL36wVeyTsFr597/UhU1OAYdHGMDiHrw==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@ -3333,9 +3435,9 @@
}
},
"node_modules/@types/node": {
"version": "16.18.68",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz",
"integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg=="
"version": "16.18.101",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.101.tgz",
"integrity": "sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA=="
},
"node_modules/@types/nodemailer": {
"version": "6.4.14",
@ -3490,6 +3592,15 @@
"@types/node": "*"
}
},
"node_modules/@types/set-cookie-parser": {
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.9.tgz",
"integrity": "sha512-bCorlULvl0xTdjj4BPUHX4cqs9I+go2TfW/7Do1nnFYWS0CPP429Qr1AY42kiFhCwLpvAkWFr1XIBHd8j6/MCQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@ -4882,6 +4993,18 @@
}
]
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
@ -4934,6 +5057,12 @@
"node": ">= 6"
}
},
"node_modules/blueimp-md5": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz",
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@ -4980,6 +5109,12 @@
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="
},
"node_modules/bowser": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -8532,6 +8667,18 @@
"node": ">= 0.8"
}
},
"node_modules/http-graceful-shutdown": {
"version": "3.1.13",
"resolved": "https://registry.npmjs.org/http-graceful-shutdown/-/http-graceful-shutdown-3.1.13.tgz",
"integrity": "sha512-Ci5LRufQ8AtrQ1U26AevS8QoMXDOhnAHCJI3eZu1com7mZGHxREmw3dNj85ftpQokQCvak8nI2pnFS8zyM1M+Q==",
"dev": true,
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
@ -8719,6 +8866,15 @@
"node": ">= 0.10"
}
},
"node_modules/is-absolute-url": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz",
"integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -10653,6 +10809,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -10761,6 +10923,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/loglevel": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
"integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
@ -11574,6 +11749,49 @@
"node": ">=12.20.0"
}
},
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"dev": true,
"dependencies": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/morgan/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/morgan/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/morgan/node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"dev": true,
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/mri": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
@ -11688,6 +11906,29 @@
"lower-case": "^1.1.1"
}
},
"node_modules/nocache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz",
"integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==",
"dev": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/nock": {
"version": "13.5.4",
"resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz",
"integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==",
"dev": true,
"dependencies": {
"debug": "^4.1.0",
"json-stringify-safe": "^5.0.1",
"propagate": "^2.0.0"
},
"engines": {
"node": ">= 10.13"
}
},
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@ -12745,6 +12986,15 @@
"node": ">= 6"
}
},
"node_modules/propagate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -12947,6 +13197,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"dev": true
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -13130,6 +13386,12 @@
"node": ">=0.10.0"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -13262,6 +13524,12 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"node_modules/route-recognizer": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/route-recognizer/-/route-recognizer-0.3.4.tgz",
"integrity": "sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g==",
"dev": true
},
"node_modules/run-applescript": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-3.2.0.tgz",
@ -13619,6 +13887,12 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
"dev": true
},
"node_modules/set-function-length": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
@ -13643,6 +13917,15 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/setup-polly-jest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/setup-polly-jest/-/setup-polly-jest-0.11.0.tgz",
"integrity": "sha512-3ywsCFGfCvfi3ZpwYyDc4YDPNiB70QtjODoKFD5hbhza1GMOh0ZzAYUZO9OBmo/1isasynxcS5WzKYMyDJUeZw==",
"dev": true,
"peerDependencies": {
"@pollyjs/core": "*"
}
},
"node_modules/sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
@ -13754,6 +14037,15 @@
"node": "*"
}
},
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"dev": true,
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/sonic-boom": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz",
@ -14353,9 +14645,9 @@
}
},
"node_modules/ts-jest": {
"version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
"integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==",
"version": "29.1.5",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz",
"integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==",
"dev": true,
"dependencies": {
"bs-logger": "0.x",
@ -14371,10 +14663,11 @@
"ts-jest": "cli.js"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
"node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"@babel/core": ">=7.0.0-beta.0 <8",
"@jest/transform": "^29.0.0",
"@jest/types": "^29.0.0",
"babel-jest": "^29.0.0",
"jest": "^29.0.0",
@ -14384,6 +14677,9 @@
"@babel/core": {
"optional": true
},
"@jest/transform": {
"optional": true
},
"@jest/types": {
"optional": true
},
@ -14857,6 +15153,22 @@
"node": ">=6"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dev": true,
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
"dev": true
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -13,11 +13,13 @@
"start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "NODE_ENV=production node dist/src/main",
"test": "NODE_ENV=test jest --config jest.config.ts",
"test:record": "NODE_ENV=test POLLY_MODE=record jest --config jest.config.ts --detectOpenHandles",
"test": "NODE_ENV=test jest --config jest.config.ts --detectOpenHandles",
"test:watch": "NODE_ENV=test jest --watch",
"test:cov": "NODE_ENV=test jest --coverage",
"test:debug": "NODE_ENV=test node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "NODE_ENV=test jest --runInBand --config ./test/jest-e2e.json --detectOpenHandles",
"test:e2e:record": "NODE_ENV=test POLLY_MODE=record jest --runInBand --config ./test/jest-e2e.json --detectOpenHandles",
"db:create": "ts-node ./scripts/create-database.ts",
"db:create:prod": "node dist/scripts/create-database.js ",
"db:drop": "ts-node ./scripts/drop-database.ts",
@ -95,13 +97,16 @@
"@golevelup/ts-jest": "^0.3.2",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@pollyjs/adapter-node-http": "^6.0.6",
"@pollyjs/core": "^6.0.6",
"@pollyjs/persister-fs": "^6.0.6",
"@types/compression": "^1.7.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/express-http-proxy": "^1.6.3",
"@types/got": "^9.6.12",
"@types/humps": "^2.0.1",
"@types/jest": "^27.0.0",
"@types/jest": "^27.5.2",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/nodemailer": "^6.4.4",
@ -121,8 +126,9 @@
"prettier": "^2.3.2",
"preview-email": "^3.0.19",
"rimraf": "^3.0.2",
"setup-polly-jest": "^0.11.0",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-jest": "^29.1.5",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5"
},

View file

@ -2,6 +2,50 @@ import { QueryFailedError } from 'typeorm';
import { InternalTable } from 'src/entities/internal_table.entity';
import { capitalize } from 'lodash';
export const TJDB = {
character_varying: 'character varying' as const,
integer: 'integer' as const,
bigint: 'bigint' as const,
serial: 'serial' as const,
double_precision: 'double precision' as const,
boolean: 'boolean' as const,
};
export type TooljetDatabaseDataTypes = (typeof TJDB)[keyof typeof TJDB];
export type TooljetDatabaseColumn = {
column_name: string;
data_type: TooljetDatabaseDataTypes;
column_default: string | null;
character_maximum_length: number | null;
numeric_precision: number | null;
constraints_type: {
is_not_null: boolean;
is_primary_key: boolean;
is_unique: boolean;
};
keytype: string | null;
};
export type TooljetDatabaseForeignKey = {
column_names: string[];
referenced_table_name: string;
referenced_column_names: string[];
on_update: string;
on_delete: string;
constraint_name: string;
referenced_table_id: string;
};
export type TooljetDatabaseTable = {
id: string;
table_name: string;
schema: {
columns: TooljetDatabaseColumn[];
foreign_keys: TooljetDatabaseForeignKey[];
};
};
enum PostgresErrorCode {
UniqueViolation = '23505',
CheckViolation = '23514',

View file

@ -3,28 +3,14 @@ import { EntityManager, In, ObjectLiteral, SelectQueryBuilder, Table, TableColum
import { InjectEntityManager } from '@nestjs/typeorm';
import { InternalTable } from 'src/entities/internal_table.entity';
import { isString, isEmpty, camelCase } from 'lodash';
import { TooljetDatabaseError, TooljetDbActions } from 'src/modules/tooljet_db/tooljet-db.types';
export type TableColumnSchema = {
column_name: string;
data_type: SupportedDataTypes;
column_default: string | null;
character_maximum_length: number | null;
numeric_precision: number | null;
is_nullable: 'YES' | 'NO';
constraint_type: string | null;
keytype: string | null;
};
export type ForeignKeyDetails = {
column_names: Array<string>;
referenced_table_name: string;
referenced_column_names: Array<string>;
on_delete: string;
on_update: string;
};
export type SupportedDataTypes = 'character varying' | 'integer' | 'bigint' | 'serial' | 'double precision' | 'boolean';
import {
TooljetDatabaseColumn,
TooljetDatabaseDataTypes,
TooljetDatabaseError,
TooljetDatabaseForeignKey,
TooljetDbActions,
TJDB,
} from 'src/modules/tooljet_db/tooljet-db.types';
// Patching TypeORM SelectQueryBuilder to handle for right and full outer joins
declare module 'typeorm' {
@ -91,7 +77,7 @@ export class TooljetDbService {
organizationId: string,
params,
connectionManagers: Record<string, EntityManager> = { appManager: this.manager, tjdbManager: this.tooljetDbManager }
): Promise<{ foreign_keys: ForeignKeyDetails[]; columns: TableColumnSchema[] }> {
): Promise<{ foreign_keys: TooljetDatabaseForeignKey[]; columns: TooljetDatabaseColumn[] }> {
const { table_name: tableName, id: id } = params;
const { appManager, tjdbManager } = connectionManagers;
@ -667,7 +653,7 @@ export class TooljetDbService {
if (!tables?.length) throw new BadRequestException('Tables are not chosen');
const tableIdList: Array<string> = tables
.filter((table) => table.type === 'Table')
.filter((table) => table.type === 'Table' && !isEmpty(table.name))
.map((filteredTable) => filteredTable.name);
const internalTables = await this.findOrFailInternalTableFromTableId(tableIdList, organizationId);
@ -847,17 +833,17 @@ export class TooljetDbService {
}
}
private prepareColumnListForCreateTable(columns) {
private prepareColumnListForCreateTable(columns: TooljetDatabaseColumn[]) {
const columnList = columns.map((column) => {
const { column_name, constraints_type = {} } = column;
const { column_name, constraints_type = {} as any } = column;
const is_primary_key_column = constraints_type?.is_primary_key || false;
const prepareDataTypeAndDefault = (column): { data_type: SupportedDataTypes; column_default: unknown } => {
const prepareDataTypeAndDefault = (column): { data_type: TooljetDatabaseDataTypes; column_default: unknown } => {
const { data_type, column_default = undefined } = column;
const isSerial = () => data_type === 'integer' && /^nextval\(/.test(column_default);
const isCharacterVarying = () => data_type === 'character varying';
const isSerial = () => data_type === TJDB.integer && /^nextval\(/.test(column_default);
const isCharacterVarying = () => data_type === TJDB.character_varying;
if (isSerial()) return { data_type: 'serial', column_default: undefined };
if (isSerial()) return { data_type: TJDB.serial, column_default: undefined };
if (isCharacterVarying()) return { data_type, column_default: this.addQuotesIfString(column_default) };
return { data_type, column_default };
@ -876,7 +862,7 @@ export class TooljetDbService {
return columnList;
}
private prepareForeignKeyDetailsJSON(foreign_keys, referenced_tables_info) {
private prepareForeignKeyDetailsJSON(foreign_keys: TooljetDatabaseForeignKey[], referenced_tables_info) {
if (!foreign_keys.length) return [];
const foreignKeyList = foreign_keys.map((foreignKeyDetail) => {
const {

View file

@ -2,12 +2,13 @@ import { BadRequestException, Injectable, NotFoundException, Optional } from '@n
import { EntityManager } from 'typeorm';
import { InternalTable } from 'src/entities/internal_table.entity';
import * as csv from 'fast-csv';
import { SupportedDataTypes, TableColumnSchema, TooljetDbService } from './tooljet_db.service';
import { TooljetDbService } from './tooljet_db.service';
import { InjectEntityManager } from '@nestjs/typeorm';
import { isEmpty } from 'lodash';
import { pipeline } from 'stream/promises';
import { PassThrough } from 'stream';
import { v4 as uuid } from 'uuid';
import { TJDB, TooljetDatabaseColumn, TooljetDatabaseDataTypes } from 'src/modules/tooljet_db/tooljet-db.types';
const MAX_ROW_COUNT = 1000;
@ -33,17 +34,17 @@ export class TooljetDbBulkUploadService {
throw new NotFoundException(`Table ${tableName} not found`);
}
const { columns: internalTableColumnSchema }: { columns: TableColumnSchema[] } =
const { columns: internalTableDatabaseColumn }: { columns: TooljetDatabaseColumn[] } =
await this.tooljetDbService.perform(organizationId, 'view_table', {
table_name: tableName,
});
return await this.bulkUploadCsv(internalTable.id, internalTableColumnSchema, fileBuffer);
return await this.bulkUploadCsv(internalTable.id, internalTableDatabaseColumn, fileBuffer);
}
async bulkUploadCsv(
internalTableId: string,
internalTableColumnSchema: TableColumnSchema[],
internalTableDatabaseColumn: TooljetDatabaseColumn[],
fileBuffer: Buffer
): Promise<{ processedRows: number }> {
const rowsToUpsert = [];
@ -53,17 +54,17 @@ export class TooljetDbBulkUploadService {
strictColumnHandling: true,
discardUnmappedColumns: true,
});
const primaryKeyColumnSchema = internalTableColumnSchema.filter(
const primaryKeyColumnSchema = internalTableDatabaseColumn.filter(
(colDetails) => colDetails.keytype === 'PRIMARY KEY'
);
const primaryKeyValuesToUpsert = new Set();
let rowsProcessed = 0;
csvStream
.on('headers', (headers) => this.validateHeadersAsColumnSubset(internalTableColumnSchema, headers, csvStream))
.on('headers', (headers) => this.validateHeadersAsColumnSubset(internalTableDatabaseColumn, headers, csvStream))
.transform((row) =>
this.validateAndParseColumnDataType(
internalTableColumnSchema,
internalTableDatabaseColumn,
primaryKeyColumnSchema,
row,
rowsProcessed,
@ -102,7 +103,7 @@ export class TooljetDbBulkUploadService {
await pipeline(passThrough, csvStream);
await this.tooljetDbManager.transaction(async (tooljetDbManager) => {
await this.bulkUpsertRows(tooljetDbManager, rowsToUpsert, internalTableId, internalTableColumnSchema);
await this.bulkUpsertRows(tooljetDbManager, rowsToUpsert, internalTableId, internalTableDatabaseColumn);
});
return { processedRows: rowsProcessed };
@ -112,15 +113,15 @@ export class TooljetDbBulkUploadService {
tooljetDbManager: EntityManager,
rowsToUpsert: unknown[],
internalTableId: string,
internalTableColumnSchema: TableColumnSchema[]
internalTableDatabaseColumn: TooljetDatabaseColumn[]
) {
if (isEmpty(rowsToUpsert)) return;
const primaryKeyColumns = internalTableColumnSchema
const primaryKeyColumns = internalTableDatabaseColumn
.filter((colDetails) => colDetails.keytype === 'PRIMARY KEY')
.map((colDetails) => colDetails.column_name);
const serialTypeColumns = internalTableColumnSchema
const serialTypeColumns = internalTableDatabaseColumn
.filter((colDetails) => colDetails.data_type === 'integer' && /^nextval\(/.test(colDetails.column_default))
.map((colDetails) => colDetails.column_name);
@ -164,11 +165,11 @@ export class TooljetDbBulkUploadService {
}
async validateHeadersAsColumnSubset(
internalTableColumnSchema: TableColumnSchema[],
internalTableDatabaseColumn: TooljetDatabaseColumn[],
headers: string[],
csvStream: csv.CsvParserStream<csv.ParserRow<any>, csv.ParserRow<any>>
) {
const internalTableColumns = new Set<string>(internalTableColumnSchema.map((c) => c.column_name));
const internalTableColumns = new Set<string>(internalTableDatabaseColumn.map((c) => c.column_name));
const columnsInCsv = new Set<string>(headers);
const isSubset = (subset: Set<string>, superset: Set<string>) => [...subset].every((item) => superset.has(item));
@ -179,15 +180,15 @@ export class TooljetDbBulkUploadService {
}
}
findPrimaryKey(columnName: string, primaryKeyColumns: TableColumnSchema[]) {
findPrimaryKey(columnName: string, primaryKeyColumns: TooljetDatabaseColumn[]) {
return primaryKeyColumns.find(
(colDetails) => colDetails.column_name === columnName && colDetails.keytype === 'PRIMARY KEY'
);
}
validateAndParseColumnDataType(
internalTableColumnSchema: TableColumnSchema[],
primaryKeyColumnSchema: TableColumnSchema[],
internalTableDatabaseColumn: TooljetDatabaseColumn[],
primaryKeyColumnSchema: TooljetDatabaseColumn[],
row: unknown,
rowsProcessed: number,
csvStream: csv.CsvParserStream<csv.ParserRow<any>, csv.ParserRow<any>>
@ -197,7 +198,7 @@ export class TooljetDbBulkUploadService {
try {
const columnsInCsv = Object.keys(row);
const transformedRow = columnsInCsv.reduce((result, columnInCsv) => {
const columnDetails = internalTableColumnSchema.find((colDetails) => colDetails.column_name === columnInCsv);
const columnDetails = internalTableDatabaseColumn.find((colDetails) => colDetails.column_name === columnInCsv);
const primaryKey = this.findPrimaryKey(columnInCsv, primaryKeyColumnSchema);
if (!isEmpty(primaryKey) && isEmpty(primaryKey.column_default) && isEmpty(row[columnInCsv]))
@ -213,15 +214,15 @@ export class TooljetDbBulkUploadService {
}
}
convertToDataType(columnValue: string, supportedDataType: SupportedDataTypes) {
convertToDataType(columnValue: string, supportedDataType: TooljetDatabaseDataTypes) {
if (!columnValue) return null;
switch (supportedDataType) {
case 'boolean':
case TJDB.boolean:
return this.convertBoolean(columnValue);
case 'integer':
case 'double precision':
case 'bigint':
case TJDB.integer:
case TJDB.double_precision:
case TJDB.bigint:
return this.convertNumber(columnValue, supportedDataType);
default:
return columnValue;

View file

@ -0,0 +1,128 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService/.createRow/should create a new row and verify its content",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "197a6feb7e633d071ca275fc502d5b6f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "b17c6d5a-93b6-4eee-8420-4c19b9bf3ddf"
},
{
"name": "tableinfo",
"value": {
"9a26655a-3afb-410f-8dd1-13f0787255b4": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NTUsImV4cCI6MTcyMDEyNjgxNX0.rcfp0ZwfIdJ8fIkpb1ZBvOdyQLF9KtKdQJXc8F7gtX4"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/9a26655a-3afb-410f-8dd1-13f0787255b4"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:15 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:15.597Z",
"time": 78,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 78
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -0,0 +1,246 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService/.deleteRows/should delete rows and verify the deletion",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "197a6feb7e633d071ca275fc502d5b6f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "d029ddeb-e4c5-46ed-aa23-bbd58fbb445b"
},
{
"name": "tableinfo",
"value": {
"d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODAsImV4cCI6MTcyMDEyNjg0MH0.oSaB7oSG8LYnxHs_CbaiBd7ZZXasu--TZuqo9nwohZY"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:40 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:40.142Z",
"time": 34,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 34
}
},
{
"_id": "025d1c262b73f8739031fa6b0b522c67",
"_order": 0,
"cache": {},
"request": {
"bodySize": 0,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "d029ddeb-e4c5-46ed-aa23-bbd58fbb445b"
},
{
"name": "tableinfo",
"value": {
"d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODAsImV4cCI6MTcyMDEyNjg0MH0.oSaB7oSG8LYnxHs_CbaiBd7ZZXasu--TZuqo9nwohZY"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 581,
"httpVersion": "HTTP/1.1",
"method": "DELETE",
"queryString": [
{
"name": "name",
"value": "eq.John Doe"
},
{
"name": "limit",
"value": "1"
},
{
"name": "order",
"value": "id"
}
],
"url": "http://localhost:3001/d4dbdb73-e3fd-4cb8-8fd0-a8540f38267d?name=eq.John%20Doe&limit=1&order=id"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:40 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2024-07-04T20:59:40.235Z",
"time": 25,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 25
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -0,0 +1,242 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService/.joinTables/should join tables and verify the result",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "197a6feb7e633d071ca275fc502d5b6f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "14ff6b2c-18b9-45d5-8098-cbb2d3faa8d8"
},
{
"name": "tableinfo",
"value": {
"0d81d897-f3f7-4616-afcb-a7873a6fdbd0": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODcsImV4cCI6MTcyMDEyNjg0N30.zMLW2N6-FhzS7zgwBgQM6fAPjI4rxqfAhum_izOLpDA"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/0d81d897-f3f7-4616-afcb-a7873a6fdbd0"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:47 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:47.938Z",
"time": 34,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 34
}
},
{
"_id": "11ab62f4916d91ebf3464b4d366801f1",
"_order": 0,
"cache": {},
"request": {
"bodySize": 27,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "14ff6b2c-18b9-45d5-8098-cbb2d3faa8d8"
},
{
"name": "tableinfo",
"value": {
"b8e3bafa-fa05-4bbc-8e3f-5af967405d30": "orders"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3ODcsImV4cCI6MTcyMDEyNjg0N30.zMLW2N6-FhzS7zgwBgQM6fAPjI4rxqfAhum_izOLpDA"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "27"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"user_id\":1,\"total\":100.5}"
},
"queryString": [],
"url": "http://localhost:3001/b8e3bafa-fa05-4bbc-8e3f-5af967405d30"
},
"response": {
"bodySize": 36,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 36,
"text": "[{\"id\":1,\"user_id\":1,\"total\":100.5}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:47 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:47.999Z",
"time": 14,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 14
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -0,0 +1,237 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService/.listRows/should list rows correctly",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "197a6feb7e633d071ca275fc502d5b6f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "08eae723-42ac-4fd5-a7dc-3db4f0a1ba9c"
},
{
"name": "tableinfo",
"value": {
"291dda09-4ca4-48cd-b62c-cf0e325c65ae": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NjMsImV4cCI6MTcyMDEyNjgyM30.dlBsKWsPZMxNr3fGSL0-bi8x-viUGYpTScCU0YS8CQg"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/291dda09-4ca4-48cd-b62c-cf0e325c65ae"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:23 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:23.808Z",
"time": 59,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 59
}
},
{
"_id": "a201948f8b1cb6b14aa3987eeb19b1f5",
"_order": 0,
"cache": {},
"request": {
"bodySize": 0,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "08eae723-42ac-4fd5-a7dc-3db4f0a1ba9c"
},
{
"name": "tableinfo",
"value": {
"291dda09-4ca4-48cd-b62c-cf0e325c65ae": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NjMsImV4cCI6MTcyMDEyNjgyM30.dlBsKWsPZMxNr3fGSL0-bi8x-viUGYpTScCU0YS8CQg"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 542,
"httpVersion": "HTTP/1.1",
"method": "GET",
"queryString": [],
"url": "http://localhost:3001/291dda09-4ca4-48cd-b62c-cf0e325c65ae"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:23 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "0-0/1"
},
{
"name": "content-location",
"value": "/291dda09-4ca4-48cd-b62c-cf0e325c65ae"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
},
{
"name": "preference-applied",
"value": "count=exact"
}
],
"headersSize": 262,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2024-07-04T20:59:23.901Z",
"time": 19,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 19
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -0,0 +1,120 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "bed7fb75983310f155c3e3b6b1ccc6d8",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "5e724498-ae98-42bc-bb3d-b7d255b1d2c5"
},
{
"name": "tableinfo",
"value": {
"96732d2a-9d32-409e-b34f-4cf6a99ef342": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMTY0MDAsImV4cCI6MTcyMDExNjQ2MH0.nQu4YxlacdZMYi0ssTe7MIWWU61rQNKe7xZc0GeGTXQ"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/96732d2a-9d32-409e-b34f-4cf6a99ef342"
},
"response": {
"bodySize": 2,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 2,
"text": "{}"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 18:06:40 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 150,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 404,
"statusText": "Not Found"
},
"startedDateTime": "2024-07-04T18:06:40.291Z",
"time": 13,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 13
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -0,0 +1,360 @@
{
"log": {
"_recordingName": "TooljetDbOperationsService/.updateRows/should update rows and verify the changes",
"creator": {
"comment": "persister:fs",
"name": "Polly.JS",
"version": "6.0.6"
},
"entries": [
{
"_id": "197a6feb7e633d071ca275fc502d5b6f",
"_order": 0,
"cache": {},
"request": {
"bodySize": 46,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "577ed2e5-3243-432a-8084-c90bf85a6afc"
},
{
"name": "tableinfo",
"value": {
"e3f89568-858d-4d4b-a443-d863ad392e7f": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "46"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 563,
"httpVersion": "HTTP/1.1",
"method": "POST",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
},
"queryString": [],
"url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:31 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "*/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 226,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 201,
"statusText": "Created"
},
"startedDateTime": "2024-07-04T20:59:31.529Z",
"time": 25,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 25
}
},
{
"_id": "e671061f70ab82499db975c0d090cc9a",
"_order": 0,
"cache": {},
"request": {
"bodySize": 19,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "577ed2e5-3243-432a-8084-c90bf85a6afc"
},
{
"name": "tableinfo",
"value": {
"e3f89568-858d-4d4b-a443-d863ad392e7f": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "content-length",
"value": "19"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 592,
"httpVersion": "HTTP/1.1",
"method": "PATCH",
"postData": {
"mimeType": "text/plain",
"params": [],
"text": "{\"name\":\"Jane Doe\"}"
},
"queryString": [
{
"name": "name",
"value": "eq.John Doe"
},
{
"name": "order",
"value": "id"
}
],
"url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f?name=eq.John%20Doe&order=id"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"Jane Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:31 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "0-0/1"
},
{
"name": "preference-applied",
"value": "return=representation, count=exact"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"headersSize": 228,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2024-07-04T20:59:31.599Z",
"time": 30,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 30
}
},
{
"_id": "a201948f8b1cb6b14aa3987eeb19b1f5",
"_order": 0,
"cache": {},
"request": {
"bodySize": 0,
"cookies": [],
"headers": [
{
"name": "user-agent",
"value": "got (https://github.com/sindresorhus/got)"
},
{
"name": "data-query-id",
"value": "query-id"
},
{
"name": "tj-workspace-id",
"value": "577ed2e5-3243-432a-8084-c90bf85a6afc"
},
{
"name": "tableinfo",
"value": {
"e3f89568-858d-4d4b-a443-d863ad392e7f": "users"
}
},
{
"name": "authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMiLCJpYXQiOjE3MjAxMjY3NzEsImV4cCI6MTcyMDEyNjgzMX0.-LwnMhI-S-rWKIFXWAqURKY7qGVsMTV5eAoP6vLe9_k"
},
{
"name": "prefer",
"value": "count=exact, return=representation"
},
{
"name": "accept",
"value": "application/json"
},
{
"name": "accept-encoding",
"value": "gzip, deflate, br"
},
{
"name": "host",
"value": "localhost:3001"
}
],
"headersSize": 542,
"httpVersion": "HTTP/1.1",
"method": "GET",
"queryString": [],
"url": "http://localhost:3001/e3f89568-858d-4d4b-a443-d863ad392e7f"
},
"response": {
"bodySize": 55,
"content": {
"mimeType": "application/json; charset=utf-8",
"size": 55,
"text": "[{\"id\":1,\"name\":\"Jane Doe\",\"email\":\"john@example.com\"}]"
},
"cookies": [],
"headers": [
{
"name": "transfer-encoding",
"value": "chunked"
},
{
"name": "date",
"value": "Thu, 04 Jul 2024 20:59:31 GMT"
},
{
"name": "server",
"value": "postgrest/12.0.3 (6a506d1)"
},
{
"name": "content-range",
"value": "0-0/1"
},
{
"name": "content-location",
"value": "/e3f89568-858d-4d4b-a443-d863ad392e7f"
},
{
"name": "content-type",
"value": "application/json; charset=utf-8"
},
{
"name": "preference-applied",
"value": "count=exact"
}
],
"headersSize": 262,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2024-07-04T20:59:31.695Z",
"time": 25,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 25
}
}
],
"pages": [],
"version": "1.2"
}
}

View file

@ -1,5 +1,5 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"moduleFileExtensions": ["js", "json", "ts", "node"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
@ -11,6 +11,7 @@
"@plugins/(.*)": "<rootDir>/../plugins/$1",
"@dto/(.*)": "<rootDir>/../src/dto/$1",
"@services/(.*)": "<rootDir>/../src/services/$1",
"@entities/(.*)": "<rootDir>/src/entities/$1",
"@controllers/(.*)": "<rootDir>/../src/controllers/$1",
"@ee/(.*)": "<rootDir>/../ee/$1"
}

View file

@ -0,0 +1,377 @@
/** @jest-environment setup-polly-jest/jest-environment-node */
import { INestApplication } from '@nestjs/common';
import { getManager, getConnection, EntityManager } from 'typeorm';
import { TooljetDbOperationsService } from '../../src/services/tooljet_db_operations.service';
import { TooljetDbService } from '../../src/services/tooljet_db.service';
import { setupPolly } from 'setup-polly-jest';
import * as NodeHttpAdapter from '@pollyjs/adapter-node-http';
import * as FSPersister from '@pollyjs/persister-fs';
import * as path from 'path';
import { clearDB, createUser } from '../test.helper';
import { setupTestTables } from '../tooljet-db-test.helper';
import { InternalTable } from '@entities/internal_table.entity';
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ormconfig, tooljetDbOrmconfig } from '../../ormconfig';
import { PostgrestProxyService } from '@services/postgrest_proxy.service';
import { getEnvVars } from '../../scripts/database-config-utils';
import { User } from '@entities/user.entity';
import { Organization } from '@entities/organization.entity';
import { OrganizationUser } from '@entities/organization_user.entity';
import { AppVersion } from '@entities/app_version.entity';
import { GroupPermission } from '@entities/group_permission.entity';
import { UserGroupPermission } from '@entities/user_group_permission.entity';
import { App } from '@entities/app.entity';
describe('TooljetDbOperationsService', () => {
let app: INestApplication;
let appManager: EntityManager;
let tjDbManager: EntityManager;
let service: TooljetDbOperationsService;
let tooljetDbService: TooljetDbService;
let organizationId: string;
let usersTableId: string;
const context = setupPolly({
adapters: [NodeHttpAdapter],
persister: FSPersister,
recordFailedRequests: true,
matchRequestsBy: {
method: true,
headers: {
exclude: ['tj-workspace-id', 'authorization', 'tableinfo'], // Exclude headers as they contain dynamic information
},
body: true,
url: {
protocol: true,
username: true,
password: true,
hostname: true,
port: true,
pathname: false, // Don't match by pathname as it contains the dynamic table ID
query: true,
},
},
persisterOptions: {
fs: {
recordingsDir: path.resolve(__dirname, '../__fixtures__'),
},
},
});
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['../.env.test'],
load: [() => getEnvVars()],
}),
TypeOrmModule.forRoot(ormconfig),
TypeOrmModule.forRoot(tooljetDbOrmconfig),
TypeOrmModule.forFeature([
User,
Organization,
OrganizationUser,
App,
AppVersion,
GroupPermission,
UserGroupPermission,
InternalTable,
]),
],
providers: [TooljetDbOperationsService, TooljetDbService, PostgrestProxyService],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
appManager = getManager();
tjDbManager = getConnection('tooljetDb').manager;
service = moduleFixture.get<TooljetDbOperationsService>(TooljetDbOperationsService);
tooljetDbService = moduleFixture.get<TooljetDbService>(TooljetDbService);
});
beforeEach(async () => {
await clearDB();
const adminUserData = await createUser(app, {
email: 'admin@tooljet.io',
groups: ['all_users', 'admin'],
});
organizationId = adminUserData.organization.id;
await setupTestTables(appManager, tjDbManager, tooljetDbService, organizationId);
const usersTable = await appManager.findOneOrFail(InternalTable, { organizationId, tableName: 'users' });
usersTableId = usersTable.id;
});
afterEach(async () => {
context.polly.stop();
});
afterAll(async () => {
await app.close();
await clearDB();
});
describe('.createRow', () => {
it('should create a new row and verify its content', async () => {
const queryOptions = {
table_id: usersTableId,
create_row: {
name: { column: 'name', value: 'John Doe' },
email: { column: 'email', value: 'john@example.com' },
},
id: 'query-id',
organization_id: organizationId,
};
const result = await service.createRow(queryOptions);
expect(result).toBeDefined();
expect(result.status).toBe('ok');
expect(Array.isArray(result.data)).toBe(true);
const data = result.data as Array<Record<string, unknown>>;
expect(data.length).toBeGreaterThan(0);
expect(data[0]).toEqual(
expect.objectContaining({
id: expect.any(Number),
name: 'John Doe',
email: 'john@example.com',
})
);
});
});
describe('.listRows', () => {
it('should list rows correctly', async () => {
// Create a test row
await service.createRow({
table_id: usersTableId,
create_row: {
name: { column: 'name', value: 'John Doe' },
email: { column: 'email', value: 'john@example.com' },
},
id: 'query-id',
organization_id: organizationId,
});
const queryOptions = {
table_id: usersTableId,
list_rows: {
limit: 10,
offset: 0,
},
id: 'query-id',
organization_id: organizationId,
};
const result = await service.listRows(queryOptions);
expect(result).toBeDefined();
expect(result.status).toBe('ok');
const data = result.data as Array<Record<string, unknown>>;
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBe(1);
expect(data[0]).toEqual(
expect.objectContaining({
name: 'John Doe',
email: 'john@example.com',
})
);
});
});
describe('.updateRows', () => {
it('should update rows and verify the changes', async () => {
// Create a test row
await service.createRow({
table_id: usersTableId,
create_row: {
name: { column: 'name', value: 'John Doe' },
email: { column: 'email', value: 'john@example.com' },
},
id: 'query-id',
organization_id: organizationId,
});
const queryOptions = {
table_id: usersTableId,
update_rows: {
where_filters: {
filter1: { column: 'name', operator: 'eq', value: 'John Doe' },
},
columns: {
name: { column: 'name', value: 'Jane Doe' },
},
},
id: 'query-id',
organization_id: organizationId,
};
const result = await service.updateRows(queryOptions);
expect(result).toBeDefined();
expect(result.status).toBe('ok');
// Verify the update
const listResult = await service.listRows({
table_id: usersTableId,
list_rows: { limit: 1 },
id: 'query-id',
organization_id: organizationId,
});
const data = listResult.data as Array<Record<string, unknown>>;
expect(Array.isArray(data)).toBe(true);
expect(data).toEqual([
{
id: 1,
name: 'Jane Doe',
email: 'john@example.com',
},
]);
});
});
describe('.deleteRows', () => {
it('should delete rows and verify the deletion', async () => {
await service.createRow({
table_id: usersTableId,
create_row: {
name: { column: 'name', value: 'John Doe' },
email: { column: 'email', value: 'john@example.com' },
},
id: 'query-id',
organization_id: organizationId,
});
const queryOptions = {
table_id: usersTableId,
delete_rows: {
where_filters: {
filter1: { column: 'name', operator: 'eq', value: 'John Doe' },
},
},
id: 'query-id',
organization_id: organizationId,
};
const result = await service.deleteRows(queryOptions);
expect(result).toBeDefined();
expect(result.status).toBe('ok');
});
});
describe('.joinTables', () => {
it('should join tables and verify the result', async () => {
// Check if tables exist
const usersTable = await appManager.findOne(InternalTable, { organizationId, tableName: 'users' });
const ordersTable = await appManager.findOne(InternalTable, { organizationId, tableName: 'orders' });
expect(usersTable).toBeDefined();
expect(ordersTable).toBeDefined();
const insertUserResult = await tjDbManager
.createQueryBuilder()
.insert()
.into(usersTable.id)
.values({
name: 'John Doe',
email: 'john@example.com',
})
.returning('id')
.execute();
const userId = insertUserResult.raw[0].id;
await tjDbManager
.createQueryBuilder()
.insert()
.into(ordersTable.id)
.values({
user_id: userId,
total: 100.5,
})
.execute();
// Perform join
const queryOptions = {
join_table: {
joins: [
{
id: Date.now(), // unique ID
conditions: {
operator: 'AND',
conditionsList: [
{
operator: '=',
leftField: {
table: usersTable.id,
columnName: 'id',
type: 'Column',
},
rightField: {
table: ordersTable.id,
columnName: 'user_id',
type: 'Column',
},
},
],
},
joinType: 'INNER',
table: ordersTable.id,
},
],
from: {
name: usersTable.id,
type: 'Table',
},
fields: [
{ name: 'id', table: usersTable.id },
{ name: 'name', table: usersTable.id },
{ name: 'email', table: usersTable.id },
{ name: 'id', table: ordersTable.id },
{ name: 'user_id', table: ordersTable.id },
{ name: 'total', table: ordersTable.id },
],
conditions: {
conditionsList: [],
},
order_by: [],
},
organization_id: organizationId,
};
const joinResult = await service.joinTables(queryOptions);
const expectedResponse = {
status: 'ok',
data: {
result: [
{
users_id: expect.any(Number),
users_name: 'John Doe',
users_email: 'john@example.com',
orders_id: expect.any(Number),
orders_user_id: expect.any(Number),
orders_total: 100.5,
},
],
},
};
expect(joinResult).toEqual(expectedResponse);
});
});
});

View file

@ -36,6 +36,7 @@ import { AppEnvironment } from 'src/entities/app_environments.entity';
import { defaultAppEnvironments } from 'src/helpers/utils.helper';
import { DataSourceOptions } from 'src/entities/data_source_options.entity';
import * as cookieParser from 'cookie-parser';
import { InternalTable } from '@entities/internal_table.entity';
export async function createNestAppInstance(): Promise<INestApplication> {
let app: INestApplication;
@ -103,13 +104,27 @@ export function authHeaderForUser(user: User, organizationId?: string, isPasswor
}
export async function clearDB() {
const entities = getConnection().entityMetadatas;
for (const entity of entities) {
const repository = getConnection().getRepository(entity.name);
if (process.env.NODE_ENV !== 'test') return;
if (process.env.ENABLE_TOOLJET_DB === 'true') await dropTooljetDbTables();
const connection = getConnection();
for (const entity of connection.entityMetadatas) {
const repository = connection.getRepository(entity.name);
await repository.query(`TRUNCATE ${entity.tableName} RESTART IDENTITY CASCADE;`);
}
}
async function dropTooljetDbTables() {
const connection = getConnection();
const tooljetDbConnection = getConnection('tooljetDb');
const internalTables = (await connection.manager.find(InternalTable, { select: ['id'] })) as InternalTable[];
for (const table of internalTables) {
await tooljetDbConnection.query(`DROP TABLE IF EXISTS "${table.id}" CASCADE`);
}
}
export async function createApplication(nestApp, { name, user, isPublic, slug }: any, shouldCreateEnvs = true) {
let appRepository: Repository<App>;
appRepository = nestApp.get('AppRepository');

View file

@ -0,0 +1,159 @@
import { EntityManager } from 'typeorm';
import { TooljetDbService } from '@services/tooljet_db.service';
import { InternalTable } from '@entities/internal_table.entity';
import {
TooljetDatabaseColumn,
TooljetDatabaseForeignKey,
TooljetDatabaseTable,
} from 'src/modules/tooljet_db/tooljet-db.types';
const mockTableSchemas: Array<TooljetDatabaseTable> = [
{
id: 'user_table_uuid',
table_name: 'users',
schema: {
columns: [
{
column_name: 'id',
data_type: 'integer',
column_default: "nextval('users_id_seq'::regclass)",
character_maximum_length: null,
numeric_precision: 32,
constraints_type: {
is_not_null: true,
is_primary_key: true,
is_unique: true,
},
keytype: 'PRIMARY KEY',
},
{
column_name: 'name',
data_type: 'character varying',
column_default: null,
character_maximum_length: null,
numeric_precision: null,
constraints_type: {
is_not_null: true,
is_primary_key: false,
is_unique: false,
},
keytype: '',
},
{
column_name: 'email',
data_type: 'character varying',
column_default: null,
character_maximum_length: null,
numeric_precision: null,
constraints_type: {
is_not_null: true,
is_primary_key: false,
is_unique: true,
},
keytype: '',
},
],
foreign_keys: [],
},
},
{
id: 'orders_table_uuid',
table_name: 'orders',
schema: {
columns: [
{
column_name: 'id',
data_type: 'integer',
column_default: "nextval('orders_id_seq'::regclass)",
character_maximum_length: null,
numeric_precision: 32,
constraints_type: {
is_not_null: true,
is_primary_key: true,
is_unique: true,
},
keytype: 'PRIMARY KEY',
},
{
column_name: 'user_id',
data_type: 'integer',
column_default: null,
character_maximum_length: null,
numeric_precision: 32,
constraints_type: {
is_not_null: true,
is_primary_key: false,
is_unique: false,
},
keytype: '',
},
{
column_name: 'total',
data_type: 'double precision',
column_default: null,
character_maximum_length: null,
numeric_precision: null,
constraints_type: {
is_not_null: true,
is_primary_key: false,
is_unique: false,
},
keytype: '',
},
],
foreign_keys: [
{
referenced_table_name: 'users',
constraint_name: 'fk_orders_user_id',
column_names: ['user_id'],
referenced_column_names: ['id'],
on_update: 'NO ACTION',
on_delete: 'CASCADE',
referenced_table_id: 'user_table_id',
},
],
},
},
];
export async function setupTestTables(
appManager: EntityManager,
tjdbManager: EntityManager,
tooljetDbService: TooljetDbService,
organizationId: string,
tableSchemas: Array<TooljetDatabaseTable> = mockTableSchemas
): Promise<void> {
const createTableParams = tableSchemas.map((table) => ({ ...table.schema, table_name: table.table_name }));
for (const params of createTableParams) {
await createTable(appManager, tjdbManager, tooljetDbService, organizationId, params);
}
// Wait for the tables to be created and postgrest to reload the schema
// when running tests in record mode
if (process.env.POLLY_MODE === 'record') {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
async function createTable(
appManager: EntityManager,
tjdbManager: EntityManager,
tooljetDbService: TooljetDbService,
organizationId: string,
params: { table_name: string; columns: TooljetDatabaseColumn[]; foreign_keys: TooljetDatabaseForeignKey[] }
) {
await tooljetDbService.perform(organizationId, 'create_table', params, { appManager, tjdbManager });
}
export async function dropTable(
appManager: EntityManager,
tjdbManager: EntityManager,
tooljetDbService: TooljetDbService,
organizationId: string,
tableName: string
) {
await tooljetDbService.perform(organizationId, 'drop_table', { table_name: tableName }, { appManager, tjdbManager });
await appManager.delete(InternalTable, { organizationId, tableName });
}

View file

@ -14,6 +14,7 @@
"incremental": true,
"paths": {
"@ee/*": ["ee/*"],
"@entities/*": ["src/entities/*"],
"@services/*": ["src/services/*"],
"@controllers/*": ["src/controllers/*"],
"@repositories/*": ["src/repositories/*"],