ToolJet/server/src/controllers/data_queries.controller.ts
Midhun G S 487252c5ea
Feature - Multi Environment support (#4833)
* initial commit

* multi env changes

* multi-env changes

* entity fixes

* data query changes

* fix

* trying to avoid conflict with EE

* moved version creation to app creation function

* fixing some issues

* execution of data query

* revert options changes

* changed migration

* fixed some migration issues: testing migration

* multi env support

* app import export fix

* fixes

* migration fixes

* removed plugins from data query

* fixing some migration issues

* fixes

* remove console log

* fix

* front end api changes

* backward compatibility for app import

* Fixed a bug

* correcting some mistakes

* Added constraints and fixed some issues

* changes

* fix for data source listing

* fixing version operation issues

* remove kind from data query

* removed kind from data query

* fixes

* fixes

* fix for version creation

* migration fixes

* Fixed preview and run query issues

* Fix: new version and event query id issue

* fixed rest api oauth issue
- next test refresh token

* import export changes

* fixes for app import

* import fix

* added await for for loops

* fix

* fix for migration

* Fixed backend oauth-envId issue

* import export changes

* migration fixes

* fix

* fix

* fix for app import from 0.9.0

* test case fixes

* test case fixes

* making app name mandatory for import

* adding type for options

* fix: imported apps query linking issues

* review changes

* lint issue fixes

* added on delete cascade

Co-authored-by: Muhsin Shah <muhsinshah21@gmail.com>
2022-12-09 20:23:42 +05:30

235 lines
7.5 KiB
TypeScript

import {
Controller,
Get,
Param,
Body,
Post,
Patch,
Delete,
Query,
UseGuards,
ForbiddenException,
BadRequestException,
} from '@nestjs/common';
import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
import { decamelizeKeys } from 'humps';
import { DataQueriesService } from '../../src/services/data_queries.service';
import { DataSourcesService } from '../../src/services/data_sources.service';
import { QueryAuthGuard } from 'src/modules/auth/query-auth.guard';
import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.factory';
import { AppsService } from '@services/apps.service';
import { CreateDataQueryDto, UpdateDataQueryDto } from '@dto/data-query.dto';
import { User } from 'src/decorators/user.decorator';
import { decode } from 'js-base64';
import { dbTransactionWrap } from 'src/helpers/utils.helper';
import { EntityManager } from 'typeorm';
import { DataSource } from 'src/entities/data_source.entity';
@Controller('data_queries')
export class DataQueriesController {
constructor(
private appsService: AppsService,
private dataQueriesService: DataQueriesService,
private dataSourcesService: DataSourcesService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@UseGuards(JwtAuthGuard)
@Get()
async index(@User() user, @Query() query) {
const app = await this.appsService.findAppFromVersion(query.app_version_id);
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('getQueries', app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const queries = await this.dataQueriesService.all(query);
const seralizedQueries = [];
// serialize
for (const query of queries) {
if (query.dataSource.kind === 'runjsdefault' || query.dataSource.kind === 'restapidefault') {
delete query['dataSourceId'];
}
delete query['dataSource'];
const decamelizedQuery = decamelizeKeys(query);
decamelizedQuery['options'] = query.options;
if (query.plugin) {
decamelizedQuery['plugin'].manifest_file.data = JSON.parse(
decode(query.plugin.manifestFile.data.toString('utf8'))
);
decamelizedQuery['plugin'].icon_file.data = query.plugin.iconFile.data.toString('utf8');
}
seralizedQueries.push(decamelizedQuery);
}
const response = { data_queries: seralizedQueries };
return response;
}
@UseGuards(JwtAuthGuard)
@Post()
async create(@User() user, @Body() dataQueryDto: CreateDataQueryDto): Promise<object> {
const {
kind,
name,
options,
data_source_id: dataSourceId,
plugin_id: pluginId,
app_version_id: appVersionId,
} = dataQueryDto;
let dataSource: DataSource;
if (!dataSourceId && !(kind === 'restapi' || kind === 'runjs')) {
throw new BadRequestException();
}
return dbTransactionWrap(async (manager: EntityManager) => {
if (!dataSourceId && (kind === 'restapi' || kind === 'runjs')) {
dataSource = await this.dataSourcesService.findDefaultDataSource(kind, appVersionId, pluginId, manager);
}
const app = await this.dataSourcesService.findApp(dataSource?.id || dataSourceId, manager);
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('createQuery', app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
// todo: pass the whole dto instead of indv. values
const dataQuery = await this.dataQueriesService.create(name, options, dataSource?.id || dataSourceId, manager);
return decamelizeKeys(dataQuery);
});
}
@UseGuards(JwtAuthGuard)
@Patch(':id')
async update(@User() user, @Param('id') dataQueryId, @Body() updateDataQueryDto: UpdateDataQueryDto) {
const { name, options } = updateDataQueryDto;
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.dataSource.app.id);
if (!ability.can('updateQuery', dataQuery.dataSource.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const result = await this.dataQueriesService.update(dataQueryId, name, options);
return decamelizeKeys(result);
}
@UseGuards(JwtAuthGuard)
@Delete(':id')
async delete(@User() user, @Param('id') dataQueryId) {
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.dataSource.app.id);
if (!ability.can('deleteQuery', dataQuery.dataSource.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const result = await this.dataQueriesService.delete(dataQueryId);
return decamelizeKeys(result);
}
@UseGuards(QueryAuthGuard)
@Post([':id/run/:environmentId', ':id/run'])
async runQuery(
@User() user,
@Param('id') dataQueryId,
@Param('environmentId') environmentId,
@Body() updateDataQueryDto: UpdateDataQueryDto
) {
const { options } = updateDataQueryDto;
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
if (user) {
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.dataSource.app.id);
if (!ability.can('runQuery', dataQuery.dataSource.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
}
let result = {};
try {
result = await this.dataQueriesService.runQuery(user, dataQuery, options, environmentId);
} catch (error) {
if (error.constructor.name === 'QueryError') {
result = {
status: 'failed',
message: error.message,
description: error.description,
data: error.data,
};
} else {
console.log(error);
result = {
status: 'failed',
message: 'Internal server error',
description: error.message,
data: {},
};
}
}
return result;
}
@UseGuards(JwtAuthGuard)
@Post(['/preview/:environmentId', '/preview'])
async previewQuery(
@User() user,
@Body() updateDataQueryDto: UpdateDataQueryDto,
@Param('environmentId') environmentId
) {
const { options, query } = updateDataQueryDto;
const kind = query ? query['kind'] : null;
const dataQueryEntity = {
...query,
dataSource: query['data_source_id']
? await this.dataSourcesService.findOne(query['data_source_id'])
: await this.dataSourcesService.findDatasourceByKind(`${kind}default`, environmentId),
};
const ability = await this.appsAbilityFactory.appsActions(user, dataQueryEntity.dataSource.app.id);
if (!ability.can('previewQuery', dataQueryEntity.dataSource.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
let result = {};
try {
result = await this.dataQueriesService.runQuery(user, dataQueryEntity, options, environmentId);
} catch (error) {
if (error.constructor.name === 'QueryError') {
result = {
status: 'failed',
message: error.message,
description: error.description,
data: error.data,
};
} else {
console.log(error);
result = {
status: 'failed',
message: 'Internal server error',
description: error.message,
data: {},
};
}
}
return result;
}
}