mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
668 changed files with 1445775 additions and 72 deletions
@ -0,0 +1,78 @@
|
||||
module.exports = { |
||||
parser: '@typescript-eslint/parser', |
||||
parserOptions: { |
||||
project: 'tsconfig.json', |
||||
tsconfigRootDir: __dirname, |
||||
sourceType: 'module', |
||||
}, |
||||
plugins: ['import', 'eslint-comments', 'functional'], |
||||
extends: [ |
||||
'eslint:recommended', |
||||
'plugin:eslint-comments/recommended', |
||||
'plugin:@typescript-eslint/recommended', |
||||
'plugin:import/typescript', |
||||
'plugin:prettier/recommended', |
||||
], |
||||
root: true, |
||||
env: { |
||||
node: true, |
||||
jest: true, |
||||
es6: true, |
||||
}, |
||||
ignorePatterns: [ |
||||
'node_modules', |
||||
'build', |
||||
'coverage', |
||||
'dist', |
||||
'nc', |
||||
'.eslintrc.js', |
||||
], |
||||
globals: { |
||||
BigInt: true, |
||||
console: true, |
||||
WebAssembly: true, |
||||
}, |
||||
rules: { |
||||
'@typescript-eslint/explicit-module-boundary-types': 'off', |
||||
'eslint-comments/disable-enable-pair': [ |
||||
'error', |
||||
{ |
||||
allowWholeFile: true, |
||||
}, |
||||
], |
||||
'eslint-comments/no-unused-disable': 'error', |
||||
'sort-imports': [ |
||||
'error', |
||||
{ |
||||
ignoreDeclarationSort: true, |
||||
ignoreCase: true, |
||||
}, |
||||
], |
||||
'import/order': [ |
||||
'error', |
||||
{ |
||||
groups: [ |
||||
'builtin', |
||||
'external', |
||||
'internal', |
||||
'parent', |
||||
'sibling', |
||||
'index', |
||||
'object', |
||||
'type', |
||||
], |
||||
}, |
||||
], |
||||
'@typescript-eslint/no-this-alias': 'off', |
||||
|
||||
// todo: enable
|
||||
'@typescript-eslint/ban-ts-comment': 'off', |
||||
'@typescript-eslint/no-explicit-any': 'off', |
||||
'@typescript-eslint/no-unused-vars': 'off', |
||||
'@typescript-eslint/no-var-requires': 'off', |
||||
'no-useless-catch': 'off', |
||||
'no-empty': 'off', |
||||
'@typescript-eslint/no-empty-function': 'off', |
||||
'@typescript-eslint/consistent-type-imports': 'warn', |
||||
}, |
||||
}; |
@ -0,0 +1,38 @@
|
||||
# compiled output |
||||
/dist |
||||
/node_modules |
||||
|
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
pnpm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
|
||||
# OS |
||||
.DS_Store |
||||
|
||||
# Tests |
||||
/coverage |
||||
/.nyc_output |
||||
|
||||
# IDEs and editors |
||||
/.idea |
||||
.project |
||||
.classpath |
||||
.c9/ |
||||
*.launch |
||||
.settings/ |
||||
*.sublime-workspace |
||||
|
||||
# IDE - VSCode |
||||
.vscode/* |
||||
!.vscode/settings.json |
||||
!.vscode/tasks.json |
||||
!.vscode/launch.json |
||||
!.vscode/extensions.json |
||||
/noco.db |
||||
|
||||
/docker/main.js |
@ -0,0 +1,4 @@
|
||||
{ |
||||
"singleQuote": true, |
||||
"trailingComma": "all" |
||||
} |
@ -0,0 +1,72 @@
|
||||
########### |
||||
# Litestream Builder |
||||
########### |
||||
FROM golang:alpine3.14 as lt-builder |
||||
|
||||
WORKDIR /usr/src/ |
||||
|
||||
RUN apk add --no-cache git make musl-dev gcc |
||||
|
||||
# build litestream |
||||
RUN git clone https://github.com/benbjohnson/litestream.git litestream |
||||
RUN cd litestream ; go install ./cmd/litestream |
||||
|
||||
RUN cp $GOPATH/bin/litestream /usr/src/lt |
||||
|
||||
|
||||
|
||||
########### |
||||
# Builder |
||||
########### |
||||
FROM node:16.17.0-alpine3.15 as builder |
||||
WORKDIR /usr/src/app |
||||
|
||||
# install node-gyp dependencies |
||||
RUN apk add --no-cache python3 make g++ |
||||
|
||||
# Copy application dependency manifests to the container image. |
||||
# A wildcard is used to ensure both package.json AND package-lock.json are copied. |
||||
# Copying this separately prevents re-running npm ci on every code change. |
||||
COPY ./package*.json ./ |
||||
COPY ./docker/main.js ./docker/main.js |
||||
#COPY ./docker/start.sh /usr/src/appEntry/start.sh |
||||
COPY ./docker/start-litestream.sh /usr/src/appEntry/start.sh |
||||
COPY ./src/lib/public/css/*.css ./docker/public/css/ |
||||
COPY ./src/lib/public/js/*.js ./docker/public/js/ |
||||
COPY ./src/lib/public/favicon.ico ./docker/public/ |
||||
|
||||
# install production dependencies, |
||||
# reduce node_module size with modclean & removing sqlite deps, |
||||
# package built code into app.tar.gz & add execute permission to start.sh |
||||
RUN npm ci --omit=dev --quiet \ |
||||
&& npx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**" --run \ |
||||
&& rm -rf ./node_modules/sqlite3/deps \ |
||||
&& tar -czf ../appEntry/app.tar.gz ./* \ |
||||
&& chmod +x /usr/src/appEntry/start.sh |
||||
|
||||
########## |
||||
# Runner |
||||
########## |
||||
FROM alpine:3.15 |
||||
WORKDIR /usr/src/app |
||||
|
||||
ENV NC_DOCKER 0.6 |
||||
ENV NODE_ENV production |
||||
ENV PORT 8080 |
||||
ENV NC_TOOL_DIR=/usr/app/data/ |
||||
|
||||
RUN apk --update --no-cache add \ |
||||
nodejs \ |
||||
tar \ |
||||
dumb-init |
||||
|
||||
# Copy litestream binary build |
||||
COPY --from=lt-builder /usr/src/lt /usr/src/appEntry/litestream |
||||
# Copy packaged production code & main entry file |
||||
COPY --from=builder /usr/src/appEntry/ /usr/src/appEntry/ |
||||
|
||||
EXPOSE 8080 |
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"] |
||||
|
||||
# Start Nocodb |
||||
CMD ["/usr/src/appEntry/start.sh"] |
@ -0,0 +1,73 @@
|
||||
<p align="center"> |
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a> |
||||
</p> |
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 |
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest |
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p> |
||||
<p align="center"> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a> |
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a> |
||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a> |
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a> |
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a> |
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a> |
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a> |
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a> |
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a> |
||||
</p> |
||||
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer) |
||||
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)--> |
||||
|
||||
## Description |
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. |
||||
|
||||
## Installation |
||||
|
||||
```bash |
||||
$ npm install |
||||
``` |
||||
|
||||
## Running the app |
||||
|
||||
```bash |
||||
# development |
||||
$ npm run start |
||||
|
||||
# watch mode |
||||
$ npm run start:dev |
||||
|
||||
# production mode |
||||
$ npm run start:prod |
||||
``` |
||||
|
||||
## Test |
||||
|
||||
```bash |
||||
# unit tests |
||||
$ npm run test |
||||
|
||||
# e2e tests |
||||
$ npm run test:e2e |
||||
|
||||
# test coverage |
||||
$ npm run test:cov |
||||
``` |
||||
|
||||
## Support |
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). |
||||
|
||||
## Stay in touch |
||||
|
||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) |
||||
- Website - [https://nestjs.com](https://nestjs.com/) |
||||
- Twitter - [@nestframework](https://twitter.com/nestframework) |
||||
|
||||
## License |
||||
|
||||
Nest is [MIT licensed](LICENSE). |
@ -0,0 +1,32 @@
|
||||
#!/bin/sh |
||||
|
||||
FILE="/usr/src/app/package.json" |
||||
#sleep 5 |
||||
|
||||
if [ ! -z "${NC_TOOL_DIR}" ]; then |
||||
mkdir -p $NC_TOOL_DIR |
||||
fi |
||||
|
||||
if [[ ! -z "${AWS_ACCESS_KEY_ID}" && ! -z "${AWS_SECRET_ACCESS_KEY}" && ! -z "${AWS_BUCKET}" && ! -z "${AWS_BUCKET_PATH}" ]]; then |
||||
|
||||
if [ -f "${NC_TOOL_DIR}noco.db" ] |
||||
then |
||||
rm "${NC_TOOL_DIR}noco.db" |
||||
rm "${NC_TOOL_DIR}noco.db-shm" |
||||
rm "${NC_TOOL_DIR}noco.db-wal" |
||||
fi |
||||
|
||||
/usr/src/appEntry/litestream restore -o "${NC_TOOL_DIR}noco.db" s3://$AWS_BUCKET/$AWS_BUCKET_PATH; |
||||
if [ ! -f "${NC_TOOL_DIR}noco.db" ] |
||||
then |
||||
touch "${NC_TOOL_DIR}noco.db" |
||||
fi |
||||
/usr/src/appEntry/litestream replicate "${NC_TOOL_DIR}noco.db" s3://$AWS_BUCKET/$AWS_BUCKET_PATH & |
||||
fi |
||||
|
||||
if [ ! -f "$FILE" ] |
||||
then |
||||
tar -xzf /usr/src/appEntry/app.tar.gz -C /usr/src/app/ |
||||
fi |
||||
|
||||
node docker/main.js |
@ -0,0 +1,14 @@
|
||||
#!/bin/sh |
||||
|
||||
FILE="/usr/src/app/package.json" |
||||
|
||||
if [ ! -z "${NC_TOOL_DIR}" ]; then |
||||
mkdir -p $NC_TOOL_DIR |
||||
fi |
||||
|
||||
if [ ! -f "$FILE" ] |
||||
then |
||||
tar -xzf /usr/src/appEntry/app.tar.gz -C /usr/src/app/ |
||||
fi |
||||
|
||||
node docker/index.js |
@ -0,0 +1,10 @@
|
||||
#!/bin/sh |
||||
|
||||
FILE="/usr/src/app/package.json" |
||||
|
||||
if [ ! -f "$FILE" ] |
||||
then |
||||
tar -xzf /usr/src/appEntry/app.tar.gz -C /usr/src/app/ |
||||
fi |
||||
|
||||
node docker/main.js |
@ -0,0 +1,46 @@
|
||||
const nodeExternals = require('webpack-node-externals'); |
||||
const webpack = require('webpack') |
||||
const TerserPlugin = require('terser-webpack-plugin'); |
||||
|
||||
module.exports = { |
||||
entry: './src/run/dockerEntry.ts', |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.tsx?$/, |
||||
exclude: /node_modules/, |
||||
use: { |
||||
loader: 'ts-loader', |
||||
options: { |
||||
transpileOnly: true, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
resolve: { |
||||
extensions: ['.tsx', '.ts', '.js', '.json'], |
||||
}, |
||||
output: { |
||||
path: require('path').resolve("./docker"), |
||||
filename: "main.js", |
||||
library: 'libs', |
||||
libraryTarget: 'umd', |
||||
globalObject: "typeof self !== 'undefined' ? self : this" |
||||
}, |
||||
optimization: { |
||||
minimize: true, //Update this to true or false
|
||||
minimizer: [new TerserPlugin()], |
||||
nodeEnv:false |
||||
}, |
||||
externals: [nodeExternals()], |
||||
plugins: [ |
||||
new webpack.EnvironmentPlugin([ |
||||
'EE' |
||||
]), |
||||
], |
||||
target: 'node', |
||||
node: { |
||||
__dirname: false, |
||||
}, |
||||
}; |
@ -0,0 +1,8 @@
|
||||
{ |
||||
"$schema": "https://json.schemastore.org/nest-cli", |
||||
"collection": "@nestjs/schematics", |
||||
"sourceRoot": "src", |
||||
"compilerOptions": { |
||||
"deleteOutDir": true |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,196 @@
|
||||
{ |
||||
"name": "nocodb-nest", |
||||
"version": "0.0.1", |
||||
"description": "NocoDB Backend (Nest)", |
||||
"main": "dist/bundle.js", |
||||
"author": { |
||||
"name": "NocoDB Inc", |
||||
"url": "https://nocodb.com/" |
||||
}, |
||||
"homepage": "https://github.com/nocodb/nocodb", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://github.com/nocodb/nocodb.git" |
||||
}, |
||||
"bugs": { |
||||
"url": "https://github.com/nocodb/nocodb/issues" |
||||
}, |
||||
"private": true, |
||||
"license": "AGPL-3.0-or-later", |
||||
"scripts": { |
||||
"build": "nest build", |
||||
"build:obfuscate": "EE=true webpack --config webpack.config.js", |
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", |
||||
"start": "nest start", |
||||
"start:dev": "nest start --watch", |
||||
"start:debug": "nest start --debug --watch", |
||||
"start:prod": "node dist/main", |
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", |
||||
"test": "jest", |
||||
"test:watch": "jest --watch", |
||||
"test:cov": "jest --coverage", |
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", |
||||
"test:e2e": "jest --config ./test/jest-e2e.json", |
||||
"watch:run:playwright": "rm -f ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", |
||||
"watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", |
||||
"watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"", |
||||
"test:unit": "cross-env TS_NODE_PROJECT=./tests/unit/tsconfig.json mocha -r ts-node/register tests/unit/index.test.ts --recursive --timeout 300000 --exit --delay", |
||||
"test:unit:pg": "cp tests/unit/.pg.env tests/unit/.env; cross-env TS_NODE_PROJECT=./tests/unit/tsconfig.json mocha -r ts-node/register tests/unit/index.test.ts --recursive --timeout 300000 --exit --delay", |
||||
"docker:build": "EE=\"true-xc-test\" webpack --config docker/webpack.config.js" |
||||
}, |
||||
"dependencies": { |
||||
"@google-cloud/storage": "^5.7.2", |
||||
"@graphql-tools/merge": "^6.0.12", |
||||
"@nestjs/common": "^9.4.0", |
||||
"@nestjs/core": "^9.4.0", |
||||
"@nestjs/jwt": "^10.0.3", |
||||
"@nestjs/mapped-types": "*", |
||||
"@nestjs/passport": "^9.0.3", |
||||
"@nestjs/platform-express": "^9.4.0", |
||||
"@nestjs/serve-static": "^3.0.1", |
||||
"@sentry/node": "^6.3.5", |
||||
"@types/chai": "^4.2.12", |
||||
"airtable": "^0.11.3", |
||||
"ajv": "^8.12.0", |
||||
"ajv-formats": "^2.1.1", |
||||
"archiver": "^5.0.2", |
||||
"auto-bind": "^4.0.0", |
||||
"aws-sdk": "^2.829.0", |
||||
"axios": "^0.21.1", |
||||
"bcryptjs": "^2.4.3", |
||||
"body-parser": "^1.19.0", |
||||
"boxen": "^5.0.0", |
||||
"bullmq": "^1.81.1", |
||||
"clear": "^0.1.0", |
||||
"colors": "^1.4.0", |
||||
"compare-versions": "^6.0.0-rc.1", |
||||
"cookie-parser": "^1.4.5", |
||||
"cors": "^2.8.5", |
||||
"cron": "^1.8.2", |
||||
"crypto-js": "^4.0.0", |
||||
"dataloader": "^2.0.0", |
||||
"dayjs": "^1.8.34", |
||||
"debug": "^4.2.0", |
||||
"dotenv": "^8.2.0", |
||||
"ejs": "^3.1.3", |
||||
"emittery": "^0.7.1", |
||||
"express": "^4.17.1", |
||||
"express-graphql": "^0.11.0", |
||||
"extract-zip": "^2.0.1", |
||||
"fast-levenshtein": "^2.0.6", |
||||
"fs-extra": "^9.0.1", |
||||
"glob": "^7.1.6", |
||||
"graphql": "^15.3.0", |
||||
"graphql-depth-limit": "^1.1.0", |
||||
"graphql-type-json": "^0.3.2", |
||||
"handlebars": "^4.7.6", |
||||
"import-fresh": "^3.2.1", |
||||
"inflection": "^1.12.0", |
||||
"ioredis": "^4.28.5", |
||||
"ioredis-mock": "^7.1.0", |
||||
"is-docker": "^2.2.1", |
||||
"isomorphic-dompurify": "^0.19.0", |
||||
"jsep": "^1.3.6", |
||||
"jsonfile": "^6.1.0", |
||||
"jsonwebtoken": "^9.0.0", |
||||
"knex": "2.2.0", |
||||
"lodash": "^4.17.19", |
||||
"lru-cache": "^6.0.0", |
||||
"mailersend": "^1.1.0", |
||||
"minio": "^7.0.18", |
||||
"mkdirp": "^2.1.3", |
||||
"morgan": "^1.10.0", |
||||
"mssql": "^6.2.0", |
||||
"multer": "^1.4.4", |
||||
"mysql2": "^3.2.0", |
||||
"nanoid": "^3.1.20", |
||||
"nc-help": "^0.2.87", |
||||
"nc-lib-gui": "0.106.0", |
||||
"nc-plugin": "0.1.2", |
||||
"ncp": "^2.0.0", |
||||
"nocodb-sdk": "file:../nocodb-sdk", |
||||
"nodemailer": "^6.4.10", |
||||
"object-hash": "^3.0.0", |
||||
"os-locale": "^6.0.2", |
||||
"papaparse": "^5.3.1", |
||||
"parse-database-url": "^0.3.0", |
||||
"passport": "^0.4.1", |
||||
"passport-auth-token": "^1.0.1", |
||||
"passport-custom": "^1.1.1", |
||||
"passport-github": "^1.1.0", |
||||
"passport-google-oauth20": "^2.0.0", |
||||
"passport-jwt": "^4.0.1", |
||||
"passport-local": "^1.0.0", |
||||
"pg": "^8.10.0", |
||||
"reflect-metadata": "^0.1.13", |
||||
"request": "^2.88.2", |
||||
"request-ip": "^2.1.3", |
||||
"rmdir": "^1.2.0", |
||||
"rxjs": "^7.2.0", |
||||
"slash": "^3.0.0", |
||||
"socket.io": "^4.4.1", |
||||
"sqlite3": "^5.1.6", |
||||
"tedious": "^15.0.0", |
||||
"tinycolor2": "^1.4.2", |
||||
"twilio": "^3.55.1", |
||||
"unique-names-generator": "^4.3.1", |
||||
"uuid": "^9.0.0", |
||||
"validator": "^13.1.1", |
||||
"xc-core-ts": "^0.1.0", |
||||
"xlsx": "^0.18.5" |
||||
}, |
||||
"devDependencies": { |
||||
"@nestjs/cli": "^9.0.0", |
||||
"@nestjs/schematics": "^9.0.0", |
||||
"@nestjs/testing": "^9.0.0", |
||||
"@nestjsplus/dyn-schematics": "^1.0.12", |
||||
"@types/express": "^4.17.13", |
||||
"@types/jest": "^29.5.0", |
||||
"@types/mocha": "^10.0.1", |
||||
"@types/multer": "^1.4.7", |
||||
"@types/node": "18.15.11", |
||||
"@types/passport-google-oauth20": "^2.0.11", |
||||
"@types/passport-jwt": "^3.0.8", |
||||
"@types/supertest": "^2.0.11", |
||||
"@typescript-eslint/eslint-plugin": "^5.0.0", |
||||
"@typescript-eslint/parser": "^5.0.0", |
||||
"chai": "^4.2.0", |
||||
"copy-webpack-plugin": "^11.0.0", |
||||
"cross-env": "^7.0.3", |
||||
"eslint": "^7.8.0", |
||||
"eslint-config-prettier": "^6.15.0", |
||||
"eslint-plugin-eslint-comments": "^3.2.0", |
||||
"eslint-plugin-functional": "^3.0.2", |
||||
"eslint-plugin-import": "^2.25.2", |
||||
"eslint-plugin-prettier": "^4.0.0", |
||||
"jest": "29.5.0", |
||||
"mocha": "^10.1.0", |
||||
"nodemon": "^2.0.22", |
||||
"prettier": "^2.7.1", |
||||
"source-map-support": "^0.5.20", |
||||
"supertest": "^6.1.3", |
||||
"ts-jest": "29.0.5", |
||||
"ts-loader": "^9.2.3", |
||||
"ts-node": "^10.0.0", |
||||
"tsconfig-paths": "4.2.0", |
||||
"typescript": "^4.7.4", |
||||
"webpack-cli": "^5.0.1" |
||||
}, |
||||
"jest": { |
||||
"moduleFileExtensions": [ |
||||
"js", |
||||
"json", |
||||
"ts" |
||||
], |
||||
"rootDir": "src", |
||||
"testRegex": ".*\\.spec\\.ts$", |
||||
"transform": { |
||||
"^.+\\.(t|j)s$": "ts-jest" |
||||
}, |
||||
"collectCoverageFrom": [ |
||||
"**/*.(t|j)s" |
||||
], |
||||
"coverageDirectory": "../coverage", |
||||
"testEnvironment": "node" |
||||
} |
||||
} |
@ -0,0 +1,288 @@
|
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Montserrat'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
@ -0,0 +1,336 @@
|
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxFIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxMIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxEIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxLIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxHIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxGIzIXKMnyrYk.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 100; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgVxIIzIXKMny.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 500; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
/* cyrillic-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCRc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
||||
} |
||||
/* cyrillic */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfABc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
||||
} |
||||
/* greek-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCBc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+1F00-1FFF; |
||||
} |
||||
/* greek */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0370-03FF; |
||||
} |
||||
/* vietnamese */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfCxc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
||||
} |
||||
/* latin-ext */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfChc4AMP6lbBP.woff2) format('woff2'); |
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
/* latin */ |
||||
@font-face { |
||||
font-family: 'Roboto'; |
||||
font-style: normal; |
||||
font-weight: 900; |
||||
src: url(https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBBc4AMP6lQ.woff2) format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.3 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -0,0 +1,117 @@
|
||||
// import * as Sentry from '@sentry/node';
|
||||
import { NestFactory } from '@nestjs/core'; |
||||
import clear from 'clear'; |
||||
import * as express from 'express'; |
||||
import NcToolGui from 'nc-lib-gui'; |
||||
import { AppModule } from './app.module'; |
||||
|
||||
import { NC_LICENSE_KEY } from './constants'; |
||||
import Store from './models/Store'; |
||||
import type { Express } from 'express'; |
||||
import type * as http from 'http'; |
||||
|
||||
export default class Noco { |
||||
private static _this: Noco; |
||||
private static ee: boolean; |
||||
public static readonly env: string = '_noco'; |
||||
private static _httpServer: http.Server; |
||||
private static _server: Express; |
||||
|
||||
public static get dashboardUrl(): string { |
||||
let siteUrl = `http://localhost:${process.env.PORT || 8080}`; |
||||
// if (Noco._this?.config?.envs?.[Noco._this?.env]?.publicUrl) {
|
||||
// siteUrl = Noco._this?.config?.envs?.[Noco._this?.env]?.publicUrl;
|
||||
// }
|
||||
if (Noco._this?.config?.envs?.['_noco']?.publicUrl) { |
||||
siteUrl = Noco._this?.config?.envs?.['_noco']?.publicUrl; |
||||
} |
||||
|
||||
return `${siteUrl}${Noco._this?.config?.dashboardPath}`; |
||||
} |
||||
|
||||
public static config: any; |
||||
public readonly router: express.Router; |
||||
public readonly projectRouter: express.Router; |
||||
public static _ncMeta: any; |
||||
public readonly metaMgr: any; |
||||
public readonly metaMgrv2: any; |
||||
public env: string; |
||||
|
||||
private ncToolApi; |
||||
private config: any; |
||||
private requestContext: any; |
||||
|
||||
constructor() { |
||||
process.env.PORT = process.env.PORT || '8080'; |
||||
// todo: move
|
||||
// if env variable NC_MINIMAL_DBS is set, then disable project creation with external sources
|
||||
if (process.env.NC_MINIMAL_DBS) { |
||||
process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED = 'true'; |
||||
} |
||||
|
||||
this.router = express.Router(); |
||||
this.projectRouter = express.Router(); |
||||
|
||||
clear(); |
||||
/******************* prints : end *******************/ |
||||
} |
||||
|
||||
public getConfig(): any { |
||||
return this.config; |
||||
} |
||||
|
||||
public getToolDir(): string { |
||||
return this.getConfig()?.toolDir; |
||||
} |
||||
|
||||
public addToContext(context: any) { |
||||
this.requestContext = context; |
||||
} |
||||
|
||||
public static get ncMeta(): any { |
||||
return this._ncMeta; |
||||
} |
||||
|
||||
public get ncMeta(): any { |
||||
return Noco._ncMeta; |
||||
} |
||||
|
||||
public static getConfig(): any { |
||||
return Noco.config; |
||||
} |
||||
|
||||
public static isEE(): boolean { |
||||
return Noco.ee; |
||||
} |
||||
|
||||
public static async loadEEState(): Promise<boolean> { |
||||
try { |
||||
return (Noco.ee = !!(await Store.get(NC_LICENSE_KEY))?.value); |
||||
} catch {} |
||||
return (Noco.ee = false); |
||||
} |
||||
|
||||
static async init(param: any, httpServer: http.Server, server: Express) { |
||||
this._httpServer = httpServer; |
||||
this._server = server; |
||||
|
||||
const nestApp = await NestFactory.create(AppModule); |
||||
nestApp.use( |
||||
express.json({ limit: process.env.NC_REQUEST_BODY_SIZE || '50mb' }), |
||||
); |
||||
await nestApp.init(); |
||||
|
||||
const dashboardPath = process.env.NC_DASHBOARD_URL || '/dashboard'; |
||||
server.use(NcToolGui.expressMiddleware(dashboardPath)); |
||||
server.get('/', (_req, res) => res.redirect(dashboardPath)); |
||||
return nestApp.getHttpAdapter().getInstance(); |
||||
} |
||||
|
||||
public static get httpServer(): http.Server { |
||||
return Noco._httpServer; |
||||
} |
||||
|
||||
public static get server(): Express { |
||||
return Noco._server; |
||||
} |
||||
} |
@ -0,0 +1,85 @@
|
||||
import { Module, RequestMethod } from '@nestjs/common'; |
||||
import { APP_FILTER } from '@nestjs/core'; |
||||
import { Connection } from './connection/connection'; |
||||
import { GlobalExceptionFilter } from './filters/global-exception/global-exception.filter'; |
||||
import NcPluginMgrv2 from './helpers/NcPluginMgrv2'; |
||||
import { GlobalMiddleware } from './middlewares/global/global.middleware'; |
||||
import { GuiMiddleware } from './middlewares/gui/gui.middleware'; |
||||
import { PublicMiddleware } from './middlewares/public/public.middleware'; |
||||
import { DatasModule } from './modules/datas/datas.module'; |
||||
import { AuthService } from './services/auth.service'; |
||||
import { UsersModule } from './modules/users/users.module'; |
||||
import { MetaService } from './meta/meta.service'; |
||||
import Noco from './Noco'; |
||||
import { TestModule } from './modules/test/test.module'; |
||||
import { GlobalModule } from './modules/global/global.module'; |
||||
import { LocalStrategy } from './strategies/local.strategy'; |
||||
import { AuthTokenStrategy } from './strategies/authtoken.strategy/authtoken.strategy'; |
||||
import { BaseViewStrategy } from './strategies/base-view.strategy/base-view.strategy'; |
||||
import NcUpgrader from './version-upgrader/NcUpgrader'; |
||||
import { MetasModule } from './modules/metas/metas.module'; |
||||
import NocoCache from './cache/NocoCache'; |
||||
import type { |
||||
MiddlewareConsumer, |
||||
OnApplicationBootstrap, |
||||
} from '@nestjs/common'; |
||||
|
||||
@Module({ |
||||
imports: [ |
||||
GlobalModule, |
||||
UsersModule, |
||||
...(process.env['PLAYWRIGHT_TEST'] === 'true' ? [TestModule] : []), |
||||
MetasModule, |
||||
DatasModule, |
||||
], |
||||
controllers: [], |
||||
providers: [ |
||||
AuthService, |
||||
{ |
||||
provide: APP_FILTER, |
||||
useClass: GlobalExceptionFilter, |
||||
}, |
||||
LocalStrategy, |
||||
AuthTokenStrategy, |
||||
BaseViewStrategy, |
||||
], |
||||
}) |
||||
export class AppModule implements OnApplicationBootstrap { |
||||
constructor( |
||||
private readonly connection: Connection, |
||||
private readonly metaService: MetaService, |
||||
) {} |
||||
|
||||
// Global Middleware
|
||||
configure(consumer: MiddlewareConsumer) { |
||||
consumer |
||||
.apply(GuiMiddleware) |
||||
.forRoutes({ path: '*', method: RequestMethod.GET }) |
||||
.apply(PublicMiddleware) |
||||
.forRoutes({ path: '*', method: RequestMethod.GET }) |
||||
.apply(GlobalMiddleware) |
||||
.forRoutes({ path: '*', method: RequestMethod.ALL }); |
||||
} |
||||
|
||||
// app init
|
||||
async onApplicationBootstrap(): Promise<void> { |
||||
process.env.NC_VERSION = '0105004'; |
||||
|
||||
await NocoCache.init(); |
||||
|
||||
await this.connection.init(); |
||||
await this.metaService.init(); |
||||
|
||||
// todo: remove
|
||||
// temporary hack
|
||||
Noco._ncMeta = this.metaService; |
||||
Noco.config = this.connection.config; |
||||
|
||||
// init plugin manager
|
||||
await NcPluginMgrv2.init(Noco.ncMeta); |
||||
await Noco.loadEEState(); |
||||
|
||||
// run upgrader
|
||||
await NcUpgrader.upgrade({ ncMeta: Noco._ncMeta }); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
|
||||
export default abstract class CacheMgr { |
||||
public abstract get(key: string, type: string): Promise<any>; |
||||
public abstract set(key: string, value: any): Promise<any>; |
||||
public abstract del(key: string): Promise<any>; |
||||
public abstract getAll(pattern: string): Promise<any[]>; |
||||
public abstract delAll(scope: string, pattern: string): Promise<any[]>; |
||||
public abstract getList( |
||||
scope: string, |
||||
list: string[], |
||||
): Promise<{ |
||||
list: any[]; |
||||
isNoneList: boolean; |
||||
}>; |
||||
public abstract setList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
list: any[], |
||||
): Promise<boolean>; |
||||
public abstract deepDel( |
||||
scope: string, |
||||
key: string, |
||||
direction: string, |
||||
): Promise<boolean>; |
||||
public abstract appendToList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
key: string, |
||||
): Promise<boolean>; |
||||
public abstract destroy(): Promise<boolean>; |
||||
public abstract export(): Promise<any>; |
||||
} |
@ -0,0 +1,112 @@
|
||||
import { CacheGetType } from '../utils/globals'; |
||||
import RedisCacheMgr from './RedisCacheMgr'; |
||||
import RedisMockCacheMgr from './RedisMockCacheMgr'; |
||||
import type CacheMgr from './CacheMgr'; |
||||
|
||||
export default class NocoCache { |
||||
private static client: CacheMgr; |
||||
private static cacheDisabled: boolean; |
||||
private static prefix: string; |
||||
|
||||
public static init() { |
||||
this.cacheDisabled = (process.env.NC_DISABLE_CACHE || false) === 'true'; |
||||
if (this.cacheDisabled) { |
||||
return; |
||||
} |
||||
if (process.env.NC_REDIS_URL) { |
||||
this.client = new RedisCacheMgr(process.env.NC_REDIS_URL); |
||||
} else { |
||||
this.client = new RedisMockCacheMgr(); |
||||
} |
||||
|
||||
// TODO(cache): fetch orgs once it's implemented
|
||||
const orgs = 'noco'; |
||||
this.prefix = `nc:${orgs}`; |
||||
} |
||||
|
||||
public static async set(key, value): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.set(`${this.prefix}:${key}`, value); |
||||
} |
||||
|
||||
public static async get(key, type): Promise<any> { |
||||
if (this.cacheDisabled) { |
||||
if (type === CacheGetType.TYPE_ARRAY) return Promise.resolve([]); |
||||
else if (type === CacheGetType.TYPE_OBJECT) return Promise.resolve(null); |
||||
return Promise.resolve(null); |
||||
} |
||||
return this.client.get(`${this.prefix}:${key}`, type); |
||||
} |
||||
|
||||
public static async getAll(pattern: string): Promise<any[]> { |
||||
if (this.cacheDisabled) return Promise.resolve([]); |
||||
return this.client.getAll(`${this.prefix}:${pattern}`); |
||||
} |
||||
|
||||
public static async del(key): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.del(`${this.prefix}:${key}`); |
||||
} |
||||
|
||||
public static async delAll(scope: string, pattern: string): Promise<any[]> { |
||||
if (this.cacheDisabled) return Promise.resolve([]); |
||||
return this.client.delAll(scope, pattern); |
||||
} |
||||
|
||||
public static async getList( |
||||
scope: string, |
||||
subKeys: string[], |
||||
): Promise<{ |
||||
list: any[]; |
||||
isNoneList: boolean; |
||||
}> { |
||||
if (this.cacheDisabled) |
||||
return Promise.resolve({ |
||||
list: [], |
||||
isNoneList: false, |
||||
}); |
||||
return this.client.getList(scope, subKeys); |
||||
} |
||||
|
||||
public static async setList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
list: any[], |
||||
): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.setList(scope, subListKeys, list); |
||||
} |
||||
|
||||
public static async deepDel( |
||||
scope: string, |
||||
key: string, |
||||
direction: string, |
||||
): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.deepDel(scope, key, direction); |
||||
} |
||||
|
||||
public static async appendToList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
|
||||
key: string, |
||||
): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.appendToList( |
||||
scope, |
||||
subListKeys, |
||||
`${this.prefix}:${key}`, |
||||
); |
||||
} |
||||
|
||||
public static async destroy(): Promise<boolean> { |
||||
if (this.cacheDisabled) return Promise.resolve(true); |
||||
return this.client.destroy(); |
||||
} |
||||
|
||||
public static async export(): Promise<any> { |
||||
if (this.cacheDisabled) return Promise.resolve({}); |
||||
return this.client.export(); |
||||
} |
||||
} |
@ -0,0 +1,277 @@
|
||||
import debug from 'debug'; |
||||
import Redis from 'ioredis'; |
||||
import { CacheDelDirection, CacheGetType, CacheScope } from '../utils/globals'; |
||||
import CacheMgr from './CacheMgr'; |
||||
|
||||
const log = debug('nc:cache'); |
||||
|
||||
export default class RedisCacheMgr extends CacheMgr { |
||||
client: any; |
||||
prefix: string; |
||||
|
||||
constructor(config: any) { |
||||
super(); |
||||
this.client = new Redis(config); |
||||
// flush the existing db with selected key (Default: 0)
|
||||
this.client.flushdb(); |
||||
|
||||
// TODO(cache): fetch orgs once it's implemented
|
||||
const orgs = 'noco'; |
||||
this.prefix = `nc:${orgs}`; |
||||
} |
||||
|
||||
// avoid circular structure to JSON
|
||||
getCircularReplacer = () => { |
||||
const seen = new WeakSet(); |
||||
return (_, value) => { |
||||
if (typeof value === 'object' && value !== null) { |
||||
if (seen.has(value)) { |
||||
return; |
||||
} |
||||
seen.add(value); |
||||
} |
||||
return value; |
||||
}; |
||||
}; |
||||
|
||||
// @ts-ignore
|
||||
async del(key: string): Promise<any> { |
||||
log(`RedisCacheMgr::del: deleting key ${key}`); |
||||
return this.client.del(key); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async get(key: string, type: string, config?: any): Promise<any> { |
||||
log(`RedisCacheMgr::get: getting key ${key} with type ${type}`); |
||||
if (type === CacheGetType.TYPE_ARRAY) { |
||||
return this.client.smembers(key); |
||||
} else if (type === CacheGetType.TYPE_OBJECT) { |
||||
const res = await this.client.get(key); |
||||
try { |
||||
const o = JSON.parse(res); |
||||
if (typeof o === 'object') { |
||||
if ( |
||||
o && |
||||
Object.keys(o).length === 0 && |
||||
Object.getPrototypeOf(o) === Object.prototype |
||||
) { |
||||
log(`RedisCacheMgr::get: object is empty!`); |
||||
} |
||||
return Promise.resolve(o); |
||||
} |
||||
} catch (e) { |
||||
return Promise.resolve(res); |
||||
} |
||||
return Promise.resolve(res); |
||||
} else if (type === CacheGetType.TYPE_STRING) { |
||||
return await this.client.get(key); |
||||
} |
||||
log(`Invalid CacheGetType: ${type}`); |
||||
return Promise.resolve(false); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async set(key: string, value: any): Promise<any> { |
||||
if (typeof value !== 'undefined' && value) { |
||||
log(`RedisCacheMgr::set: setting key ${key} with value ${value}`); |
||||
if (typeof value === 'object') { |
||||
if (Array.isArray(value) && value.length) { |
||||
return this.client.sadd(key, value); |
||||
} |
||||
return this.client.set( |
||||
key, |
||||
JSON.stringify(value, this.getCircularReplacer()), |
||||
); |
||||
} |
||||
return this.client.set(key, value); |
||||
} else { |
||||
log(`RedisCacheMgr::set: value is empty for ${key}. Skipping ...`); |
||||
return Promise.resolve(true); |
||||
} |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async getAll(pattern: string): Promise<any> { |
||||
return this.client.hgetall(pattern); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async delAll(scope: string, pattern: string): Promise<any[]> { |
||||
// Example: nc:<orgs>:model:*:<id>
|
||||
const keys = await this.client.keys(`${this.prefix}:${scope}:${pattern}`); |
||||
log( |
||||
`RedisCacheMgr::delAll: deleting all keys with pattern ${this.prefix}:${scope}:${pattern}`, |
||||
); |
||||
await Promise.all( |
||||
keys.map(async (k) => { |
||||
await this.deepDel(scope, k, CacheDelDirection.CHILD_TO_PARENT); |
||||
}), |
||||
); |
||||
return Promise.all( |
||||
keys.map(async (k) => { |
||||
await this.del(k); |
||||
}), |
||||
); |
||||
} |
||||
|
||||
async getList( |
||||
scope: string, |
||||
subKeys: string[], |
||||
): Promise<{ |
||||
list: any[]; |
||||
isNoneList: boolean; |
||||
}> { |
||||
// remove null from arrays
|
||||
subKeys = subKeys.filter((k) => k); |
||||
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const key = |
||||
subKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`; |
||||
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
|
||||
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; |
||||
log(`RedisCacheMgr::getList: getting list with key ${key}`); |
||||
const isNoneList = arr.length && arr[0] === 'NONE'; |
||||
|
||||
if (isNoneList) { |
||||
return Promise.resolve({ |
||||
list: [], |
||||
isNoneList, |
||||
}); |
||||
} |
||||
|
||||
return { |
||||
list: await Promise.all( |
||||
arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)), |
||||
), |
||||
isNoneList, |
||||
}; |
||||
} |
||||
|
||||
async setList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
list: any[], |
||||
): Promise<boolean> { |
||||
// remove null from arrays
|
||||
subListKeys = subListKeys.filter((k) => k); |
||||
// construct key for List
|
||||
// e.g. nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const listKey = |
||||
subListKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; |
||||
if (!list.length) { |
||||
// Set NONE here so that it won't hit the DB on each page load
|
||||
return this.set(listKey, ['NONE']); |
||||
} |
||||
// fetch existing list
|
||||
const listOfGetKeys = |
||||
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
for (const o of list) { |
||||
// construct key for Get
|
||||
// e.g. nc:<orgs>:<scope>:<model_id_1>
|
||||
let getKey = `${this.prefix}:${scope}:${o.id}`; |
||||
// special case - MODEL_ROLE_VISIBILITY
|
||||
if (scope === CacheScope.MODEL_ROLE_VISIBILITY) { |
||||
getKey = `${this.prefix}:${scope}:${o.id}:${o.role}`; |
||||
} |
||||
// set Get Key
|
||||
log(`RedisCacheMgr::setList: setting key ${getKey}`); |
||||
await this.set(getKey, JSON.stringify(o, this.getCircularReplacer())); |
||||
// push Get Key to List
|
||||
listOfGetKeys.push(getKey); |
||||
} |
||||
// set List Key
|
||||
log(`RedisCacheMgr::setList: setting list with key ${listKey}`); |
||||
return this.set(listKey, listOfGetKeys); |
||||
} |
||||
|
||||
async deepDel( |
||||
scope: string, |
||||
key: string, |
||||
direction: string, |
||||
): Promise<boolean> { |
||||
key = `${this.prefix}:${key}`; |
||||
log(`RedisCacheMgr::deepDel: choose direction ${direction}`); |
||||
if (direction === CacheDelDirection.CHILD_TO_PARENT) { |
||||
// given a child key, delete all keys in corresponding parent lists
|
||||
const scopeList = await this.client.keys(`${this.prefix}:${scope}*list`); |
||||
for (const listKey of scopeList) { |
||||
// get target list
|
||||
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
if (!list.length) { |
||||
continue; |
||||
} |
||||
// remove target Key
|
||||
list = list.filter((k) => k !== key); |
||||
// delete list
|
||||
log(`RedisCacheMgr::deepDel: remove listKey ${listKey}`); |
||||
await this.del(listKey); |
||||
if (list.length) { |
||||
// set target list
|
||||
log(`RedisCacheMgr::deepDel: set key ${listKey}`); |
||||
await this.del(listKey); |
||||
await this.set(listKey, list); |
||||
} |
||||
} |
||||
log(`RedisCacheMgr::deepDel: remove key ${key}`); |
||||
return await this.del(key); |
||||
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) { |
||||
// given a list key, delete all the children
|
||||
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY); |
||||
// delete each child key
|
||||
await Promise.all(listOfChildren.map(async (k) => await this.del(k))); |
||||
// delete list key
|
||||
return await this.del(key); |
||||
} else { |
||||
log(`Invalid deepDel direction found : ${direction}`); |
||||
return Promise.resolve(false); |
||||
} |
||||
} |
||||
|
||||
async appendToList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
key: string, |
||||
): Promise<boolean> { |
||||
// remove null from arrays
|
||||
subListKeys = subListKeys.filter((k) => k); |
||||
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const listKey = |
||||
subListKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; |
||||
log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`); |
||||
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
if (list.length && list[0] === 'NONE') { |
||||
list = []; |
||||
await this.del(listKey); |
||||
} |
||||
list.push(key); |
||||
return this.set(listKey, list); |
||||
} |
||||
|
||||
async destroy(): Promise<boolean> { |
||||
log('RedisCacheMgr::destroy: destroy redis'); |
||||
return this.client.flushdb(); |
||||
} |
||||
|
||||
async export(): Promise<any> { |
||||
log('RedisCacheMgr::export: export data'); |
||||
const data = await this.client.keys('*'); |
||||
const res = {}; |
||||
return await Promise.all( |
||||
data.map(async (k) => { |
||||
res[k] = await this.get( |
||||
k, |
||||
k.slice(-4) === 'list' |
||||
? CacheGetType.TYPE_ARRAY |
||||
: CacheGetType.TYPE_OBJECT, |
||||
); |
||||
}), |
||||
).then(() => { |
||||
return res; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,277 @@
|
||||
import debug from 'debug'; |
||||
import Redis from 'ioredis-mock'; |
||||
import { CacheDelDirection, CacheGetType, CacheScope } from '../utils/globals'; |
||||
import CacheMgr from './CacheMgr'; |
||||
const log = debug('nc:cache'); |
||||
|
||||
export default class RedisMockCacheMgr extends CacheMgr { |
||||
client: any; |
||||
prefix: string; |
||||
|
||||
constructor() { |
||||
super(); |
||||
this.client = new Redis(); |
||||
// flush the existing db with selected key (Default: 0)
|
||||
this.client.flushdb(); |
||||
|
||||
// TODO(cache): fetch orgs once it's implemented
|
||||
const orgs = 'noco'; |
||||
this.prefix = `nc:${orgs}`; |
||||
} |
||||
|
||||
// avoid circular structure to JSON
|
||||
getCircularReplacer = () => { |
||||
const seen = new WeakSet(); |
||||
return (_, value) => { |
||||
if (typeof value === 'object' && value !== null) { |
||||
if (seen.has(value)) { |
||||
return; |
||||
} |
||||
seen.add(value); |
||||
} |
||||
return value; |
||||
}; |
||||
}; |
||||
|
||||
// @ts-ignore
|
||||
async del(key: string): Promise<any> { |
||||
log(`RedisMockCacheMgr::del: deleting key ${key}`); |
||||
return this.client.del(key); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async get(key: string, type: string, config?: any): Promise<any> { |
||||
log(`RedisMockCacheMgr::get: getting key ${key} with type ${type}`); |
||||
if (type === CacheGetType.TYPE_ARRAY) { |
||||
return this.client.smembers(key); |
||||
} else if (type === CacheGetType.TYPE_OBJECT) { |
||||
const res = await this.client.get(key); |
||||
try { |
||||
const o = JSON.parse(res); |
||||
if (typeof o === 'object') { |
||||
if ( |
||||
o && |
||||
Object.keys(o).length === 0 && |
||||
Object.getPrototypeOf(o) === Object.prototype |
||||
) { |
||||
log(`RedisMockCacheMgr::get: object is empty!`); |
||||
} |
||||
return Promise.resolve(o); |
||||
} |
||||
} catch (e) { |
||||
return Promise.resolve(res); |
||||
} |
||||
return Promise.resolve(res); |
||||
} else if (type === CacheGetType.TYPE_STRING) { |
||||
return await this.client.get(key); |
||||
} |
||||
log(`Invalid CacheGetType: ${type}`); |
||||
return Promise.resolve(false); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async set(key: string, value: any): Promise<any> { |
||||
if (typeof value !== 'undefined' && value) { |
||||
log(`RedisMockCacheMgr::set: setting key ${key} with value ${value}`); |
||||
if (typeof value === 'object') { |
||||
if (Array.isArray(value) && value.length) { |
||||
return this.client.sadd(key, value); |
||||
} |
||||
return this.client.set( |
||||
key, |
||||
JSON.stringify(value, this.getCircularReplacer()), |
||||
); |
||||
} |
||||
return this.client.set(key, value); |
||||
} else { |
||||
log(`RedisMockCacheMgr::set: value is empty for ${key}. Skipping ...`); |
||||
return Promise.resolve(true); |
||||
} |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async getAll(pattern: string): Promise<any> { |
||||
return this.client.hgetall(pattern); |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
async delAll(scope: string, pattern: string): Promise<any[]> { |
||||
// Example: nc:<orgs>:model:*:<id>
|
||||
const keys = await this.client.keys(`${this.prefix}:${scope}:${pattern}`); |
||||
log( |
||||
`RedisMockCacheMgr::delAll: deleting all keys with pattern ${this.prefix}:${scope}:${pattern}`, |
||||
); |
||||
await Promise.all( |
||||
keys.map( |
||||
async (k) => |
||||
await this.deepDel(scope, k, CacheDelDirection.CHILD_TO_PARENT), |
||||
), |
||||
); |
||||
return Promise.all( |
||||
keys.map(async (k) => { |
||||
await this.del(k); |
||||
}), |
||||
); |
||||
} |
||||
|
||||
async getList( |
||||
scope: string, |
||||
subKeys: string[], |
||||
): Promise<{ |
||||
list: any[]; |
||||
isNoneList: boolean; |
||||
}> { |
||||
// remove null from arrays
|
||||
subKeys = subKeys.filter((k) => k); |
||||
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const key = |
||||
subKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`; |
||||
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
|
||||
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || []; |
||||
log(`RedisMockCacheMgr::getList: getting list with key ${key}`); |
||||
const isNoneList = arr.length && arr[0] === 'NONE'; |
||||
|
||||
if (isNoneList) { |
||||
return Promise.resolve({ |
||||
list: [], |
||||
isNoneList, |
||||
}); |
||||
} |
||||
|
||||
return { |
||||
list: await Promise.all( |
||||
arr.map(async (k) => await this.get(k, CacheGetType.TYPE_OBJECT)), |
||||
), |
||||
isNoneList, |
||||
}; |
||||
} |
||||
|
||||
async setList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
list: any[], |
||||
): Promise<boolean> { |
||||
// remove null from arrays
|
||||
subListKeys = subListKeys.filter((k) => k); |
||||
// construct key for List
|
||||
// e.g. nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const listKey = |
||||
subListKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; |
||||
if (!list.length) { |
||||
// Set NONE here so that it won't hit the DB on each page load
|
||||
return this.set(listKey, ['NONE']); |
||||
} |
||||
// fetch existing list
|
||||
const listOfGetKeys = |
||||
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
for (const o of list) { |
||||
// construct key for Get
|
||||
// e.g. nc:<orgs>:<scope>:<model_id_1>
|
||||
let getKey = `${this.prefix}:${scope}:${o.id}`; |
||||
// special case - MODEL_ROLE_VISIBILITY
|
||||
if (scope === CacheScope.MODEL_ROLE_VISIBILITY) { |
||||
getKey = `${this.prefix}:${scope}:${o.id}:${o.role}`; |
||||
} |
||||
// set Get Key
|
||||
log(`RedisMockCacheMgr::setList: setting key ${getKey}`); |
||||
await this.set(getKey, JSON.stringify(o, this.getCircularReplacer())); |
||||
// push Get Key to List
|
||||
listOfGetKeys.push(getKey); |
||||
} |
||||
// set List Key
|
||||
log(`RedisMockCacheMgr::setList: setting list with key ${listKey}`); |
||||
return this.set(listKey, listOfGetKeys); |
||||
} |
||||
|
||||
async deepDel( |
||||
scope: string, |
||||
key: string, |
||||
direction: string, |
||||
): Promise<boolean> { |
||||
key = `${this.prefix}:${key}`; |
||||
log(`RedisMockCacheMgr::deepDel: choose direction ${direction}`); |
||||
if (direction === CacheDelDirection.CHILD_TO_PARENT) { |
||||
// given a child key, delete all keys in corresponding parent lists
|
||||
const scopeList = await this.client.keys(`${this.prefix}:${scope}*list`); |
||||
for (const listKey of scopeList) { |
||||
// get target list
|
||||
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
if (!list.length) { |
||||
continue; |
||||
} |
||||
// remove target Key
|
||||
list = list.filter((k) => k !== key); |
||||
// delete list
|
||||
log(`RedisMockCacheMgr::deepDel: remove listKey ${listKey}`); |
||||
await this.del(listKey); |
||||
if (list.length) { |
||||
// set target list
|
||||
log(`RedisMockCacheMgr::deepDel: set key ${listKey}`); |
||||
await this.del(listKey); |
||||
await this.set(listKey, list); |
||||
} |
||||
} |
||||
log(`RedisMockCacheMgr::deepDel: remove key ${key}`); |
||||
return await this.del(key); |
||||
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) { |
||||
// given a list key, delete all the children
|
||||
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY); |
||||
// delete each child key
|
||||
await Promise.all(listOfChildren.map(async (k) => await this.del(k))); |
||||
// delete list key
|
||||
return await this.del(key); |
||||
} else { |
||||
log(`Invalid deepDel direction found : ${direction}`); |
||||
return Promise.resolve(false); |
||||
} |
||||
} |
||||
|
||||
async appendToList( |
||||
scope: string, |
||||
subListKeys: string[], |
||||
key: string, |
||||
): Promise<boolean> { |
||||
// remove null from arrays
|
||||
subListKeys = subListKeys.filter((k) => k); |
||||
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<base_id_1>:list
|
||||
const listKey = |
||||
subListKeys.length === 0 |
||||
? `${this.prefix}:${scope}:list` |
||||
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`; |
||||
log(`RedisMockCacheMgr::appendToList: append key ${key} to ${listKey}`); |
||||
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || []; |
||||
if (list.length && list[0] === 'NONE') { |
||||
list = []; |
||||
await this.del(listKey); |
||||
} |
||||
list.push(key); |
||||
return this.set(listKey, list); |
||||
} |
||||
|
||||
async destroy(): Promise<boolean> { |
||||
log('RedisMockCacheMgr::destroy: destroy redis'); |
||||
return this.client.flushdb(); |
||||
} |
||||
|
||||
async export(): Promise<any> { |
||||
log('RedisMockCacheMgr::export: export data'); |
||||
const data = await this.client.keys('*'); |
||||
const res = {}; |
||||
return await Promise.all( |
||||
data.map(async (k) => { |
||||
res[k] = await this.get( |
||||
k, |
||||
k.slice(-4) === 'list' |
||||
? CacheGetType.TYPE_ARRAY |
||||
: CacheGetType.TYPE_OBJECT, |
||||
); |
||||
}), |
||||
).then(() => { |
||||
return res; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { Connection } from './knex'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('Knex', () => { |
||||
let provider: Connection; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [Connection], |
||||
}).compile(); |
||||
|
||||
provider = module.get<Connection>(Connection); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(provider).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,37 @@
|
||||
import { Global, Injectable, Scope } from '@nestjs/common'; |
||||
|
||||
import { XKnex } from '../db/CustomKnex'; |
||||
import NcConfigFactory from '../utils/NcConfigFactory'; |
||||
import type * as knex from 'knex'; |
||||
|
||||
@Injectable({ |
||||
scope: Scope.DEFAULT, |
||||
}) |
||||
export class Connection { |
||||
public static knex: knex.Knex; |
||||
public static _config: any; |
||||
|
||||
get knexInstance(): knex.Knex { |
||||
return Connection.knex; |
||||
} |
||||
|
||||
get config(): knex.Knex { |
||||
return Connection._config; |
||||
} |
||||
|
||||
// init metadb connection
|
||||
static async init(): Promise<void> { |
||||
Connection._config = await NcConfigFactory.make(); |
||||
if (!Connection.knex) { |
||||
Connection.knex = XKnex({ |
||||
...this._config.meta.db, |
||||
useNullAsDefault: true, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
// init metadb connection
|
||||
async init(): Promise<void> { |
||||
return await Connection.init(); |
||||
} |
||||
} |
@ -0,0 +1,4 @@
|
||||
export const NC_LICENSE_KEY = 'nc-license-key'; |
||||
export const NC_APP_SETTINGS = 'nc-app-settings'; |
||||
export const NC_ATTACHMENT_FIELD_SIZE = |
||||
+process.env['NC_ATTACHMENT_FIELD_SIZE'] || 20 * 1024 * 1024; // 20 MB
|
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { ApiDocsService } from '../../services/api-docs/api-docs.service'; |
||||
import { ApiDocsController } from './api-docs.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('ApiDocsController', () => { |
||||
let controller: ApiDocsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [ApiDocsController], |
||||
providers: [ApiDocsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<ApiDocsController>(ApiDocsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,43 @@
|
||||
import { |
||||
Controller, |
||||
Get, |
||||
Param, |
||||
Request, |
||||
Response, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { GlobalGuard } from '../../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { ApiDocsService } from '../../services/api-docs/api-docs.service'; |
||||
import getSwaggerHtml from './template/swaggerHtml'; |
||||
import getRedocHtml from './template/redocHtml'; |
||||
|
||||
@Controller() |
||||
export class ApiDocsController { |
||||
constructor(private readonly apiDocsService: ApiDocsService) {} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/swagger.json') |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
@Acl('swaggerJson') |
||||
async swaggerJson(@Param('projectId') projectId: string, @Request() req) { |
||||
const swagger = await this.apiDocsService.swaggerJson({ |
||||
projectId: projectId, |
||||
siteUrl: req.ncSiteUrl, |
||||
}); |
||||
|
||||
return swagger; |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/swagger') |
||||
swaggerHtml(@Param('projectId') projectId: string, @Response() res) { |
||||
res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/redoc') |
||||
redocHtml(@Param('projectId') projectId: string, @Response() res) { |
||||
res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); |
||||
} |
||||
} |
@ -0,0 +1,93 @@
|
||||
export default ({ |
||||
ncSiteUrl, |
||||
}: { |
||||
ncSiteUrl: string; |
||||
}): string => `<!DOCTYPE html>
|
||||
<html> |
||||
<head> |
||||
<title>NocoDB API Documentation</title> |
||||
<!-- needed for adaptive design --> |
||||
<meta charset="utf-8"/> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<link href="${ncSiteUrl}/css/fonts.montserrat.css" rel="stylesheet"> |
||||
<!-- |
||||
Redoc doesn't change outer page styles |
||||
--> |
||||
<style> |
||||
body { |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div id="redoc"></div> |
||||
<script src="${ncSiteUrl}/js/redoc.standalone.min.js"></script> |
||||
<script> |
||||
let initialLocalStorage = {} |
||||
|
||||
try { |
||||
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}'); |
||||
} catch (e) { |
||||
console.error('Failed to parse local storage', e); |
||||
} |
||||
|
||||
const xhttp = new XMLHttpRequest(); |
||||
|
||||
xhttp.open("GET", "./swagger.json"); |
||||
xhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); |
||||
xhttp.setRequestHeader("xc-auth", initialLocalStorage && initialLocalStorage.token); |
||||
|
||||
xhttp.onload = function () { |
||||
const swaggerJson = this.responseText; |
||||
const swagger = JSON.parse(swaggerJson); |
||||
Redoc.init(swagger, { |
||||
scrollYOffset: 50 |
||||
}, document.getElementById('redoc')) |
||||
}; |
||||
|
||||
xhttp.send(); |
||||
</script> |
||||
<script> |
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px') |
||||
const linkEl = document.createElement('a') |
||||
linkEl.setAttribute('href', "http://careers.nocodb.com") |
||||
linkEl.setAttribute('target', '_blank') |
||||
linkEl.setAttribute('class', 'we-are-hiring') |
||||
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀' |
||||
const styleEl = document.createElement('style'); |
||||
styleEl.innerHTML = \` |
||||
.we-are-hiring { |
||||
position: fixed; |
||||
bottom: 50px; |
||||
right: -250px; |
||||
opacity: 0; |
||||
background: orange; |
||||
border-radius: 4px; |
||||
padding: 19px; |
||||
z-index: 200; |
||||
text-decoration: none;
|
||||
text-transform: uppercase; |
||||
color: black; |
||||
transition: 1s opacity, 1s right; |
||||
display: block; |
||||
font-weight: bold; |
||||
}
|
||||
|
||||
.we-are-hiring.active { |
||||
opacity: 1; |
||||
right:25px; |
||||
} |
||||
|
||||
@media only screen and (max-width: 600px) { |
||||
.we-are-hiring { |
||||
display: none; |
||||
} |
||||
} |
||||
\` |
||||
document.body.appendChild(linkEl, document.body.firstChild) |
||||
document.body.appendChild(styleEl, document.body.firstChild) |
||||
setTimeout(() => linkEl.classList.add('active'), 2000) |
||||
</script> |
||||
</body> |
||||
</html>`;
|
@ -0,0 +1,87 @@
|
||||
export default ({ |
||||
ncSiteUrl, |
||||
}: { |
||||
ncSiteUrl: string; |
||||
}): string => `<!DOCTYPE html>
|
||||
<html> |
||||
<head> |
||||
<title>NocoDB : API Docs</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> |
||||
<link rel="shortcut icon" href="${ncSiteUrl}/favicon.ico" /> |
||||
<link rel="stylesheet" href="${ncSiteUrl}/css/swagger-ui-bundle.4.5.2.min.css"/> |
||||
<script src="${ncSiteUrl}/js/swagger-ui-bundle.4.5.2.min.js"></script> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
<script> |
||||
|
||||
let initialLocalStorage = {} |
||||
|
||||
try { |
||||
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}'); |
||||
} catch (e) { |
||||
console.error('Failed to parse local storage', e); |
||||
} |
||||
|
||||
var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance
|
||||
xmlhttp.open("GET", "./swagger.json"); |
||||
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); |
||||
xmlhttp.setRequestHeader("xc-auth", initialLocalStorage && initialLocalStorage.token); |
||||
xmlhttp.onload = function () { |
||||
|
||||
const ui = SwaggerUIBundle({ |
||||
// url: ,
|
||||
spec: JSON.parse(xmlhttp.responseText), |
||||
dom_id: '#app', |
||||
presets: [ |
||||
SwaggerUIBundle.presets.apis, |
||||
SwaggerUIBundle.SwaggerUIStandalonePreset |
||||
], |
||||
}) |
||||
} |
||||
xmlhttp.send(); |
||||
|
||||
|
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px'); |
||||
const linkEl = document.createElement('a') |
||||
linkEl.setAttribute('href', "http://careers.nocodb.com") |
||||
linkEl.setAttribute('target', '_blank') |
||||
linkEl.setAttribute('class', 'we-are-hiring') |
||||
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀' |
||||
const styleEl = document.createElement('style'); |
||||
styleEl.innerHTML = \` |
||||
.we-are-hiring { |
||||
position: fixed; |
||||
bottom: 50px; |
||||
right: -250px; |
||||
opacity: 0; |
||||
background: orange; |
||||
border-radius: 4px; |
||||
padding: 19px; |
||||
z-index: 200; |
||||
text-decoration: none;
|
||||
text-transform: uppercase; |
||||
color: black; |
||||
transition: 1s opacity, 1s right; |
||||
display: block; |
||||
font-weight: bold; |
||||
}
|
||||
|
||||
.we-are-hiring.active { |
||||
opacity: 1; |
||||
right:25px; |
||||
} |
||||
|
||||
@media only screen and (max-width: 600px) { |
||||
.we-are-hiring { |
||||
display: none; |
||||
} |
||||
} |
||||
\` |
||||
document.body.appendChild(linkEl, document.body.firstChild) |
||||
document.body.appendChild(styleEl, document.body.firstChild) |
||||
setTimeout(() => linkEl.classList.add('active'), 2000) |
||||
</script> |
||||
</body> |
||||
</html> |
||||
`;
|
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { ApiTokensService } from '../services/api-tokens.service'; |
||||
import { ApiTokensController } from './api-tokens.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('ApiTokensController', () => { |
||||
let controller: ApiTokensController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [ApiTokensController], |
||||
providers: [ApiTokensService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<ApiTokensController>(ApiTokensController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,52 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { ApiTokensService } from '../services/api-tokens.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class ApiTokensController { |
||||
constructor(private readonly apiTokensService: ApiTokensService) {} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/api-tokens') |
||||
@Acl('apiTokenList') |
||||
async apiTokenList(@Request() req) { |
||||
return new PagedResponseImpl( |
||||
await this.apiTokensService.apiTokenList({ userId: req['user'].id }), |
||||
); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/projects/:projectId/api-tokens') |
||||
@HttpCode(200) |
||||
@Acl('apiTokenCreate') |
||||
async apiTokenCreate(@Request() req, @Body() body) { |
||||
return await this.apiTokensService.apiTokenCreate({ |
||||
tokenBody: body, |
||||
userId: req['user'].id, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/projects/:projectId/api-tokens/:token') |
||||
@Acl('apiTokenDelete') |
||||
async apiTokenDelete(@Request() req, @Param('token') token: string) { |
||||
return await this.apiTokensService.apiTokenDelete({ |
||||
token, |
||||
user: req['user'], |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { AttachmentsService } from '../services/attachments.service'; |
||||
import { AttachmentsController } from './attachments.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('AttachmentsController', () => { |
||||
let controller: AttachmentsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [AttachmentsController], |
||||
providers: [AttachmentsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<AttachmentsController>(AttachmentsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,100 @@
|
||||
import path from 'path'; |
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Post, |
||||
Query, |
||||
Request, |
||||
Response, |
||||
UploadedFiles, |
||||
UseGuards, |
||||
UseInterceptors, |
||||
} from '@nestjs/common'; |
||||
import { AnyFilesInterceptor } from '@nestjs/platform-express'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { UploadAllowedInterceptor } from '../interceptors/is-upload-allowed/is-upload-allowed.interceptor'; |
||||
import { AttachmentsService } from '../services/attachments.service'; |
||||
|
||||
@Controller() |
||||
export class AttachmentsController { |
||||
constructor(private readonly attachmentsService: AttachmentsService) {} |
||||
|
||||
@UseGuards(GlobalGuard) |
||||
@Post('/api/v1/db/storage/upload') |
||||
@HttpCode(200) |
||||
@UseInterceptors(UploadAllowedInterceptor, AnyFilesInterceptor()) |
||||
async upload( |
||||
@UploadedFiles() files: Array<any>, |
||||
@Body() body: any, |
||||
@Request() req: any, |
||||
@Query('path') path: string, |
||||
) { |
||||
const attachments = await this.attachmentsService.upload({ |
||||
files: files, |
||||
path: req.query?.path as string, |
||||
}); |
||||
|
||||
return attachments; |
||||
} |
||||
|
||||
@Post('/api/v1/db/storage/upload-by-url') |
||||
@HttpCode(200) |
||||
@UseInterceptors(UploadAllowedInterceptor) |
||||
@UseGuards(GlobalGuard) |
||||
async uploadViaURL(@Body() body: any, @Query('path') path: string) { |
||||
const attachments = await this.attachmentsService.uploadViaURL({ |
||||
urls: body, |
||||
path, |
||||
}); |
||||
|
||||
return attachments; |
||||
} |
||||
|
||||
// @Get(/^\/download\/(.+)$/)
|
||||
// , getCacheMiddleware(), catchError(fileRead));
|
||||
@Get('/download/:filename(*)') |
||||
// This route will match any URL that starts with
|
||||
async fileRead(@Param('filename') filename: string, @Response() res) { |
||||
try { |
||||
const { img, type } = await this.attachmentsService.fileRead({ |
||||
path: path.join('nc', 'uploads', filename), |
||||
}); |
||||
|
||||
res.writeHead(200, { 'Content-Type': type }); |
||||
res.end(img, 'binary'); |
||||
} catch (e) { |
||||
console.log(e); |
||||
res.status(404).send('Not found'); |
||||
} |
||||
} |
||||
|
||||
// @Get(/^\/dl\/([^/]+)\/([^/]+)\/(.+)$/)
|
||||
@Get('/dl/:param1([a-zA-Z0-9_-]+)/:param2([a-zA-Z0-9_-]+)/:filename(*)') |
||||
// getCacheMiddleware(),
|
||||
async fileReadv2( |
||||
@Param('param1') param1: string, |
||||
@Param('param2') param2: string, |
||||
@Param('filename') filename: string, |
||||
@Response() res, |
||||
) { |
||||
try { |
||||
const { img, type } = await this.attachmentsService.fileRead({ |
||||
path: path.join( |
||||
'nc', |
||||
param1, |
||||
param2, |
||||
'uploads', |
||||
...filename.split('/'), |
||||
), |
||||
}); |
||||
|
||||
res.writeHead(200, { 'Content-Type': type }); |
||||
res.end(img, 'binary'); |
||||
} catch (e) { |
||||
res.status(404).send('Not found'); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { AuditsService } from '../services/audits.service'; |
||||
import { AuditsController } from './audits.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('AuditsController', () => { |
||||
let controller: AuditsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [AuditsController], |
||||
providers: [AuditsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<AuditsController>(AuditsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,96 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Query, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { Audit } from '../models'; |
||||
import { AuditsService } from '../services/audits.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class AuditsController { |
||||
constructor(private readonly auditsService: AuditsService) {} |
||||
|
||||
@Post('/api/v1/db/meta/audits/comments') |
||||
@HttpCode(200) |
||||
@Acl('commentRow') |
||||
async commentRow(@Request() req) { |
||||
return await this.auditsService.commentRow({ |
||||
user: (req as any).user, |
||||
body: req.body, |
||||
}); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/audits/rows/:rowId/update') |
||||
@HttpCode(200) |
||||
@Acl('auditRowUpdate') |
||||
async auditRowUpdate(@Param('rowId') rowId: string, @Body() body: any) { |
||||
return await this.auditsService.auditRowUpdate({ |
||||
rowId, |
||||
body, |
||||
}); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/audits/comments') |
||||
@Acl('commentList') |
||||
async commentList(@Request() req) { |
||||
return new PagedResponseImpl( |
||||
await this.auditsService.commentList({ query: req.query }), |
||||
); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/audits/:auditId/comment') |
||||
@Acl('commentUpdate') |
||||
async commentUpdate( |
||||
@Param('auditId') auditId: string, |
||||
@Request() req, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.auditsService.commentUpdate({ |
||||
auditId, |
||||
userEmail: req.user?.email, |
||||
body: body, |
||||
}); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/audits') |
||||
@Acl('auditList') |
||||
async auditList(@Request() req, @Param('projectId') projectId: string) { |
||||
return new PagedResponseImpl( |
||||
await this.auditsService.auditList({ |
||||
query: req.query, |
||||
projectId, |
||||
}), |
||||
{ |
||||
count: await Audit.projectAuditCount(projectId), |
||||
...req.query, |
||||
}, |
||||
); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/audits/comments/count') |
||||
@Acl('commentsCount') |
||||
async commentsCount( |
||||
@Query('fk_model_id') fk_model_id: string, |
||||
@Query('ids') ids: string[], |
||||
) { |
||||
return await this.auditsService.commentsCount({ |
||||
fk_model_id, |
||||
ids, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { AuthService } from '../services/auth.service'; |
||||
import { AuthController } from './auth.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('AuthController', () => { |
||||
let controller: AuthController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [AuthController], |
||||
providers: [AuthService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<AuthController>(AuthController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,48 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { ExtractProjectIdMiddleware } from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import extractRolesObj from '../utils/extractRolesObj'; |
||||
import { AuthService } from '../services/auth.service'; |
||||
|
||||
export class CreateUserDto { |
||||
readonly username: string; |
||||
readonly email: string; |
||||
readonly password: string; |
||||
} |
||||
|
||||
@Controller() |
||||
export class AuthController { |
||||
constructor(private readonly authService: AuthService) {} |
||||
|
||||
@UseGuards(AuthGuard('local')) |
||||
@Post('/api/v1/auth/user/signin') |
||||
@HttpCode(200) |
||||
async signin(@Request() req) { |
||||
return this.authService.login(req.user); |
||||
} |
||||
|
||||
@Post('/api/v1/auth/user/signup') |
||||
@HttpCode(200) |
||||
async signup(@Body() createUserDto: CreateUserDto) { |
||||
return await this.authService.signup(createUserDto); |
||||
} |
||||
|
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
@Get('/api/v1/auth/user/me') |
||||
async me(@Request() req) { |
||||
const user = { |
||||
...req.user, |
||||
roles: extractRolesObj(req.user.roles), |
||||
}; |
||||
return user; |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { BasesService } from '../services/bases.service'; |
||||
import { BasesController } from './bases.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('BasesController', () => { |
||||
let controller: BasesController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [BasesController], |
||||
providers: [BasesService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<BasesController>(BasesController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,89 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { BaseReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { BasesService } from '../services/bases.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class BasesController { |
||||
constructor(private readonly basesService: BasesService) {} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/bases/:baseId') |
||||
@Acl('baseGet') |
||||
async baseGet(@Param('baseId') baseId: string) { |
||||
const base = await this.basesService.baseGetWithConfig({ |
||||
baseId, |
||||
}); |
||||
|
||||
return base; |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/projects/:projectId/bases/:baseId') |
||||
@Acl('baseUpdate') |
||||
async baseUpdate( |
||||
@Param('baseId') baseId: string, |
||||
@Param('projectId') projectId: string, |
||||
@Body() body: BaseReqType, |
||||
) { |
||||
const base = await this.basesService.baseUpdate({ |
||||
baseId, |
||||
base: body, |
||||
projectId, |
||||
}); |
||||
|
||||
return base; |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/bases') |
||||
@Acl('baseList') |
||||
async baseList(@Param('projectId') projectId: string) { |
||||
const bases = await this.basesService.baseList({ |
||||
projectId, |
||||
}); |
||||
|
||||
return new PagedResponseImpl(bases, { |
||||
count: bases.length, |
||||
limit: bases.length, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/projects/:projectId/bases/:baseId') |
||||
@Acl('baseDelete') |
||||
async baseDelete(@Param('baseId') baseId: string) { |
||||
const result = await this.basesService.baseDelete({ |
||||
baseId, |
||||
}); |
||||
return result; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/projects/:projectId/bases') |
||||
@HttpCode(200) |
||||
@Acl('baseCreate') |
||||
async baseCreate( |
||||
@Param('projectId') projectId: string, |
||||
@Body() body: BaseReqType, |
||||
) { |
||||
const base = await this.basesService.baseCreate({ |
||||
projectId, |
||||
base: body, |
||||
}); |
||||
|
||||
return base; |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { BulkDataAliasController } from './bulk-data-alias.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('BulkDataAliasController', () => { |
||||
let controller: BulkDataAliasController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [BulkDataAliasController], |
||||
}).compile(); |
||||
|
||||
controller = module.get<BulkDataAliasController>(BulkDataAliasController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,112 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
Response, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { BulkDataAliasService } from '../services/bulk-data-alias.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class BulkDataAliasController { |
||||
constructor(private bulkDataAliasService: BulkDataAliasService) {} |
||||
|
||||
@Post('/api/v1/db/data/bulk/:orgs/:projectName/:tableName') |
||||
@HttpCode(200) |
||||
@Acl('bulkDataInsert') |
||||
async bulkDataInsert( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Body() body: any, |
||||
) { |
||||
const exists = await this.bulkDataAliasService.bulkDataInsert({ |
||||
body: body, |
||||
cookie: req, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
|
||||
res.json(exists); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/data/bulk/:orgs/:projectName/:tableName') |
||||
@Acl('bulkDataUpdate') |
||||
async bulkDataUpdate( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.bulkDataAliasService.bulkDataUpdate({ |
||||
body: body, |
||||
cookie: req, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
// todo: Integrate with filterArrJson bulkDataUpdateAll
|
||||
@Patch('/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all') |
||||
@Acl('bulkDataUpdateAll') |
||||
async bulkDataUpdateAll( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.bulkDataAliasService.bulkDataUpdateAll({ |
||||
body: body, |
||||
cookie: req, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/api/v1/db/data/bulk/:orgs/:projectName/:tableName') |
||||
@Acl('bulkDataDelete') |
||||
async bulkDataDelete( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.bulkDataAliasService.bulkDataDelete({ |
||||
body: body, |
||||
cookie: req, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
// todo: Integrate with filterArrJson bulkDataDeleteAll
|
||||
|
||||
@Delete('/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all') |
||||
@Acl('bulkDataDeleteAll') |
||||
async bulkDataDeleteAll( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.bulkDataAliasService.bulkDataDeleteAll({ |
||||
// cookie: req,
|
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { CachesService } from '../services/caches.service'; |
||||
import { CachesController } from './caches.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('CachesController', () => { |
||||
let controller: CachesController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [CachesController], |
||||
providers: [CachesService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<CachesController>(CachesController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,25 @@
|
||||
import { Controller, Delete, Get } from '@nestjs/common'; |
||||
import { Acl } from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { CachesService } from '../services/caches.service'; |
||||
|
||||
@Controller() |
||||
export class CachesController { |
||||
constructor(private readonly cachesService: CachesService) {} |
||||
|
||||
@Get('/api/v1/db/meta/cache') |
||||
@Acl('cacheGet') |
||||
async cacheGet(_, res) { |
||||
const data = await this.cachesService.cacheGet(); |
||||
res.set({ |
||||
'Content-Type': 'application/json', |
||||
'Content-Disposition': `attachment; filename="cache-export.json"`, |
||||
}); |
||||
return JSON.stringify(data); |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/cache') |
||||
@Acl('cacheDelete') |
||||
async cacheDelete() { |
||||
return await this.cachesService.cacheDelete(); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { ColumnsService } from '../services/columns.service'; |
||||
import { ColumnsController } from './columns.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('ColumnsController', () => { |
||||
let controller: ColumnsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [ColumnsController], |
||||
providers: [ColumnsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<ColumnsController>(ColumnsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,74 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { ColumnReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { ColumnsService } from '../services/columns.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class ColumnsController { |
||||
constructor(private readonly columnsService: ColumnsService) {} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/columns/') |
||||
@HttpCode(200) |
||||
@Acl('columnAdd') |
||||
async columnAdd( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ColumnReqType, |
||||
@Request() req: any, |
||||
) { |
||||
return await this.columnsService.columnAdd({ |
||||
tableId, |
||||
column: body, |
||||
req, |
||||
}); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/columns/:columnId') |
||||
@Acl('columnUpdate') |
||||
async columnUpdate( |
||||
@Param('columnId') columnId: string, |
||||
@Body() body: ColumnReqType, |
||||
@Request() req: any, |
||||
) { |
||||
return await this.columnsService.columnUpdate({ |
||||
columnId: columnId, |
||||
column: body, |
||||
req, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/columns/:columnId') |
||||
@Acl('columnDelete') |
||||
async columnDelete(@Param('columnId') columnId: string, @Request() req: any) { |
||||
return await this.columnsService.columnDelete({ columnId, req }); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/columns/:columnId') |
||||
@Acl('columnGet') |
||||
async columnGet(@Param('columnId') columnId: string) { |
||||
return await this.columnsService.columnGet({ columnId }); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/columns/:columnId/primary') |
||||
@HttpCode(200) |
||||
@Acl('columnSetAsPrimary') |
||||
async columnSetAsPrimary(@Param('columnId') columnId: string) { |
||||
return await this.columnsService.columnSetAsPrimary({ columnId }); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { DataAliasExportController } from './data-alias-export.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('DataAliasExportController', () => { |
||||
let controller: DataAliasExportController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [DataAliasExportController], |
||||
}).compile(); |
||||
|
||||
controller = module.get<DataAliasExportController>( |
||||
DataAliasExportController, |
||||
); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,68 @@
|
||||
import { Controller, Get, Request, Response, UseGuards } from '@nestjs/common'; |
||||
import * as XLSX from 'xlsx'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { View } from '../models'; |
||||
import { DatasService } from '../services/datas.service'; |
||||
import { extractCsvData, extractXlsxData } from '../modules/datas/helpers'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class DataAliasExportController { |
||||
constructor(private datasService: DatasService) {} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/export/excel', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/export/excel', |
||||
]) |
||||
@Acl('exportExcel') |
||||
async excelDataExport(@Request() req, @Response() res) { |
||||
const { model, view } = |
||||
await this.datasService.getViewAndModelFromRequestByAliasOrId(req); |
||||
let targetView = view; |
||||
if (!targetView) { |
||||
targetView = await View.getDefaultView(model.id); |
||||
} |
||||
const { offset, elapsed, data } = await extractXlsxData(targetView, req); |
||||
const wb = XLSX.utils.book_new(); |
||||
XLSX.utils.book_append_sheet(wb, data, targetView.title); |
||||
const buf = XLSX.write(wb, { type: 'base64', bookType: 'xlsx' }); |
||||
res.set({ |
||||
'Access-Control-Expose-Headers': 'nc-export-offset', |
||||
'nc-export-offset': offset, |
||||
'nc-export-elapsed-time': elapsed, |
||||
'Content-Disposition': `attachment; filename="${encodeURI( |
||||
targetView.title, |
||||
)}-export.xlsx"`,
|
||||
}); |
||||
res.end(buf); |
||||
} |
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/export/csv', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/export/csv', |
||||
]) |
||||
@Acl('exportCsv') |
||||
async csvDataExport(@Request() req, @Response() res) { |
||||
const { model, view } = |
||||
await this.datasService.getViewAndModelFromRequestByAliasOrId(req); |
||||
let targetView = view; |
||||
if (!targetView) { |
||||
targetView = await View.getDefaultView(model.id); |
||||
} |
||||
const { offset, elapsed, data } = await extractCsvData(targetView, req); |
||||
|
||||
res.set({ |
||||
'Access-Control-Expose-Headers': 'nc-export-offset', |
||||
'nc-export-offset': offset, |
||||
'nc-export-elapsed-time': elapsed, |
||||
'Content-Disposition': `attachment; filename="${encodeURI( |
||||
targetView.title, |
||||
)}-export.csv"`,
|
||||
}); |
||||
res.send(data); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { DataAliasNestedController } from './data-alias-nested.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('DataAliasNestedController', () => { |
||||
let controller: DataAliasNestedController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [DataAliasNestedController], |
||||
}).compile(); |
||||
|
||||
controller = module.get<DataAliasNestedController>( |
||||
DataAliasNestedController, |
||||
); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,174 @@
|
||||
import { |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { DataAliasNestedService } from '../services/data-alias-nested.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class DataAliasNestedController { |
||||
constructor(private dataAliasNestedService: DataAliasNestedService) {} |
||||
|
||||
// todo: handle case where the given column is not ltar
|
||||
@Get('/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName') |
||||
@Acl('mmList') |
||||
async mmList( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.dataAliasNestedService.mmList({ |
||||
query: req.query, |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
@Get( |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName/exclude', |
||||
) |
||||
@Acl('mmExcludedList') |
||||
async mmExcludedList( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.dataAliasNestedService.mmExcludedList({ |
||||
query: req.query, |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
@Get( |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName/exclude', |
||||
) |
||||
@Acl('hmExcludedList') |
||||
async hmExcludedList( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.dataAliasNestedService.hmExcludedList({ |
||||
query: req.query, |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
@Get( |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/bt/:columnName/exclude', |
||||
) |
||||
@Acl('btExcludedList') |
||||
async btExcludedList( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.dataAliasNestedService.btExcludedList({ |
||||
query: req.query, |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
// todo: handle case where the given column is not ltar
|
||||
|
||||
@Get('/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName') |
||||
@Acl('hmList') |
||||
async hmList( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
return await this.dataAliasNestedService.hmList({ |
||||
query: req.query, |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
}); |
||||
} |
||||
|
||||
@Delete( |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId', |
||||
) |
||||
@Acl('relationDataRemove') |
||||
async relationDataRemove( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('refRowId') refRowId: string, |
||||
@Param('relationType') relationType: string, |
||||
) { |
||||
await this.dataAliasNestedService.relationDataRemove({ |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
cookie: req, |
||||
refRowId: refRowId, |
||||
}); |
||||
|
||||
return { msg: 'The relation data has been deleted successfully' }; |
||||
} |
||||
|
||||
// todo: Give proper error message when reference row is already related and handle duplicate ref row id in hm
|
||||
@Post( |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId', |
||||
) |
||||
@Acl('relationDataAdd') |
||||
@HttpCode(200) |
||||
async relationDataAdd( |
||||
@Request() req, |
||||
@Param('columnName') columnName: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('refRowId') refRowId: string, |
||||
@Param('relationType') relationType: string, |
||||
) { |
||||
await this.dataAliasNestedService.relationDataAdd({ |
||||
columnName: columnName, |
||||
rowId: rowId, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
cookie: req, |
||||
refRowId: refRowId, |
||||
}); |
||||
|
||||
return { msg: 'The relation data has been created successfully' }; |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { DataAliasNestedService } from '../services/data-alias-nested.service'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('DataAliasNestedService', () => { |
||||
let service: DataAliasNestedService; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [DataAliasNestedService], |
||||
}).compile(); |
||||
|
||||
service = module.get<DataAliasNestedService>(DataAliasNestedService); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(service).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { DatasService } from '../services/datas.service'; |
||||
import { DataAliasController } from './data-alias.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('DataAliasController', () => { |
||||
let controller: DataAliasController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [DataAliasController], |
||||
providers: [DatasService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<DataAliasController>(DataAliasController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,250 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
Response, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { parseHrtimeToSeconds } from '../helpers'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { DatasService } from '../services/datas.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class DataAliasController { |
||||
constructor(private readonly datasService: DatasService) {} |
||||
|
||||
// todo: Handle the error case where view doesnt belong to model
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName', |
||||
]) |
||||
@Acl('dataList') |
||||
async dataList( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
) { |
||||
const startTime = process.hrtime(); |
||||
const responseData = await this.datasService.dataList({ |
||||
query: req.query, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
}); |
||||
const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); |
||||
res.setHeader('xc-db-response', elapsedSeconds); |
||||
res.json(responseData); |
||||
} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/find-one', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/find-one', |
||||
]) |
||||
@Acl('dataFindOne') |
||||
async dataFindOne( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
) { |
||||
return await this.datasService.dataFindOne({ |
||||
query: req.query, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
}); |
||||
} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/groupby', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/groupby', |
||||
]) |
||||
@Acl('dataGroupBy') |
||||
async dataGroupBy( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
) { |
||||
return await this.datasService.dataGroupBy({ |
||||
query: req.query, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
}); |
||||
} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/count', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/count', |
||||
]) |
||||
@Acl('dataCount') |
||||
async dataCount( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
) { |
||||
const countResult = await this.datasService.dataCount({ |
||||
query: req.query, |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
}); |
||||
|
||||
res.json(countResult); |
||||
} |
||||
|
||||
@Post([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName', |
||||
]) |
||||
@HttpCode(200) |
||||
@Acl('dataInsert') |
||||
async dataInsert( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.datasService.dataInsert({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
body: body, |
||||
cookie: req, |
||||
}); |
||||
} |
||||
|
||||
@Patch([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId', |
||||
]) |
||||
@Acl('dataUpdate') |
||||
async dataUpdate( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.dataUpdate({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
body: req.body, |
||||
cookie: req, |
||||
rowId: rowId, |
||||
}); |
||||
} |
||||
|
||||
@Delete([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId', |
||||
]) |
||||
@Acl('dataDelete') |
||||
async dataDelete( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.dataDelete({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
cookie: req, |
||||
rowId: rowId, |
||||
}); |
||||
} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId', |
||||
]) |
||||
@Acl('dataRead') |
||||
async dataRead( |
||||
@Request() req, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.dataRead({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/exist', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId/exist', |
||||
]) |
||||
@Acl('dataExist') |
||||
async dataExist( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
const exists = await this.datasService.dataExist({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
|
||||
res.json(exists); |
||||
} |
||||
|
||||
// todo: Handle the error case where view doesnt belong to model
|
||||
|
||||
@Get([ |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/group/:columnId', |
||||
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/group/:columnId', |
||||
]) |
||||
@Acl('groupedDataList') |
||||
async groupedDataList( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectName') projectName: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('viewName') viewName: string, |
||||
@Param('columnId') columnId: string, |
||||
) { |
||||
const startTime = process.hrtime(); |
||||
const groupedData = await this.datasService.groupedDataList({ |
||||
projectName: projectName, |
||||
tableName: tableName, |
||||
viewName: viewName, |
||||
query: req.query, |
||||
columnId: columnId, |
||||
}); |
||||
const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); |
||||
res.setHeader('xc-db-response', elapsedSeconds); |
||||
res.json(groupedData); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { DatasService } from '../services/datas.service'; |
||||
import { DatasController } from './datas.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('DatasController', () => { |
||||
let controller: DatasController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [DatasController], |
||||
providers: [DatasService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<DatasController>(DatasController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,215 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { DatasService } from '../services/datas.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class DatasController { |
||||
constructor(private readonly datasService: DatasService) {} |
||||
|
||||
@Get('/data/:viewId/') |
||||
@Acl('dataList') |
||||
async dataList(@Request() req, @Param('viewId') viewId: string) { |
||||
return await this.datasService.dataListByViewId({ |
||||
viewId: viewId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId/mm/:colId') |
||||
@Acl('mmList') |
||||
async mmList( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('colId') colId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.mmList({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId/mm/:colId/exclude') |
||||
@Acl('mmExcludedList') |
||||
async mmExcludedList( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('colId') colId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.mmExcludedList({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId/hm/:colId/exclude') |
||||
@Acl('hmExcludedList') |
||||
async hmExcludedList( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('colId') colId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
await this.datasService.hmExcludedList({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId/bt/:colId/exclude') |
||||
@Acl('btExcludedList') |
||||
async btExcludedList( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('colId') colId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.btExcludedList({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId/hm/:colId') |
||||
@Acl('hmList') |
||||
async hmList( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('colId') colId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.hmList({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Get('/data/:viewId/:rowId') |
||||
@Acl('dataRead') |
||||
async dataRead( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.dataReadByViewId({ |
||||
viewId, |
||||
rowId, |
||||
query: req.query, |
||||
}); |
||||
} |
||||
|
||||
@Post('/data/:viewId/') |
||||
@HttpCode(200) |
||||
@Acl('dataInsert') |
||||
async dataInsert( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.datasService.dataInsertByViewId({ |
||||
viewId: viewId, |
||||
body: body, |
||||
cookie: req, |
||||
}); |
||||
} |
||||
|
||||
@Patch('/data/:viewId/:rowId') |
||||
@Acl('dataUpdate') |
||||
async dataUpdate( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('rowId') rowId: string, |
||||
@Body() body: any, |
||||
) { |
||||
return await this.datasService.dataUpdateByViewId({ |
||||
viewId: viewId, |
||||
rowId: rowId, |
||||
body: body, |
||||
cookie: req, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/data/:viewId/:rowId') |
||||
@Acl('dataDelete') |
||||
async dataDelete( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
return await this.datasService.dataDeleteByViewId({ |
||||
viewId: viewId, |
||||
rowId: rowId, |
||||
cookie: req, |
||||
}); |
||||
} |
||||
|
||||
@Delete('/data/:viewId/:rowId/:relationType/:colId/:childId') |
||||
@Acl('relationDataDelete') |
||||
async relationDataDelete( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('relationType') relationType: string, |
||||
@Param('colId') colId: string, |
||||
@Param('childId') childId: string, |
||||
) { |
||||
await this.datasService.relationDataDelete({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
childId: childId, |
||||
rowId: rowId, |
||||
cookie: req, |
||||
}); |
||||
|
||||
return { msg: 'The relation data has been deleted successfully' }; |
||||
} |
||||
|
||||
@Post('/data/:viewId/:rowId/:relationType/:colId/:childId') |
||||
@HttpCode(200) |
||||
@Acl('relationDataAdd') |
||||
async relationDataAdd( |
||||
@Request() req, |
||||
@Param('viewId') viewId: string, |
||||
@Param('rowId') rowId: string, |
||||
@Param('relationType') relationType: string, |
||||
@Param('colId') colId: string, |
||||
@Param('childId') childId: string, |
||||
) { |
||||
await this.datasService.relationDataAdd({ |
||||
viewId: viewId, |
||||
colId: colId, |
||||
childId: childId, |
||||
rowId: rowId, |
||||
cookie: req, |
||||
}); |
||||
|
||||
return { msg: 'The relation data has been created successfully' }; |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { FiltersService } from '../services/filters.service'; |
||||
import { FiltersController } from './filters.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('FiltersController', () => { |
||||
let controller: FiltersController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [FiltersController], |
||||
providers: [FiltersService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<FiltersController>(FiltersController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,113 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { FilterReqType } from 'nocodb-sdk'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
UseAclMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { FiltersService } from '../services/filters.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class FiltersController { |
||||
constructor(private readonly filtersService: FiltersService) {} |
||||
|
||||
@Get('/api/v1/db/meta/views/:viewId/filters') |
||||
@Acl('filterList') |
||||
async filterList(@Param('viewId') viewId: string) { |
||||
return new PagedResponseImpl( |
||||
await this.filtersService.filterList({ |
||||
viewId, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/views/:viewId/filters') |
||||
@HttpCode(200) |
||||
@Acl('filterCreate') |
||||
async filterCreate( |
||||
@Param('viewId') viewId: string, |
||||
@Body() body: FilterReqType, |
||||
) { |
||||
const filter = await this.filtersService.filterCreate({ |
||||
filter: body, |
||||
viewId: viewId, |
||||
}); |
||||
return filter; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/hooks/:hookId/filters') |
||||
@HttpCode(200) |
||||
@Acl('hookFilterCreate') |
||||
async hookFilterCreate( |
||||
@Param('hookId') hookId: string, |
||||
@Body() body: FilterReqType, |
||||
) { |
||||
const filter = await this.filtersService.hookFilterCreate({ |
||||
filter: body, |
||||
hookId, |
||||
}); |
||||
return filter; |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/filters/:filterId') |
||||
@Acl('filterGet') |
||||
async filterGet(@Param('filterId') filterId: string) { |
||||
return await this.filtersService.filterGet({ filterId }); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/filters/:filterParentId/children') |
||||
@Acl('filterChildrenList') |
||||
async filterChildrenRead(filterParentId: string) { |
||||
return new PagedResponseImpl( |
||||
await this.filtersService.filterChildrenList({ |
||||
filterId: filterParentId, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/filters/:filterId') |
||||
@Acl('filterUpdate') |
||||
async filterUpdate( |
||||
@Param('filterId') filterId: string, |
||||
@Body() body: FilterReqType, |
||||
) { |
||||
const filter = await this.filtersService.filterUpdate({ |
||||
filterId: filterId, |
||||
filter: body, |
||||
}); |
||||
return filter; |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/filters/:filterId') |
||||
@Acl('filterDelete') |
||||
async filterDelete(@Param('filterId') filterId: string) { |
||||
const filter = await this.filtersService.filterDelete({ |
||||
filterId, |
||||
}); |
||||
return filter; |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/hooks/:hookId/filters') |
||||
@Acl('hookFilterList') |
||||
async hookFilterList(@Param('hookId') hookId: string) { |
||||
return new PagedResponseImpl( |
||||
await this.filtersService.hookFilterList({ |
||||
hookId: hookId, |
||||
}), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { FormColumnsService } from '../services/form-columns.service'; |
||||
import { FormColumnsController } from './form-columns.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('FormColumnsController', () => { |
||||
let controller: FormColumnsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [FormColumnsController], |
||||
providers: [FormColumnsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<FormColumnsController>(FormColumnsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,28 @@
|
||||
import { Body, Controller, Param, Patch, UseGuards } from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { FormColumnsService } from '../services/form-columns.service'; |
||||
|
||||
class FormColumnUpdateReqType {} |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class FormColumnsController { |
||||
constructor(private readonly formColumnsService: FormColumnsService) {} |
||||
|
||||
@Patch('/api/v1/db/meta/form-columns/:formViewColumnId') |
||||
@Acl('columnUpdate') |
||||
async columnUpdate( |
||||
@Param('formViewColumnId') formViewColumnId: string, |
||||
@Body() formViewColumnbody: FormColumnUpdateReqType, |
||||
) { |
||||
return await this.formColumnsService.columnUpdate({ |
||||
formViewColumnId, |
||||
formViewColumn: formViewColumnbody, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { FormsService } from '../services/forms.service'; |
||||
import { FormsController } from './forms.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('FormsController', () => { |
||||
let controller: FormsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [FormsController], |
||||
providers: [FormsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<FormsController>(FormsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,54 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { ViewCreateReqType } from 'nocodb-sdk'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { FormsService } from '../services/forms.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class FormsController { |
||||
constructor(private readonly formsService: FormsService) {} |
||||
|
||||
@Get('/api/v1/db/meta/forms/:formViewId') |
||||
@Acl('formViewGet') |
||||
async formViewGet(@Param('formViewId') formViewId: string) { |
||||
const formViewData = await this.formsService.formViewGet({ |
||||
formViewId, |
||||
}); |
||||
return formViewData; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/forms') |
||||
@HttpCode(200) |
||||
@Acl('formViewCreate') |
||||
async formViewCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ViewCreateReqType, |
||||
) { |
||||
const view = await this.formsService.formViewCreate({ |
||||
body, |
||||
tableId, |
||||
}); |
||||
return view; |
||||
} |
||||
@Patch('/api/v1/db/meta/forms/:formViewId') |
||||
@Acl('formViewUpdate') |
||||
async formViewUpdate(@Param('formViewId') formViewId: string, @Body() body) { |
||||
return await this.formsService.formViewUpdate({ |
||||
formViewId, |
||||
form: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { GalleriesService } from '../services/galleries.service'; |
||||
import { GalleriesController } from './galleries.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('GalleriesController', () => { |
||||
let controller: GalleriesController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [GalleriesController], |
||||
providers: [GalleriesService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<GalleriesController>(GalleriesController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,58 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { GalleryUpdateReqType, ViewCreateReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { GalleriesService } from '../services/galleries.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class GalleriesController { |
||||
constructor(private readonly galleriesService: GalleriesService) {} |
||||
|
||||
@Get('/api/v1/db/meta/galleries/:galleryViewId') |
||||
@Acl('galleryViewGet') |
||||
async galleryViewGet(@Param('galleryViewId') galleryViewId: string) { |
||||
return await this.galleriesService.galleryViewGet({ |
||||
galleryViewId, |
||||
}); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/galleries') |
||||
@HttpCode(200) |
||||
@Acl('galleryViewCreate') |
||||
async galleryViewCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ViewCreateReqType, |
||||
) { |
||||
return await this.galleriesService.galleryViewCreate({ |
||||
gallery: body, |
||||
// todo: sanitize
|
||||
tableId, |
||||
}); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/galleries/:galleryViewId') |
||||
@Acl('galleryViewUpdate') |
||||
async galleryViewUpdate( |
||||
@Param('galleryViewId') galleryViewId: string, |
||||
@Body() body: GalleryUpdateReqType, |
||||
) { |
||||
return await this.galleriesService.galleryViewUpdate({ |
||||
galleryViewId, |
||||
gallery: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { GridColumnsService } from '../services/grid-columns.service'; |
||||
import { GridColumnsController } from './grid-columns.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('GridColumnsController', () => { |
||||
let controller: GridColumnsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [GridColumnsController], |
||||
providers: [GridColumnsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<GridColumnsController>(GridColumnsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,34 @@
|
||||
import { Body, Controller, Get, Param, Patch, UseGuards } from '@nestjs/common'; |
||||
import { GridColumnReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { GridColumnsService } from '../services/grid-columns.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class GridColumnsController { |
||||
constructor(private readonly gridColumnsService: GridColumnsService) {} |
||||
|
||||
@Get('/api/v1/db/meta/grids/:gridViewId/grid-columns') |
||||
@Acl('columnList') |
||||
async columnList(@Param('gridViewId') gridViewId: string) { |
||||
return await this.gridColumnsService.columnList({ |
||||
gridViewId, |
||||
}); |
||||
} |
||||
@Patch('/api/v1/db/meta/grid-columns/:gridViewColumnId') |
||||
@Acl('gridColumnUpdate') |
||||
async gridColumnUpdate( |
||||
@Param('gridViewColumnId') gridViewColumnId: string, |
||||
@Body() body: GridColumnReqType, |
||||
) { |
||||
return this.gridColumnsService.gridColumnUpdate({ |
||||
gridViewColumnId, |
||||
grid: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { GridsService } from '../services/grids.service'; |
||||
import { GridsController } from './grids.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('GridsController', () => { |
||||
let controller: GridsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [GridsController], |
||||
providers: [GridsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<GridsController>(GridsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,47 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { ViewCreateReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { GridsService } from '../services/grids.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class GridsController { |
||||
get '/api/v1/db/meta/tables/:tableId/grids/'() { |
||||
return this['_/api/v1/db/meta/tables/:tableId/grids/']; |
||||
} |
||||
constructor(private readonly gridsService: GridsService) {} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/grids/') |
||||
@HttpCode(200) |
||||
@Acl('gridViewCreate') |
||||
async gridViewCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ViewCreateReqType, |
||||
) { |
||||
const view = await this.gridsService.gridViewCreate({ |
||||
grid: body, |
||||
tableId, |
||||
}); |
||||
return view; |
||||
} |
||||
@Patch('/api/v1/db/meta/grids/:viewId') |
||||
async gridViewUpdate(@Param('viewId') viewId: string, @Body() body) { |
||||
return await this.gridsService.gridViewUpdate({ |
||||
viewId, |
||||
grid: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { HooksService } from '../services/hooks.service'; |
||||
import { HooksController } from './hooks.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('HooksController', () => { |
||||
let controller: HooksController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [HooksController], |
||||
providers: [HooksService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<HooksController>(HooksController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,114 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { HookReqType, HookTestReqType } from 'nocodb-sdk'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { HooksService } from '../services/hooks.service'; |
||||
import type { HookType } from 'nocodb-sdk'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class HooksController { |
||||
constructor(private readonly hooksService: HooksService) {} |
||||
|
||||
@Get('/api/v1/db/meta/tables/:tableId/hooks') |
||||
@Acl('hookList') |
||||
async hookList(@Param('tableId') tableId: string) { |
||||
return new PagedResponseImpl(await this.hooksService.hookList({ tableId })); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/hooks') |
||||
@HttpCode(200) |
||||
@Acl('hookCreate') |
||||
async hookCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: HookReqType, |
||||
) { |
||||
const hook = await this.hooksService.hookCreate({ |
||||
hook: body, |
||||
tableId, |
||||
}); |
||||
return hook; |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/hooks/:hookId') |
||||
@Acl('hookDelete') |
||||
async hookDelete(@Param('hookId') hookId: string) { |
||||
return await this.hooksService.hookDelete({ hookId }); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/hooks/:hookId') |
||||
@Acl('hookUpdate') |
||||
async hookUpdate(@Param('hookId') hookId: string, @Body() body: HookReqType) { |
||||
return await this.hooksService.hookUpdate({ hookId, hook: body }); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/hooks/test') |
||||
@HttpCode(200) |
||||
@Acl('hookTest') |
||||
async hookTest(@Body() body: HookTestReqType, @Request() req: any) { |
||||
try { |
||||
await this.hooksService.hookTest({ |
||||
hookTest: { |
||||
...body, |
||||
payload: { |
||||
...body.payload, |
||||
user: (req as any)?.user, |
||||
}, |
||||
}, |
||||
tableId: req.params.tableId, |
||||
}); |
||||
return { msg: 'The hook has been tested successfully' }; |
||||
} catch (e) { |
||||
console.error(e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
@Get( |
||||
'/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation/:version', |
||||
) |
||||
@Acl('tableSampleData') |
||||
async tableSampleData( |
||||
@Param('tableId') tableId: string, |
||||
@Param('operation') operation: HookType['operation'], |
||||
@Param('version') version: HookType['version'], |
||||
) { |
||||
return await this.hooksService.tableSampleData({ |
||||
tableId, |
||||
operation, |
||||
version, |
||||
}); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/hooks/:hookId/logs') |
||||
@Acl('hookLogList') |
||||
async hookLogList(@Param('hookId') hookId: string, @Request() req: any) { |
||||
return new PagedResponseImpl( |
||||
await this.hooksService.hookLogList({ |
||||
query: req.query, |
||||
hookId, |
||||
}), |
||||
{ |
||||
...req.query, |
||||
count: await this.hooksService.hookLogCount({ |
||||
hookId, |
||||
}), |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,222 @@
|
||||
import { Readable } from 'stream'; |
||||
import sqlite3 from 'sqlite3'; |
||||
|
||||
class EntityMap { |
||||
initialized: boolean; |
||||
cols: string[]; |
||||
db: any; |
||||
|
||||
constructor(...args) { |
||||
this.initialized = false; |
||||
this.cols = args.map((arg) => processKey(arg)); |
||||
this.db = new Promise((resolve, reject) => { |
||||
const db = new sqlite3.Database(':memory:'); |
||||
|
||||
const colStatement = |
||||
this.cols.length > 0 |
||||
? this.cols.join(' TEXT, ') + ' TEXT' |
||||
: 'mappingPlaceholder TEXT'; |
||||
db.run(`CREATE TABLE mapping (${colStatement})`, (err) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
resolve(db); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async init() { |
||||
if (!this.initialized) { |
||||
this.db = await this.db; |
||||
this.initialized = true; |
||||
} |
||||
} |
||||
|
||||
destroy() { |
||||
if (this.initialized && this.db) { |
||||
this.db.close(); |
||||
} |
||||
} |
||||
|
||||
async addRow(row) { |
||||
if (!this.initialized) { |
||||
throw 'Please initialize first!'; |
||||
} |
||||
|
||||
const cols = Object.keys(row).map((key) => processKey(key)); |
||||
const colStatement = cols.map((key) => `'${key}'`).join(', '); |
||||
const questionMarks = cols.map(() => '?').join(', '); |
||||
|
||||
const promises = []; |
||||
|
||||
for (const col of cols.filter((col) => !this.cols.includes(col))) { |
||||
promises.push( |
||||
new Promise((resolve, reject) => { |
||||
this.db.run(`ALTER TABLE mapping ADD '${col}' TEXT;`, (err) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
this.cols.push(col); |
||||
resolve(true); |
||||
}); |
||||
}), |
||||
); |
||||
} |
||||
|
||||
await Promise.all(promises); |
||||
|
||||
const values = Object.values(row).map((val) => { |
||||
if (typeof val === 'object') { |
||||
return `JSON::${JSON.stringify(val)}`; |
||||
} |
||||
return val; |
||||
}); |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
this.db.run( |
||||
`INSERT INTO mapping (${colStatement}) VALUES (${questionMarks})`, |
||||
values, |
||||
(err) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
resolve(true); |
||||
}, |
||||
); |
||||
}); |
||||
} |
||||
|
||||
getRow(col, val, res = []): Promise<Record<string, any>> { |
||||
if (!this.initialized) { |
||||
throw 'Please initialize first!'; |
||||
} |
||||
return new Promise((resolve, reject) => { |
||||
col = processKey(col); |
||||
res = res.map((r) => processKey(r)); |
||||
this.db.get( |
||||
`SELECT ${ |
||||
res.length ? res.join(', ') : '*' |
||||
} FROM mapping WHERE ${col} = ?`,
|
||||
[val], |
||||
(err, rs) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
if (rs) { |
||||
rs = processResponseRow(rs); |
||||
} |
||||
resolve(rs); |
||||
}, |
||||
); |
||||
}); |
||||
} |
||||
|
||||
getCount(): Promise<number> { |
||||
if (!this.initialized) { |
||||
throw 'Please initialize first!'; |
||||
} |
||||
return new Promise((resolve, reject) => { |
||||
this.db.get(`SELECT COUNT(*) as count FROM mapping`, (err, rs) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
resolve(rs.count); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
getStream(res = []): DBStream { |
||||
if (!this.initialized) { |
||||
throw 'Please initialize first!'; |
||||
} |
||||
res = res.map((r) => processKey(r)); |
||||
return new DBStream( |
||||
this.db, |
||||
`SELECT ${res.length ? res.join(', ') : '*'} FROM mapping`, |
||||
); |
||||
} |
||||
|
||||
getLimit(limit, offset, res = []): Promise<Record<string, any>[]> { |
||||
if (!this.initialized) { |
||||
throw 'Please initialize first!'; |
||||
} |
||||
return new Promise((resolve, reject) => { |
||||
res = res.map((r) => processKey(r)); |
||||
this.db.all( |
||||
`SELECT ${ |
||||
res.length ? res.join(', ') : '*' |
||||
} FROM mapping LIMIT ${limit} OFFSET ${offset}`,
|
||||
(err, rs) => { |
||||
if (err) { |
||||
console.log(err); |
||||
reject(err); |
||||
} |
||||
for (let row of rs) { |
||||
row = processResponseRow(row); |
||||
} |
||||
resolve(rs); |
||||
}, |
||||
); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
class DBStream extends Readable { |
||||
db: any; |
||||
stmt: any; |
||||
sql: any; |
||||
|
||||
constructor(db, sql) { |
||||
super({ objectMode: true }); |
||||
this.db = db; |
||||
this.sql = sql; |
||||
this.stmt = this.db.prepare(this.sql); |
||||
this.on('end', () => this.stmt.finalize()); |
||||
} |
||||
|
||||
_read() { |
||||
const stream = this; |
||||
this.stmt.get(function (err, result) { |
||||
if (err) { |
||||
stream.emit('error', err); |
||||
} else { |
||||
if (result) { |
||||
result = processResponseRow(result); |
||||
} |
||||
stream.push(result || null); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
function processResponseRow(res: any) { |
||||
for (const key of Object.keys(res)) { |
||||
if (res[key] && res[key].startsWith('JSON::')) { |
||||
try { |
||||
res[key] = JSON.parse(res[key].replace('JSON::', '')); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
} |
||||
if (revertKey(key) !== key) { |
||||
res[revertKey(key)] = res[key]; |
||||
delete res[key]; |
||||
} |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
function processKey(key) { |
||||
return key.replace(/'/g, "''").replace(/[A-Z]/g, (match) => `_${match}`); |
||||
} |
||||
|
||||
function revertKey(key) { |
||||
return key.replace(/''/g, "'").replace(/_[A-Z]/g, (match) => match[1]); |
||||
} |
||||
|
||||
export default EntityMap; |
@ -0,0 +1,6 @@
|
||||
export abstract class NocoSyncSourceAdapter { |
||||
public abstract init(): Promise<void>; |
||||
public abstract destProjectWrite(): Promise<any>; |
||||
public abstract destSchemaWrite(): Promise<any>; |
||||
public abstract destDataWrite(): Promise<any>; |
||||
} |
@ -0,0 +1,7 @@
|
||||
export abstract class NocoSyncSourceAdapter { |
||||
public abstract init(): Promise<void>; |
||||
public abstract srcSchemaGet(): Promise<any>; |
||||
public abstract srcDataLoad(): Promise<any>; |
||||
public abstract srcDataListen(): Promise<any>; |
||||
public abstract srcDataPoll(): Promise<any>; |
||||
} |
@ -0,0 +1,242 @@
|
||||
import axios from 'axios'; |
||||
|
||||
const info: any = { |
||||
initialized: false, |
||||
}; |
||||
|
||||
async function initialize(shareId) { |
||||
info.cookie = ''; |
||||
const url = `https://airtable.com/${shareId}`; |
||||
|
||||
try { |
||||
const hreq = await axios |
||||
.get(url, { |
||||
headers: { |
||||
accept: |
||||
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', |
||||
'accept-language': 'en-US,en;q=0.9', |
||||
'sec-ch-ua': |
||||
'" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', |
||||
'sec-ch-ua-mobile': '?0', |
||||
'sec-ch-ua-platform': '"Linux"', |
||||
'sec-fetch-dest': 'document', |
||||
'sec-fetch-mode': 'navigate', |
||||
'sec-fetch-site': 'none', |
||||
'sec-fetch-user': '?1', |
||||
'upgrade-insecure-requests': '1', |
||||
'User-Agent': |
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', |
||||
}, |
||||
// @ts-ignore
|
||||
referrerPolicy: 'strict-origin-when-cross-origin', |
||||
body: null, |
||||
method: 'GET', |
||||
}) |
||||
.then((response) => { |
||||
for (const ck of response.headers['set-cookie']) { |
||||
info.cookie += ck.split(';')[0] + '; '; |
||||
} |
||||
return response.data; |
||||
}) |
||||
.catch(() => { |
||||
throw { |
||||
message: |
||||
'Invalid Shared Base ID :: Ensure www.airtable.com/<SharedBaseID> is accessible. Refer https://bit.ly/3x0OdXI for details', |
||||
}; |
||||
}); |
||||
|
||||
info.headers = JSON.parse( |
||||
hreq.match(/(?<=var headers =)(.*)(?=;)/g)[0].trim(), |
||||
); |
||||
info.link = unicodeToChar(hreq.match(/(?<=fetch\(")(.*)(?=")/g)[0].trim()); |
||||
info.baseInfo = decodeURIComponent(info.link) |
||||
.match(/{(.*)}/g)[0] |
||||
.split('&') |
||||
.reduce((result, el) => { |
||||
try { |
||||
return Object.assign( |
||||
result, |
||||
JSON.parse(el.includes('=') ? el.split('=')[1] : el), |
||||
); |
||||
} catch (e) { |
||||
if (el.includes('=')) { |
||||
return Object.assign(result, { |
||||
[el.split('=')[0]]: el.split('=')[1], |
||||
}); |
||||
} |
||||
} |
||||
}, {}); |
||||
info.baseId = info.baseInfo.applicationId; |
||||
info.initialized = true; |
||||
} catch (e) { |
||||
console.log(e); |
||||
info.initialized = false; |
||||
if (e.message) { |
||||
throw e; |
||||
} else { |
||||
throw { |
||||
message: |
||||
'Error processing Shared Base :: Ensure www.airtable.com/<SharedBaseID> is accessible. Refer https://bit.ly/3x0OdXI for details', |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
async function read() { |
||||
if (info.initialized) { |
||||
const resreq = await axios('https://airtable.com' + info.link, { |
||||
headers: { |
||||
accept: '*/*', |
||||
'accept-language': 'en-US,en;q=0.9', |
||||
'sec-ch-ua': |
||||
'" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', |
||||
'sec-ch-ua-mobile': '?0', |
||||
'sec-ch-ua-platform': '"Linux"', |
||||
'sec-fetch-dest': 'empty', |
||||
'sec-fetch-mode': 'cors', |
||||
'sec-fetch-site': 'same-origin', |
||||
'User-Agent': |
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', |
||||
'x-time-zone': 'Europe/Berlin', |
||||
cookie: info.cookie, |
||||
...info.headers, |
||||
}, |
||||
// @ts-ignore
|
||||
referrerPolicy: 'no-referrer', |
||||
body: null, |
||||
method: 'GET', |
||||
}) |
||||
.then((response) => { |
||||
return response.data; |
||||
}) |
||||
.catch(() => { |
||||
throw { |
||||
message: |
||||
'Error Reading :: Ensure www.airtable.com/<SharedBaseID> is accessible. Refer https://bit.ly/3x0OdXI for details', |
||||
}; |
||||
}); |
||||
|
||||
return { |
||||
schema: resreq.data, |
||||
baseId: info.baseId, |
||||
baseInfo: info.baseInfo, |
||||
}; |
||||
} else { |
||||
throw { |
||||
message: 'Error Initializing :: please try again !!', |
||||
}; |
||||
} |
||||
} |
||||
|
||||
async function readView(viewId) { |
||||
if (info.initialized) { |
||||
const resreq = await axios( |
||||
`https://airtable.com/v0.3/view/${viewId}/readData?` + |
||||
`stringifiedObjectParams=${encodeURIComponent('{}')}&requestId=${ |
||||
info.baseInfo.requestId |
||||
}&accessPolicy=${encodeURIComponent( |
||||
JSON.stringify({ |
||||
allowedActions: info.baseInfo.allowedActions, |
||||
shareId: info.baseInfo.shareId, |
||||
applicationId: info.baseInfo.applicationId, |
||||
generationNumber: info.baseInfo.generationNumber, |
||||
expires: info.baseInfo.expires, |
||||
signature: info.baseInfo.signature, |
||||
}), |
||||
)}`,
|
||||
{ |
||||
headers: { |
||||
accept: '*/*', |
||||
'accept-language': 'en-US,en;q=0.9', |
||||
'sec-ch-ua': |
||||
'" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', |
||||
'sec-ch-ua-mobile': '?0', |
||||
'sec-ch-ua-platform': '"Linux"', |
||||
'sec-fetch-dest': 'empty', |
||||
'sec-fetch-mode': 'cors', |
||||
'sec-fetch-site': 'same-origin', |
||||
'User-Agent': |
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', |
||||
'x-time-zone': 'Europe/Berlin', |
||||
cookie: info.cookie, |
||||
...info.headers, |
||||
}, |
||||
// @ts-ignore
|
||||
referrerPolicy: 'no-referrer', |
||||
body: null, |
||||
method: 'GET', |
||||
}, |
||||
) |
||||
.then((response) => { |
||||
return response.data; |
||||
}) |
||||
.catch(() => { |
||||
throw { |
||||
message: |
||||
'Error Reading View :: Ensure www.airtable.com/<SharedBaseID> is accessible. Refer https://bit.ly/3x0OdXI for details', |
||||
}; |
||||
}); |
||||
return { view: resreq.data }; |
||||
} else { |
||||
throw { |
||||
message: 'Error Initializing :: please try again !!', |
||||
}; |
||||
} |
||||
} |
||||
|
||||
async function readTemplate(templateId) { |
||||
if (!info.initialized) { |
||||
await initialize('shrO8aYf3ybwSdDKn'); |
||||
} |
||||
const resreq = await axios( |
||||
`https://www.airtable.com/v0.3/exploreApplications/${templateId}`, |
||||
{ |
||||
headers: { |
||||
accept: '*/*', |
||||
'accept-language': 'en-US,en;q=0.9', |
||||
'sec-ch-ua': |
||||
'" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', |
||||
'sec-ch-ua-mobile': '?0', |
||||
'sec-ch-ua-platform': '"Linux"', |
||||
'sec-fetch-dest': 'empty', |
||||
'sec-fetch-mode': 'cors', |
||||
'sec-fetch-site': 'same-origin', |
||||
'User-Agent': |
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', |
||||
'x-time-zone': 'Europe/Berlin', |
||||
cookie: info.cookie, |
||||
...info.headers, |
||||
}, |
||||
// @ts-ignore
|
||||
referrer: 'https://www.airtable.com/', |
||||
referrerPolicy: 'same-origin', |
||||
body: null, |
||||
method: 'GET', |
||||
mode: 'cors', |
||||
credentials: 'include', |
||||
}, |
||||
) |
||||
.then((response) => { |
||||
return response.data; |
||||
}) |
||||
.catch(() => { |
||||
throw { |
||||
message: |
||||
'Error Fetching :: Ensure www.airtable.com/templates/featured/<TemplateID> is accessible.', |
||||
}; |
||||
}); |
||||
return { template: resreq }; |
||||
} |
||||
|
||||
function unicodeToChar(text) { |
||||
return text.replace(/\\u[\dA-F]{4}/gi, function (match) { |
||||
return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); |
||||
}); |
||||
} |
||||
|
||||
export default { |
||||
initialize, |
||||
read, |
||||
readView, |
||||
readTemplate, |
||||
}; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,361 @@
|
||||
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||
import EntityMap from './EntityMap'; |
||||
import type { BulkDataAliasService } from '../../../services/bulk-data-alias.service'; |
||||
import type { TablesService } from '../../../services/tables.service'; |
||||
// @ts-ignore
|
||||
import type { AirtableBase } from 'airtable/lib/airtable_base'; |
||||
import type { TableType } from 'nocodb-sdk'; |
||||
|
||||
const BULK_DATA_BATCH_SIZE = 500; |
||||
const ASSOC_BULK_DATA_BATCH_SIZE = 1000; |
||||
const BULK_PARALLEL_PROCESS = 5; |
||||
|
||||
interface AirtableImportContext { |
||||
bulkDataService: BulkDataAliasService; |
||||
tableService: TablesService; |
||||
} |
||||
|
||||
async function readAllData({ |
||||
table, |
||||
fields, |
||||
base, |
||||
logBasic = (_str) => {}, |
||||
services, |
||||
}: { |
||||
table: { title?: string }; |
||||
fields?; |
||||
base: AirtableBase; |
||||
logBasic?: (string) => void; |
||||
logDetailed?: (string) => void; |
||||
services: AirtableImportContext; |
||||
}): Promise<EntityMap> { |
||||
return new Promise((resolve, reject) => { |
||||
let data = null; |
||||
|
||||
const selectParams: any = { |
||||
pageSize: 100, |
||||
}; |
||||
|
||||
if (fields) selectParams.fields = fields; |
||||
|
||||
base(table.title) |
||||
.select(selectParams) |
||||
.eachPage( |
||||
async function page(records, fetchNextPage) { |
||||
if (!data) { |
||||
data = new EntityMap(); |
||||
await data.init(); |
||||
} |
||||
|
||||
for await (const record of records) { |
||||
await data.addRow({ id: record.id, ...record.fields }); |
||||
} |
||||
|
||||
const tmpLength = await data.getCount(); |
||||
|
||||
logBasic( |
||||
`:: Reading '${table.title}' data :: ${Math.max( |
||||
1, |
||||
tmpLength - records.length, |
||||
)} - ${tmpLength}`,
|
||||
); |
||||
|
||||
// To fetch the next page of records, call `fetchNextPage`.
|
||||
// If there are more records, `page` will get called again.
|
||||
// If there are no more records, `done` will get called.
|
||||
fetchNextPage(); |
||||
}, |
||||
async function done(err) { |
||||
if (err) { |
||||
console.error(err); |
||||
return reject(err); |
||||
} |
||||
resolve(data); |
||||
}, |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export async function importData({ |
||||
projectName, |
||||
table, |
||||
base, |
||||
nocoBaseDataProcessing_v2, |
||||
sDB, |
||||
logDetailed = (_str) => {}, |
||||
logBasic = (_str) => {}, |
||||
services, |
||||
}: { |
||||
projectName: string; |
||||
table: { title?: string; id?: string }; |
||||
fields?; |
||||
base: AirtableBase; |
||||
logBasic: (string) => void; |
||||
logDetailed: (string) => void; |
||||
nocoBaseDataProcessing_v2; |
||||
sDB; |
||||
services: AirtableImportContext; |
||||
}): Promise<EntityMap> { |
||||
try { |
||||
// @ts-ignore
|
||||
const records = await readAllData({ |
||||
table, |
||||
base, |
||||
logDetailed, |
||||
logBasic, |
||||
}); |
||||
|
||||
await new Promise(async (resolve) => { |
||||
const readable = records.getStream(); |
||||
const allRecordsCount = await records.getCount(); |
||||
const promises = []; |
||||
let tempData = []; |
||||
let importedCount = 0; |
||||
let activeProcess = 0; |
||||
readable.on('data', async (record) => { |
||||
promises.push( |
||||
new Promise(async (resolve) => { |
||||
activeProcess++; |
||||
if (activeProcess >= BULK_PARALLEL_PROCESS) readable.pause(); |
||||
const { id: rid, ...fields } = record; |
||||
const r = await nocoBaseDataProcessing_v2(sDB, table, { |
||||
id: rid, |
||||
fields, |
||||
}); |
||||
tempData.push(r); |
||||
|
||||
if (tempData.length >= BULK_DATA_BATCH_SIZE) { |
||||
let insertArray = tempData.splice(0, tempData.length); |
||||
|
||||
await services.bulkDataService.bulkDataInsert({ |
||||
projectName, |
||||
tableName: table.title, |
||||
body: insertArray, |
||||
cookie: {}, |
||||
}); |
||||
|
||||
logBasic( |
||||
`:: Importing '${ |
||||
table.title |
||||
}' data :: ${importedCount} - ${Math.min( |
||||
importedCount + BULK_DATA_BATCH_SIZE, |
||||
allRecordsCount, |
||||
)}`,
|
||||
); |
||||
importedCount += insertArray.length; |
||||
insertArray = []; |
||||
} |
||||
activeProcess--; |
||||
if (activeProcess < BULK_PARALLEL_PROCESS) readable.resume(); |
||||
resolve(true); |
||||
}), |
||||
); |
||||
}); |
||||
readable.on('end', async () => { |
||||
await Promise.all(promises); |
||||
if (tempData.length > 0) { |
||||
await services.bulkDataService.bulkDataInsert({ |
||||
projectName, |
||||
tableName: table.title, |
||||
body: tempData, |
||||
cookie: {}, |
||||
}); |
||||
|
||||
logBasic( |
||||
`:: Importing '${ |
||||
table.title |
||||
}' data :: ${importedCount} - ${Math.min( |
||||
importedCount + BULK_DATA_BATCH_SIZE, |
||||
allRecordsCount, |
||||
)}`,
|
||||
); |
||||
importedCount += tempData.length; |
||||
tempData = []; |
||||
} |
||||
resolve(true); |
||||
}); |
||||
}); |
||||
|
||||
return records; |
||||
} catch (e) { |
||||
console.log(e); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
export async function importLTARData({ |
||||
table, |
||||
fields, |
||||
base, |
||||
projectName, |
||||
insertedAssocRef = {}, |
||||
logDetailed = (_str) => {}, |
||||
logBasic = (_str) => {}, |
||||
records, |
||||
atNcAliasRef, |
||||
ncLinkMappingTable, |
||||
syncDB, |
||||
services, |
||||
}: { |
||||
projectName: string; |
||||
table: { title?: string; id?: string }; |
||||
fields; |
||||
base: AirtableBase; |
||||
logDetailed: (string) => void; |
||||
logBasic: (string) => void; |
||||
insertedAssocRef: { [assocTableId: string]: boolean }; |
||||
records?: EntityMap; |
||||
atNcAliasRef: { |
||||
[ncTableId: string]: { |
||||
[ncTitle: string]: string; |
||||
}; |
||||
}; |
||||
ncLinkMappingTable: Record<string, Record<string, any>>[]; |
||||
syncDB; |
||||
services: AirtableImportContext; |
||||
}) { |
||||
const assocTableMetas: Array<{ |
||||
modelMeta: { id?: string; title?: string }; |
||||
colMeta: { title?: string }; |
||||
curCol: { title?: string }; |
||||
refCol: { title?: string }; |
||||
}> = []; |
||||
const allData = |
||||
records || |
||||
(await readAllData({ |
||||
table, |
||||
fields, |
||||
base, |
||||
logDetailed, |
||||
logBasic, |
||||
services, |
||||
})); |
||||
|
||||
const modelMeta: any = |
||||
await services.tableService.getTableWithAccessibleViews({ |
||||
tableId: table.id, |
||||
user: syncDB.user, |
||||
}); |
||||
|
||||
for (const colMeta of modelMeta.columns) { |
||||
// skip columns which are not LTAR and Many to many
|
||||
if ( |
||||
colMeta.uidt !== UITypes.LinkToAnotherRecord || |
||||
colMeta.colOptions.type !== RelationTypes.MANY_TO_MANY |
||||
) { |
||||
continue; |
||||
} |
||||
|
||||
// skip if already inserted
|
||||
if (colMeta.colOptions.fk_mm_model_id in insertedAssocRef) continue; |
||||
|
||||
// self links: skip if the column under consideration is the add-on column NocoDB creates
|
||||
if (ncLinkMappingTable.every((a) => a.nc.title !== colMeta.title)) continue; |
||||
|
||||
// mark as inserted
|
||||
insertedAssocRef[colMeta.colOptions.fk_mm_model_id] = true; |
||||
|
||||
const assocModelMeta: TableType = |
||||
(await services.tableService.getTableWithAccessibleViews({ |
||||
tableId: colMeta.colOptions.fk_mm_model_id, |
||||
user: syncDB.user, |
||||
})) as any; |
||||
|
||||
// extract associative table and columns meta
|
||||
assocTableMetas.push({ |
||||
modelMeta: assocModelMeta, |
||||
colMeta, |
||||
curCol: assocModelMeta.columns.find( |
||||
(c) => c.id === colMeta.colOptions.fk_mm_child_column_id, |
||||
), |
||||
refCol: assocModelMeta.columns.find( |
||||
(c) => c.id === colMeta.colOptions.fk_mm_parent_column_id, |
||||
), |
||||
}); |
||||
} |
||||
|
||||
let nestedLinkCnt = 0; |
||||
// Iterate over all related M2M associative table
|
||||
for await (const assocMeta of assocTableMetas) { |
||||
let assocTableData = []; |
||||
let importedCount = 0; |
||||
|
||||
// extract insert data from records
|
||||
await new Promise((resolve) => { |
||||
const promises = []; |
||||
const readable = allData.getStream(); |
||||
let activeProcess = 0; |
||||
readable.on('data', async (record) => { |
||||
promises.push( |
||||
new Promise(async (resolve) => { |
||||
activeProcess++; |
||||
if (activeProcess >= BULK_PARALLEL_PROCESS) readable.pause(); |
||||
const { id: _atId, ...rec } = record; |
||||
|
||||
// todo: use actual alias instead of sanitized
|
||||
assocTableData.push( |
||||
...( |
||||
rec?.[atNcAliasRef[table.id][assocMeta.colMeta.title]] || [] |
||||
).map((id) => ({ |
||||
[assocMeta.curCol.title]: record.id, |
||||
[assocMeta.refCol.title]: id, |
||||
})), |
||||
); |
||||
|
||||
if (assocTableData.length >= ASSOC_BULK_DATA_BATCH_SIZE) { |
||||
let insertArray = assocTableData.splice(0, assocTableData.length); |
||||
logBasic( |
||||
`:: Importing '${ |
||||
table.title |
||||
}' LTAR data :: ${importedCount} - ${Math.min( |
||||
importedCount + ASSOC_BULK_DATA_BATCH_SIZE, |
||||
insertArray.length, |
||||
)}`,
|
||||
); |
||||
|
||||
await services.bulkDataService.bulkDataInsert({ |
||||
projectName, |
||||
tableName: assocMeta.modelMeta.title, |
||||
body: insertArray, |
||||
cookie: {}, |
||||
}); |
||||
|
||||
importedCount += insertArray.length; |
||||
insertArray = []; |
||||
} |
||||
activeProcess--; |
||||
if (activeProcess < BULK_PARALLEL_PROCESS) readable.resume(); |
||||
resolve(true); |
||||
}), |
||||
); |
||||
}); |
||||
readable.on('end', async () => { |
||||
await Promise.all(promises); |
||||
if (assocTableData.length >= 0) { |
||||
logBasic( |
||||
`:: Importing '${ |
||||
table.title |
||||
}' LTAR data :: ${importedCount} - ${Math.min( |
||||
importedCount + ASSOC_BULK_DATA_BATCH_SIZE, |
||||
assocTableData.length, |
||||
)}`,
|
||||
); |
||||
|
||||
await services.bulkDataService.bulkDataInsert({ |
||||
projectName, |
||||
tableName: assocMeta.modelMeta.title, |
||||
body: assocTableData, |
||||
cookie: {}, |
||||
}); |
||||
|
||||
importedCount += assocTableData.length; |
||||
assocTableData = []; |
||||
} |
||||
resolve(true); |
||||
}); |
||||
}); |
||||
|
||||
nestedLinkCnt += importedCount; |
||||
} |
||||
return nestedLinkCnt; |
||||
} |
@ -0,0 +1,31 @@
|
||||
export const mapTbl = {}; |
||||
|
||||
// static mapping records between aTblId && ncId
|
||||
export const addToMappingTbl = function addToMappingTbl( |
||||
aTblId, |
||||
ncId, |
||||
ncName, |
||||
parent?, |
||||
) { |
||||
mapTbl[aTblId] = { |
||||
ncId: ncId, |
||||
ncParent: parent, |
||||
// name added to assist in quick debug
|
||||
ncName: ncName, |
||||
}; |
||||
}; |
||||
|
||||
// get NcID from airtable ID
|
||||
export const getNcIdFromAtId = function getNcIdFromAtId(aId) { |
||||
return mapTbl[aId]?.ncId; |
||||
}; |
||||
|
||||
// get nc Parent from airtable ID
|
||||
export const getNcParentFromAtId = function getNcParentFromAtId(aId) { |
||||
return mapTbl[aId]?.ncParent; |
||||
}; |
||||
|
||||
// get nc-title from airtable ID
|
||||
export const getNcNameFromAtId = function getNcNameFromAtId(aId) { |
||||
return mapTbl[aId]?.ncName; |
||||
}; |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { ImportService } from '../../services/import.service'; |
||||
import { ImportController } from './import.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('ImportController', () => { |
||||
let controller: ImportController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [ImportController], |
||||
providers: [ImportService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<ImportController>(ImportController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,148 @@
|
||||
import { Controller, HttpCode, Post, Request, UseGuards } from '@nestjs/common'; |
||||
import { forwardRef, Inject } from '@nestjs/common'; |
||||
import { ModuleRef } from '@nestjs/core'; |
||||
import { GlobalGuard } from '../../guards/global/global.guard'; |
||||
import { NcError } from '../../helpers/catchError'; |
||||
import { ExtractProjectIdMiddleware } from '../../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { SyncSource } from '../../models'; |
||||
import NocoJobs from '../../jobs/NocoJobs'; |
||||
import { SocketService } from '../../services/socket.service'; |
||||
import airtableSyncJob from './helpers/job'; |
||||
import type { AirtableSyncConfig } from './helpers/job'; |
||||
|
||||
import type { Server } from 'socket.io'; |
||||
|
||||
const AIRTABLE_IMPORT_JOB = 'AIRTABLE_IMPORT_JOB'; |
||||
const AIRTABLE_PROGRESS_JOB = 'AIRTABLE_PROGRESS_JOB'; |
||||
|
||||
enum SyncStatus { |
||||
PROGRESS = 'PROGRESS', |
||||
COMPLETED = 'COMPLETED', |
||||
FAILED = 'FAILED', |
||||
} |
||||
|
||||
const initJob = (sv: Server, jobs: { [p: string]: { last_message: any } }) => { |
||||
// add importer job handler and progress notification job handler
|
||||
NocoJobs.jobsMgr.addJobWorker(AIRTABLE_IMPORT_JOB, airtableSyncJob); |
||||
NocoJobs.jobsMgr.addJobWorker( |
||||
AIRTABLE_PROGRESS_JOB, |
||||
({ payload, progress }) => { |
||||
sv.to(payload?.id).emit('progress', { |
||||
msg: progress?.msg, |
||||
level: progress?.level, |
||||
status: progress?.status, |
||||
}); |
||||
|
||||
if (payload?.id in jobs) { |
||||
jobs[payload?.id].last_message = { |
||||
msg: progress?.msg, |
||||
level: progress?.level, |
||||
status: progress?.status, |
||||
}; |
||||
} |
||||
}, |
||||
); |
||||
|
||||
NocoJobs.jobsMgr.addProgressCbk(AIRTABLE_IMPORT_JOB, (payload, progress) => { |
||||
NocoJobs.jobsMgr.add(AIRTABLE_PROGRESS_JOB, { |
||||
payload, |
||||
progress: { |
||||
msg: progress?.msg, |
||||
level: progress?.level, |
||||
status: progress?.status, |
||||
}, |
||||
}); |
||||
}); |
||||
NocoJobs.jobsMgr.addSuccessCbk(AIRTABLE_IMPORT_JOB, (payload) => { |
||||
NocoJobs.jobsMgr.add(AIRTABLE_PROGRESS_JOB, { |
||||
payload, |
||||
progress: { |
||||
msg: 'Complete!', |
||||
status: SyncStatus.COMPLETED, |
||||
}, |
||||
}); |
||||
delete jobs[payload?.id]; |
||||
}); |
||||
NocoJobs.jobsMgr.addFailureCbk(AIRTABLE_IMPORT_JOB, (payload, error: any) => { |
||||
NocoJobs.jobsMgr.add(AIRTABLE_PROGRESS_JOB, { |
||||
payload, |
||||
progress: { |
||||
msg: error?.message || 'Failed due to some internal error', |
||||
status: SyncStatus.FAILED, |
||||
}, |
||||
}); |
||||
delete jobs[payload?.id]; |
||||
}); |
||||
}; |
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class ImportController { |
||||
constructor( |
||||
private readonly socketService: SocketService, |
||||
@Inject(forwardRef(() => ModuleRef)) private readonly moduleRef: ModuleRef, |
||||
) {} |
||||
|
||||
@Post('/api/v1/db/meta/import/airtable') |
||||
@HttpCode(200) |
||||
importAirtable(@Request() req) { |
||||
NocoJobs.jobsMgr.add(AIRTABLE_IMPORT_JOB, { |
||||
id: req.query.id, |
||||
...req.body, |
||||
}); |
||||
return {}; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/syncs/:syncId/trigger') |
||||
@HttpCode(200) |
||||
async triggerSync(@Request() req) { |
||||
if (req.params.syncId in this.socketService.jobs) { |
||||
NcError.badRequest('Sync already in progress'); |
||||
} |
||||
|
||||
const syncSource = await SyncSource.get(req.params.syncId); |
||||
|
||||
const user = await syncSource.getUser(); |
||||
|
||||
// Treat default baseUrl as siteUrl from req object
|
||||
let baseURL = (req as any).ncSiteUrl; |
||||
|
||||
// if environment value avail use it
|
||||
// or if it's docker construct using `PORT`
|
||||
if (process.env.NC_DOCKER) { |
||||
baseURL = `http://localhost:${process.env.PORT || 8080}`; |
||||
} |
||||
|
||||
setTimeout(() => { |
||||
NocoJobs.jobsMgr.add<AirtableSyncConfig>(AIRTABLE_IMPORT_JOB, { |
||||
id: req.params.syncId, |
||||
...(syncSource?.details || {}), |
||||
projectId: syncSource.project_id, |
||||
baseId: syncSource.base_id, |
||||
authToken: '', |
||||
baseURL, |
||||
user: user, |
||||
moduleRef: this.moduleRef, |
||||
}); |
||||
}, 1000); |
||||
|
||||
this.socketService.jobs[req.params.syncId] = { |
||||
last_message: { |
||||
msg: 'Sync started', |
||||
}, |
||||
}; |
||||
return {}; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/syncs/:syncId/abort') |
||||
@HttpCode(200) |
||||
async abortImport(@Request() req) { |
||||
if (req.params.syncId in this.socketService.jobs) { |
||||
delete this.socketService.jobs[req.params.syncId]; |
||||
} |
||||
return {}; |
||||
} |
||||
|
||||
async onModuleInit() { |
||||
initJob(this.socketService.io, this.socketService.jobs); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { KanbansService } from '../services/kanbans.service'; |
||||
import { KanbansController } from './kanbans.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('KanbansController', () => { |
||||
let controller: KanbansController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [KanbansController], |
||||
providers: [KanbansService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<KanbansController>(KanbansController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,56 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { ViewCreateReqType } from 'nocodb-sdk'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { KanbansService } from '../services/kanbans.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class KanbansController { |
||||
constructor(private readonly kanbansService: KanbansService) {} |
||||
|
||||
@Get('/api/v1/db/meta/kanbans/:kanbanViewId') |
||||
@Acl('kanbanViewGet') |
||||
async kanbanViewGet(@Param('kanbanViewId') kanbanViewId: string) { |
||||
return await this.kanbansService.kanbanViewGet({ |
||||
kanbanViewId, |
||||
}); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/kanbans') |
||||
@HttpCode(200) |
||||
@Acl('kanbanViewCreate') |
||||
async kanbanViewCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ViewCreateReqType, |
||||
) { |
||||
return await this.kanbansService.kanbanViewCreate({ |
||||
tableId, |
||||
kanban: body, |
||||
}); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/kanbans/:kanbanViewId') |
||||
@Acl('kanbanViewUpdate') |
||||
async kanbanViewUpdate( |
||||
@Param('kanbanViewId') kanbanViewId: string, |
||||
@Body() body, |
||||
) { |
||||
return await this.kanbansService.kanbanViewUpdate({ |
||||
kanbanViewId, |
||||
kanban: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { MapsService } from '../services/maps.service'; |
||||
import { MapsController } from './maps.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('MapsController', () => { |
||||
let controller: MapsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [MapsController], |
||||
providers: [MapsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<MapsController>(MapsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,56 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { MapUpdateReqType, ViewCreateReqType } from 'nocodb-sdk'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { MapsService } from '../services/maps.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class MapsController { |
||||
constructor(private readonly mapsService: MapsService) {} |
||||
|
||||
@Get('/api/v1/db/meta/maps/:mapViewId') |
||||
@Acl('mapViewGet') |
||||
async mapViewGet(@Param('mapViewId') mapViewId: string) { |
||||
return await this.mapsService.mapViewGet({ mapViewId }); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/maps') |
||||
@HttpCode(200) |
||||
@Acl('mapViewCreate') |
||||
async mapViewCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: ViewCreateReqType, |
||||
) { |
||||
const view = await this.mapsService.mapViewCreate({ |
||||
tableId, |
||||
map: body, |
||||
}); |
||||
return view; |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/maps/:mapViewId') |
||||
@Acl('mapViewUpdate') |
||||
async mapViewUpdate( |
||||
@Param('mapViewId') mapViewId: string, |
||||
@Body() body: MapUpdateReqType, |
||||
) { |
||||
return await this.mapsService.mapViewUpdate({ |
||||
mapViewId: mapViewId, |
||||
map: body, |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { MetaDiffsService } from '../services/meta-diffs.service'; |
||||
import { MetaDiffsController } from './meta-diffs.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('MetaDiffsController', () => { |
||||
let controller: MetaDiffsController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [MetaDiffsController], |
||||
providers: [MetaDiffsService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<MetaDiffsController>(MetaDiffsController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,61 @@
|
||||
import { |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { MetaDiffsService } from '../services/meta-diffs.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class MetaDiffsController { |
||||
constructor(private readonly metaDiffsService: MetaDiffsService) {} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/meta-diff') |
||||
@Acl('metaDiff') |
||||
async metaDiff(@Param('projectId') projectId: string) { |
||||
return await this.metaDiffsService.metaDiff({ projectId }); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/meta-diff/:baseId') |
||||
async baseMetaDiff( |
||||
@Param('projectId') projectId: string, |
||||
@Param('baseId') baseId: string, |
||||
) { |
||||
return await this.metaDiffsService.baseMetaDiff({ |
||||
baseId, |
||||
projectId, |
||||
}); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/projects/:projectId/meta-diff') |
||||
@HttpCode(200) |
||||
@Acl('metaDiffSync') |
||||
async metaDiffSync(@Param('projectId') projectId: string) { |
||||
await this.metaDiffsService.metaDiffSync({ projectId }); |
||||
return { msg: 'The meta has been synchronized successfully' }; |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/projects/:projectId/meta-diff/:baseId') |
||||
@HttpCode(200) |
||||
@Acl('baseMetaDiffSync') |
||||
async baseMetaDiffSync( |
||||
@Param('projectId') projectId: string, |
||||
@Param('baseId') baseId: string, |
||||
) { |
||||
await this.metaDiffsService.baseMetaDiffSync({ |
||||
projectId, |
||||
baseId, |
||||
}); |
||||
|
||||
return { msg: 'The base meta has been synchronized successfully' }; |
||||
} |
||||
} |
@ -0,0 +1,23 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { ModelVisibilitiesService } from '../services/model-visibilities.service'; |
||||
import { ModelVisibilitiesController } from './model-visibilities.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('ModelVisibilitiesController', () => { |
||||
let controller: ModelVisibilitiesController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [ModelVisibilitiesController], |
||||
providers: [ModelVisibilitiesService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<ModelVisibilitiesController>( |
||||
ModelVisibilitiesController, |
||||
); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,52 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Post, |
||||
Query, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { ModelVisibilitiesService } from '../services/model-visibilities.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class ModelVisibilitiesController { |
||||
constructor( |
||||
private readonly modelVisibilitiesService: ModelVisibilitiesService, |
||||
) {} |
||||
|
||||
@Post('/api/v1/db/meta/projects/:projectId/visibility-rules') |
||||
@HttpCode(200) |
||||
@Acl('modelVisibilitySet') |
||||
async xcVisibilityMetaSetAll( |
||||
@Param('projectId') projectId: string, |
||||
@Body() body: any, |
||||
) { |
||||
await this.modelVisibilitiesService.xcVisibilityMetaSetAll({ |
||||
visibilityRule: body, |
||||
projectId, |
||||
}); |
||||
|
||||
return { msg: 'UI ACL has been created successfully' }; |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/projects/:projectId/visibility-rules') |
||||
@Acl('modelVisibilityList') |
||||
async modelVisibilityList( |
||||
@Param('projectId') projectId: string, |
||||
@Query('includeM2M') includeM2M: boolean | string, |
||||
) { |
||||
return await this.modelVisibilitiesService.xcVisibilityMetaGet({ |
||||
projectId, |
||||
includeM2M: includeM2M === true || includeM2M === 'true', |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { OldDatasController } from './old-datas.controller'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('OldDatasController', () => { |
||||
let controller: OldDatasController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [OldDatasController], |
||||
}).compile(); |
||||
|
||||
controller = module.get<OldDatasController>(OldDatasController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,138 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Delete, |
||||
Get, |
||||
HttpCode, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
Request, |
||||
Response, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
import { GlobalGuard } from '../../guards/global/global.guard'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { OldDatasService } from './old-datas.service'; |
||||
|
||||
@Controller() |
||||
@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) |
||||
export class OldDatasController { |
||||
constructor(private readonly oldDatasService: OldDatasService) {} |
||||
|
||||
@Get('/nc/:projectId/api/v1/:tableName') |
||||
@Acl('dataList') |
||||
async dataList( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataList({ |
||||
query: req.query, |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Get('/nc/:projectId/api/v1/:tableName/count') |
||||
@Acl('dataCount') |
||||
async dataCount( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataCount({ |
||||
query: req.query, |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Post('/nc/:projectId/api/v1/:tableName') |
||||
@HttpCode(200) |
||||
@Acl('dataInsert') |
||||
async dataInsert( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
@Body() body: any, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataInsert({ |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
body: body, |
||||
cookie: req, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Get('/nc/:projectId/api/v1/:tableName/:rowId') |
||||
@Acl('dataRead') |
||||
async dataRead( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataRead({ |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
rowId: rowId, |
||||
query: req.query, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Patch('/nc/:projectId/api/v1/:tableName/:rowId') |
||||
@Acl('dataUpdate') |
||||
async dataUpdate( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataUpdate({ |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
body: req.body, |
||||
cookie: req, |
||||
rowId: rowId, |
||||
}), |
||||
); |
||||
} |
||||
|
||||
@Delete('/nc/:projectId/api/v1/:tableName/:rowId') |
||||
@Acl('dataDelete') |
||||
async dataDelete( |
||||
@Request() req, |
||||
@Response() res, |
||||
@Param('projectId') projectId: string, |
||||
@Param('tableName') tableName: string, |
||||
@Param('rowId') rowId: string, |
||||
) { |
||||
res.json( |
||||
await this.oldDatasService.dataDelete({ |
||||
projectId: projectId, |
||||
tableName: tableName, |
||||
cookie: req, |
||||
rowId: rowId, |
||||
}), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
import { Test } from '@nestjs/testing'; |
||||
import { OldDatasService } from './old-datas.service'; |
||||
import type { TestingModule } from '@nestjs/testing'; |
||||
|
||||
describe('OldDatasService', () => { |
||||
let service: OldDatasService; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [OldDatasService], |
||||
}).compile(); |
||||
|
||||
service = module.get<OldDatasService>(OldDatasService); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(service).toBeDefined(); |
||||
}); |
||||
}); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue