mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-06 06:48:21 +00:00
* Cloud Blocker bugfixes (#13160) * fix * minor email fixes * settings menu fix * fixes * Bugfixes/whitelabelling apis (#13180) * white-labelling apis * removed consoles logs * reverts * fixes for white-labelling * fixes * reverted breadcrumb changes (#13194) * fixes for getting public sso configurations * fix for enable signup on cloud * Cloud Trial and Banners (#13182) * Cloud Blocker bugfixes (#13160) * fix * minor email fixes * settings menu fix * fixes * Cloud Trial and Banners * revert * initial commit * Added website onboarding APIs * moved ai onboarding controller to auth module * ee banners * fix --------- Co-authored-by: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Co-authored-by: gsmithun4 <gsmithun4@gmail.com> * Bugfixes/minor UI fixes-CLoud (#13203) * Bugfixes/UI bugs platform 1 (#13205) * cleanup * Audit logs fix * gitignore changes * postgrest configs removed * removed unused import * improvements * fix * improved startup logs * Platform cypress fix (#13192) * Cloud Blocker bugfixes (#13160) * fix * minor email fixes * settings menu fix * fixes * Bugfixes/whitelabelling apis (#13180) * white-labelling apis * removed consoles logs * reverts * fixes for white-labelling * fixes * Cypress fix * reverted breadcrumb changes (#13194) * cypress fix * title fix * fixes for getting public sso configurations --------- Co-authored-by: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Co-authored-by: gsmithun4 <gsmithun4@gmail.com> * deployment fix * added interfaces and permissions * Bugfixes/lts 3.6 branch 1 platform (#13238) * fix * Licensing Banners Fixes Cloud and EE (#13241) * design: Adds license buttons to header * Refactor header actions * Cloud Blocker bugfixes (#13160) * fix * minor email fixes * settings menu fix * fixes * subscription page * fix banners --------- Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Co-authored-by: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> * fix for public apps * fix * CE Instance Signup bug (#13254) * CE Instance Signup bug * improvement * fix * Add WEBSITE_SIGNUP_URL to deployment environment variables * Add WEBSITE_SIGNUP_URL to environment variables for deployment * Super admin banner fix (#13262) * Git Sync Fixes (#13249) * git-sync module changes * git sync fixes * added app resource guard * git-sync fixes * removed require feature * fix * review comment changes * ypress fix * App logo fix inside app builder * fix for subpath cache * fix (#13274) * platform-cypress-fix (#13271) * git sync fixes (#13277) * fix * Add data-cy for new components (#13289) --------- Co-authored-by: Rohan Lahori <64496391+rohanlahori@users.noreply.github.com> Co-authored-by: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com> Co-authored-by: Ajith KV <ajith.jaban@gmail.com> Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Co-authored-by: rohanlahori <rohanlahori99@gmail.com> Co-authored-by: Adish M <adish.madhu@gmail.com> Co-authored-by: Rudra deep Biswas <rudra21ultra@gmail.com>
277 lines
10 KiB
TypeScript
277 lines
10 KiB
TypeScript
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { DataSourcesRepository } from './repository';
|
|
import { DataSourcesUtilService } from './util.service';
|
|
import { User } from '@entities/user.entity';
|
|
import { AbilityService } from '@modules/ability/interfaces/IService';
|
|
import { MODULES } from '@modules/app/constants/modules';
|
|
import { decode } from 'js-base64';
|
|
import { AppEnvironmentUtilService } from '@modules/app-environments/util.service';
|
|
import { decamelizeKeys } from 'humps';
|
|
import { DataSourceTypes } from './constants';
|
|
import {
|
|
AuthorizeDataSourceOauthDto,
|
|
CreateDataSourceDto,
|
|
GetDataSourceOauthUrlDto,
|
|
TestDataSourceDto,
|
|
TestSampleDataSourceDto,
|
|
UpdateDataSourceDto,
|
|
} from './dto';
|
|
import { GetQueryVariables, UpdateOptions } from './types';
|
|
import { DataSource } from '@entities/data_source.entity';
|
|
import { PluginsServiceSelector } from './services/plugin-selector.service';
|
|
import { IDataSourcesService } from './interfaces/IService';
|
|
import { RequestContext } from '@modules/request-context/service';
|
|
import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants';
|
|
import * as fs from 'fs';
|
|
|
|
@Injectable()
|
|
export class DataSourcesService implements IDataSourcesService {
|
|
constructor(
|
|
protected readonly dataSourcesRepository: DataSourcesRepository,
|
|
protected readonly dataSourcesUtilService: DataSourcesUtilService,
|
|
protected readonly abilityService: AbilityService,
|
|
protected readonly appEnvironmentsUtilService: AppEnvironmentUtilService,
|
|
protected readonly pluginsServiceSelector: PluginsServiceSelector
|
|
) {}
|
|
|
|
async getForApp(query: GetQueryVariables, user: User): Promise<{ data_sources: object[] }> {
|
|
const userPermissions = await this.abilityService.resourceActionsPermission(user, {
|
|
resources: [{ resource: MODULES.GLOBAL_DATA_SOURCE }],
|
|
organizationId: user.organizationId,
|
|
});
|
|
const shouldIncludeWorkflows = query.shouldIncludeWorkflows ?? true;
|
|
|
|
let dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {});
|
|
|
|
if (!shouldIncludeWorkflows) {
|
|
// remove workflowsdefault data source from static data sources
|
|
dataSources = dataSources.filter((dataSource) => dataSource.kind !== 'workflows');
|
|
}
|
|
const decamelizedDatasources = decamelizeKeys(dataSources);
|
|
return { data_sources: decamelizedDatasources };
|
|
}
|
|
|
|
async getAll(query: GetQueryVariables, user: User): Promise<{ data_sources: object[] }> {
|
|
const userPermissions = await this.abilityService.resourceActionsPermission(user, {
|
|
resources: [{ resource: MODULES.GLOBAL_DATA_SOURCE }],
|
|
organizationId: user.organizationId,
|
|
});
|
|
|
|
const selectedEnvironmentId =
|
|
query.environmentId || (await this.appEnvironmentsUtilService.get(user.organizationId, null, true))?.id;
|
|
|
|
const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, {
|
|
appVersionId: query.appVersionId,
|
|
environmentId: selectedEnvironmentId,
|
|
types: [DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE],
|
|
});
|
|
for (const dataSource of dataSources) {
|
|
const parseIfNeeded = (data: any) => {
|
|
if (typeof data === 'object' && data !== null) return data;
|
|
if (Buffer.isBuffer(data) || typeof data === 'string') {
|
|
return JSON.parse(decode(data.toString('utf8')));
|
|
}
|
|
return data;
|
|
};
|
|
try {
|
|
if (dataSource.pluginId) {
|
|
if (Buffer.isBuffer(dataSource.plugin.iconFile.data)) {
|
|
dataSource.plugin.iconFile.data = dataSource.plugin.iconFile.data.toString('utf8');
|
|
}
|
|
dataSource.plugin.manifestFile.data = parseIfNeeded(dataSource.plugin.manifestFile.data);
|
|
dataSource.plugin.operationsFile.data = parseIfNeeded(dataSource.plugin.operationsFile.data);
|
|
}
|
|
} catch (error) {
|
|
throw new BadRequestException(
|
|
`Error parsing plugin data for dataSourceId: ${dataSource.id}. Details: ${error.message}`
|
|
);
|
|
}
|
|
}
|
|
|
|
const decamelizedDatasources = dataSources.map((dataSource) => {
|
|
if (dataSource.pluginId) {
|
|
return dataSource;
|
|
}
|
|
|
|
if (dataSource.kind === 'openapi') {
|
|
const { options, ...objExceptOptions } = dataSource;
|
|
const tempDs = decamelizeKeys(objExceptOptions);
|
|
const { spec, ...objExceptSpec } = options;
|
|
const decamelizedOptions = decamelizeKeys(objExceptSpec);
|
|
decamelizedOptions['spec'] = spec;
|
|
tempDs['options'] = decamelizedOptions;
|
|
return tempDs;
|
|
}
|
|
|
|
if (dataSource.type === DataSourceTypes.SAMPLE) {
|
|
delete dataSource.options;
|
|
}
|
|
return decamelizeKeys(dataSource);
|
|
});
|
|
|
|
return { data_sources: decamelizedDatasources };
|
|
}
|
|
|
|
async create(createDataSourceDto: CreateDataSourceDto, user: User): Promise<DataSource> {
|
|
const { kind, name, options, plugin_id: pluginId, environment_id } = createDataSourceDto;
|
|
|
|
if (kind === 'grpc') {
|
|
const rootDir = process.cwd().split('/').slice(0, -1).join('/');
|
|
const protoFilePath = `${rootDir}/protos/service.proto`;
|
|
|
|
const filecontent = fs.readFileSync(protoFilePath, 'utf8');
|
|
const rcps = await this.dataSourcesUtilService.getServiceAndRpcNames(filecontent);
|
|
options.find((option) => option['key'] === 'protobuf').value = JSON.stringify(rcps, null, 2);
|
|
}
|
|
const dataSource = await this.dataSourcesUtilService.create(
|
|
{
|
|
name,
|
|
kind,
|
|
options,
|
|
pluginId,
|
|
environmentId: environment_id,
|
|
},
|
|
user
|
|
);
|
|
|
|
// Setting data for audit logs
|
|
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
|
|
userId: user.id,
|
|
organizationId: user.organizationId,
|
|
resourceId: dataSource?.id,
|
|
resourceName: dataSource?.name,
|
|
metadata: dataSource,
|
|
});
|
|
|
|
return dataSource;
|
|
}
|
|
|
|
async update(updateDataSourceDto: UpdateDataSourceDto, user: User, updateOptions: UpdateOptions) {
|
|
const { name, options } = updateDataSourceDto;
|
|
const { dataSourceId, environmentId } = updateOptions;
|
|
|
|
await this.dataSourcesUtilService.update(dataSourceId, user.organizationId, name, options, environmentId);
|
|
|
|
// Setting data for audit logs
|
|
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
|
|
userId: user.id,
|
|
organizationId: user.organizationId,
|
|
resourceId: dataSourceId,
|
|
resourceName: name,
|
|
metadata: updateDataSourceDto,
|
|
});
|
|
return;
|
|
}
|
|
|
|
async decryptOptions(options: Record<string, any>) {
|
|
return await this.dataSourcesUtilService.decrypt(options);
|
|
}
|
|
|
|
async delete(dataSourceId: string, user: User) {
|
|
const dataSource = await this.dataSourcesRepository.findById(dataSourceId);
|
|
if (!dataSource) {
|
|
return;
|
|
}
|
|
if (dataSource.type === DataSourceTypes.SAMPLE) {
|
|
throw new BadRequestException('Cannot delete sample data source');
|
|
}
|
|
|
|
const result = await this.findQueriesLinkedToDatasource(dataSourceId);
|
|
if (result.dependent_queries) {
|
|
throw new BadRequestException(`Datasource can't be deleted, queries are in use`);
|
|
}
|
|
|
|
await this.dataSourcesRepository.delete(dataSourceId);
|
|
|
|
// Setting data for audit logs
|
|
RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
|
|
userId: user.id,
|
|
organizationId: user.organizationId,
|
|
resourceId: dataSourceId,
|
|
resourceName: dataSource.name,
|
|
metadata: dataSource,
|
|
});
|
|
return;
|
|
}
|
|
|
|
async changeScope(dataSourceId: string, user: User) {
|
|
await this.dataSourcesRepository.convertToGlobalSource(dataSourceId, user.organizationId);
|
|
}
|
|
|
|
async findOneByEnvironment(
|
|
dataSourceId: string,
|
|
organizationId: string,
|
|
environmentId?: string
|
|
): Promise<DataSource> {
|
|
const dataSource = await this.dataSourcesUtilService.findOneByEnvironment(
|
|
dataSourceId,
|
|
organizationId,
|
|
environmentId
|
|
);
|
|
delete dataSource['dataSourceOptions'];
|
|
return dataSource;
|
|
}
|
|
|
|
async testConnection(testDataSourceDto: TestDataSourceDto, organization_id: string): Promise<object> {
|
|
return await this.dataSourcesUtilService.testConnection(testDataSourceDto, organization_id);
|
|
}
|
|
|
|
async testSampleDBConnection(testDataSourceDto: TestSampleDataSourceDto, user: User) {
|
|
const { environment_id, dataSourceId } = testDataSourceDto;
|
|
const dataSource = await this.dataSourcesUtilService.findOneByEnvironment(
|
|
dataSourceId,
|
|
user.defaultOrganizationId,
|
|
environment_id
|
|
);
|
|
testDataSourceDto.options = dataSource.options;
|
|
return await this.dataSourcesUtilService.testConnection(testDataSourceDto, user.organizationId);
|
|
}
|
|
async getAuthUrl(getDataSourceOauthUrlDto: GetDataSourceOauthUrlDto): Promise<{ url: string }> {
|
|
return this.dataSourcesUtilService.getAuthUrl(getDataSourceOauthUrlDto);
|
|
}
|
|
|
|
async authorizeOauth2(
|
|
dataSourceId: string,
|
|
environmentId: string,
|
|
authorizeDataSourceOauthDto: AuthorizeDataSourceOauthDto,
|
|
user: User
|
|
) {
|
|
const { code } = authorizeDataSourceOauthDto;
|
|
|
|
const dataSource = await this.dataSourcesUtilService.findOneByEnvironment(dataSourceId, environmentId);
|
|
|
|
if (!dataSource) {
|
|
throw new UnauthorizedException();
|
|
}
|
|
// TODO: add privilege if user has data source privilege or user should have app read privilege of the apps using the data source
|
|
|
|
await this.dataSourcesUtilService.authorizeOauth2(dataSource, code, user.id, environmentId, user.organizationId);
|
|
return;
|
|
}
|
|
|
|
async findQueriesLinkedToDatasource(datasourceId: string) {
|
|
const dataSourceDetails = await this.dataSourcesRepository.getQueriesByDatasourceId(datasourceId);
|
|
if (dataSourceDetails.length == 0) return { datasources: 0, dependent_queries: 0 };
|
|
|
|
const queries = [];
|
|
dataSourceDetails.forEach((datasourceDetail) => {
|
|
const { dataQueries = [] } = datasourceDetail;
|
|
if (dataQueries.length) queries.push(...dataQueries);
|
|
});
|
|
|
|
return { datasources: dataSourceDetails.length, dependent_queries: queries.length };
|
|
}
|
|
|
|
async findDatasourcesAndQueriesOfMarketplacePlugin(pluginId: string) {
|
|
const dataSourcesByMarketplacePlugin = await this.dataSourcesRepository.getDatasourceByPluginId(pluginId);
|
|
if (!dataSourcesByMarketplacePlugin.length) return { dependent_queries: 0 };
|
|
|
|
const queries = [];
|
|
dataSourcesByMarketplacePlugin?.forEach((datasource) => {
|
|
if (datasource.dataQueries.length) queries.push(...datasource.dataQueries);
|
|
});
|
|
return {
|
|
dependent_queries: queries.length,
|
|
};
|
|
}
|
|
}
|