diff --git a/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts b/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts index d663ffd2e..0e81e15c9 100644 --- a/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts +++ b/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts @@ -111,7 +111,7 @@ export class FoundTableRowsDs { table_settings: TableSettingsInRowsDS; } -export class OrderingFiledDs { +export class OrderingFieldDs { @ApiProperty() field: string; diff --git a/backend/src/entities/table/application/data-structures/import-scv-in-table.ds.ts b/backend/src/entities/table/application/data-structures/import-scv-in-table.ds.ts index 5db90d0b4..2f2067030 100644 --- a/backend/src/entities/table/application/data-structures/import-scv-in-table.ds.ts +++ b/backend/src/entities/table/application/data-structures/import-scv-in-table.ds.ts @@ -2,6 +2,6 @@ export class ImportCSVInTableDs { file: Express.Multer.File; tableName: string; connectionId: string; - materPwd: string; + masterPwd: string; userId: string; } diff --git a/backend/src/entities/table/table-datastructures.ts b/backend/src/entities/table/table-datastructures.ts index abfe28dfc..a45f2136e 100644 --- a/backend/src/entities/table/table-datastructures.ts +++ b/backend/src/entities/table/table-datastructures.ts @@ -57,7 +57,7 @@ export class StructureRowInfoDs { column_name: string; @ApiProperty() - column_default: any; + column_default: string | number | boolean | null; @ApiProperty() data_type: string; diff --git a/backend/src/entities/table/table.controller.ts b/backend/src/entities/table/table.controller.ts index d29fbd8fa..5e37dee77 100644 --- a/backend/src/entities/table/table.controller.ts +++ b/backend/src/entities/table/table.controller.ts @@ -189,10 +189,10 @@ export class TableController { @Get('/table/rows/:connectionId') async findAllRows( @QueryTableName() tableName: string, - @Query('page') page: any, - @Query('perPage') perPage: any, + @Query('page') page: string, + @Query('perPage') perPage: string, @Query('search') searchingFieldValue: string, - @Query() query, + @Query() query: Record, @SlugUuid('connectionId') connectionId: string, @UserId() userId: string, @MasterPassword() masterPwd: string, @@ -205,10 +205,12 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } + let parsedPage: number; + let parsedPerPage: number; if (page && perPage) { - page = parseInt(page, 10); - perPage = parseInt(perPage, 10); - if ((page && page <= 0) || (perPage && perPage <= 0)) { + parsedPage = parseInt(page, 10); + parsedPerPage = parseInt(perPage, 10); + if ((parsedPage && parsedPage <= 0) || (parsedPerPage && parsedPerPage <= 0)) { throw new HttpException( { message: Messages.PAGE_AND_PERPAGE_INVALID, @@ -220,8 +222,8 @@ export class TableController { const inputData: GetTableRowsDs = { connectionId: connectionId, masterPwd: masterPwd, - page: page, - perPage: perPage, + page: parsedPage, + perPage: parsedPerPage, query: query, searchingFieldValue: searchingFieldValue, tableName: tableName, @@ -249,10 +251,10 @@ export class TableController { @Post('/table/rows/find/:connectionId') async findAllRowsWithBodyFilter( @QueryTableName() tableName: string, - @Query('page') page: any, - @Query('perPage') perPage: any, + @Query('page') page: string, + @Query('perPage') perPage: string, @Query('search') searchingFieldValue: string, - @Query() query, + @Query() query: Record, @SlugUuid('connectionId') connectionId: string, @UserId() userId: string, @MasterPassword() masterPwd: string, @@ -266,10 +268,12 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } + let parsedPage: number; + let parsedPerPage: number; if (page && perPage) { - page = parseInt(page, 10); - perPage = parseInt(perPage, 10); - if ((page && page <= 0) || (perPage && perPage <= 0)) { + parsedPage = parseInt(page, 10); + parsedPerPage = parseInt(perPage, 10); + if ((parsedPage && parsedPage <= 0) || (parsedPerPage && parsedPerPage <= 0)) { throw new HttpException( { message: Messages.PAGE_AND_PERPAGE_INVALID, @@ -281,8 +285,8 @@ export class TableController { const inputData: GetTableRowsDs = { connectionId: connectionId, masterPwd: masterPwd, - page: page, - perPage: perPage, + page: parsedPage, + perPage: parsedPerPage, query: query, searchingFieldValue: searchingFieldValue, tableName: tableName, @@ -378,8 +382,8 @@ export class TableController { @UseGuards(TableEditGuard) @Put('/table/row/:connectionId') async updateRowInTable( - @Body() body: string, - @Query() query: string, + @Body() body: Record, + @Query() query: Record, @UserId() userId: string, @MasterPassword() masterPwd: string, @SlugUuid('connectionId') connectionId: string, @@ -403,7 +407,7 @@ export class TableController { connectionId: connectionId, masterPwd: masterPwd, primaryKey: primaryKey, - row: body as unknown as Record, + row: body, tableName: tableName, userId: userId, }; @@ -423,7 +427,7 @@ export class TableController { @UseGuards(TableDeleteGuard) @Delete('/table/row/:connectionId') async deleteRowInTable( - @Query() query: string, + @Query() query: Record, @MasterPassword() masterPwd: string, @SlugUuid('connectionId') connectionId: string, @UserId() userId: string, @@ -553,7 +557,7 @@ export class TableController { @UseGuards(TableReadGuard) @Get('/table/row/:connectionId') async getRowByPrimaryKey( - @Query() query: string, + @Query() query: Record, @MasterPassword() masterPwd: string, @SlugUuid('connectionId') connectionId: string, @UserId() userId: string, @@ -610,10 +614,10 @@ export class TableController { @Post('/table/csv/export/:connectionId') async exportCSVFromTable( @QueryTableName() tableName: string, - @Query('page') page: any, - @Query('perPage') perPage: any, + @Query('page') page: string, + @Query('perPage') perPage: string, @Query('search') searchingFieldValue: string, - @Query() query, + @Query() query: Record, @SlugUuid('connectionId') connectionId: string, @UserId() userId: string, @MasterPassword() masterPwd: string, @@ -627,10 +631,12 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } + let parsedPage: number; + let parsedPerPage: number; if (page && perPage) { - page = parseInt(page, 10); - perPage = parseInt(perPage, 10); - if ((page && page <= 0) || (perPage && perPage <= 0)) { + parsedPage = parseInt(page, 10); + parsedPerPage = parseInt(perPage, 10); + if ((parsedPage && parsedPage <= 0) || (parsedPerPage && parsedPerPage <= 0)) { throw new HttpException( { message: Messages.PAGE_AND_PERPAGE_INVALID, @@ -642,8 +648,8 @@ export class TableController { const inputData: GetTableRowsDs = { connectionId: connectionId, masterPwd: masterPwd, - page: page, - perPage: perPage, + page: parsedPage, + perPage: parsedPerPage, query: query, searchingFieldValue: searchingFieldValue, tableName: tableName, @@ -692,7 +698,7 @@ export class TableController { connectionId: connectionId, file: file, tableName: tableName, - materPwd: masterPwd, + masterPwd: masterPwd, userId: userId, }; await this.importCSVToTableUseCase.execute(inputData, InTransactionEnum.OFF); @@ -705,9 +711,9 @@ export class TableController { userId: string, connectionId: string, tableName: string, - query: string, + query: Record, masterPwd: string, - ): Promise> { + ): Promise>> { const primaryKeys = []; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); let userEmail: string; diff --git a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts index b799a0587..f76090be7 100644 --- a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts @@ -1,10 +1,7 @@ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; import { buildDAOsTableSettingsDs } from '@rocketadmin/shared-code/dist/src/helpers/data-structures-builders/table-settings.ds.builder.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; @@ -12,18 +9,16 @@ import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum, - WidgetTypeEnum, } from '../../../enums/index.js'; import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { isConnectionTypeAgent, isObjectEmpty, toPrettyErrorsMsg } from '../../../helpers/index.js'; +import { isObjectEmpty, toPrettyErrorsMsg } from '../../../helpers/index.js'; import { AmplitudeService } from '../../amplitude/amplitude.service.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { TableActionActivationService } from '../../table-actions/table-actions-module/table-action-activation.service.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { AddRowInTableDs } from '../application/data-structures/add-row-in-table.ds.js'; -import { ForeignKeyDSInfo, ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; +import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js'; import { convertHexDataInRowUtil } from '../utils/convert-hex-data-in-row.util.js'; import { formFullTableStructure } from '../utils/form-full-table-structure.js'; @@ -33,6 +28,12 @@ import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.u import { validateTableRowUtil } from '../utils/validate-table-row.util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IAddRowInTable } from './table-use-cases.interface.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; +import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; +import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; +import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { filterReferencedTablesByPermission, enrichReferencedTablesWithDisplayNames } from '../utils/process-referenced-tables.util.js'; +import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @Injectable() export class AddRowInTableUseCase extends AbstractUseCase implements IAddRowInTable { @@ -51,25 +52,12 @@ export class AddRowInTableUseCase extends AbstractUseCase el.tableName === tableName); @@ -110,42 +98,12 @@ export class AddRowInTableUseCase extends AbstractUseCase { - const canUserReadTable = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - referencedByTable.table_name, - masterPwd, - ); - return canUserReadTable ? referencedByTable : null; - }), - ).then((results) => results.filter(Boolean)); - } - - const referencedTableNamesAndColumnsWithTablesDisplayNames: Array = []; - - for (const el of referencedTableNamesAndColumns) { - const { referenced_by, referenced_on_column_name } = el; - const referenced_by_with_display_name = await Promise.all( - referenced_by.map(async (element) => { - const foundTableSettings = await this._dbContext.tableSettingsRepository.findTableSettings( - connectionId, - element.table_name, - ); - return { - ...element, - display_name: foundTableSettings?.display_name || null, - }; - }), - ); - - referencedTableNamesAndColumnsWithTablesDisplayNames.push({ - referenced_on_column_name, - referenced_by: referenced_by_with_display_name, - }); - } + await filterReferencedTablesByPermission(referencedTableNamesAndColumns, userId, connectionId, masterPwd, this.cedarPermissions); + const referencedTableNamesAndColumnsWithTablesDisplayNames = await enrichReferencedTablesWithDisplayNames( + referencedTableNamesAndColumns, + connectionId, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ); if (tableSettings && !tableSettings?.can_add) { throw new HttpException( @@ -156,44 +114,24 @@ export class AddRowInTableUseCase extends AbstractUseCase = tableWidgets - .filter((el) => el.widget_type === WidgetTypeEnum.Foreign_key) - .map((widget) => widget.widget_params as unknown as ForeignKeyDSInfo); + const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets); let foreignKeysWithKeysFromWidgets = [...tableForeignKeys, ...foreignKeysFromWidgets]; let foreignKeysWithAutocompleteColumns: Array = []; - const canUserReadForeignTables = await Promise.all( - foreignKeysWithKeysFromWidgets.map(async (foreignKey) => { - const canRead = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - foreignKey.referenced_table_name, - masterPwd, - ); - return { - tableName: foreignKey.referenced_table_name, - canRead, - }; - }), - ); - - const canReadMap = new Map(canUserReadForeignTables.map((item) => [item.tableName, item.canRead])); - - foreignKeysWithKeysFromWidgets = foreignKeysWithKeysFromWidgets.filter((foreignKey) => - canReadMap.get(foreignKey.referenced_table_name), + foreignKeysWithKeysFromWidgets = await filterForeignKeysByReadPermission( + foreignKeysWithKeysFromWidgets, userId, connectionId, masterPwd, this.cedarPermissions, ); if (foreignKeysWithKeysFromWidgets?.length > 0) { foreignKeysWithAutocompleteColumns = await Promise.all( - foreignKeysWithKeysFromWidgets.map(async (el) => { - try { - return await this.attachForeignColumnNames(el, userId, connectionId, dao); - } catch (_e) { - return el as ForeignKeyWithAutocompleteColumnsDS; - } - }), + foreignKeysWithKeysFromWidgets.map((el) => + attachForeignColumnNames( + el, userEmail, connectionId, dao, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ).catch(() => el as ForeignKeyWithAutocompleteColumnsDS), + ), ); } @@ -215,11 +153,6 @@ export class AddRowInTableUseCase extends AbstractUseCase; if (addedRowPrimaryKey && !isObjectEmpty(addedRowPrimaryKey)) { operationResult = OperationResultStatusEnum.successfully; @@ -241,19 +174,7 @@ export class AddRowInTableUseCase extends AbstractUseCase 0 ? builtDAOsTableSettings.sortable_by : [], - ordering: builtDAOsTableSettings.ordering ? builtDAOsTableSettings.ordering : undefined, - identity_column: builtDAOsTableSettings.identity_column ? builtDAOsTableSettings.identity_column : null, - list_fields: builtDAOsTableSettings?.list_fields?.length > 0 ? builtDAOsTableSettings.list_fields : [], - allow_csv_export: allowCsvExport, - allow_csv_import: allowCsvImport, - can_delete: can_delete, - can_update: can_update, - can_add: can_add, - columns_view: builtDAOsTableSettings?.columns_view ? builtDAOsTableSettings.columns_view : [], - ordering_field: builtDAOsTableSettings.ordering_field ? builtDAOsTableSettings.ordering_field : undefined, - }, + table_settings: buildTableSettingsForResponse(builtDAOsTableSettings, tableSettings), }; } } catch (e) { @@ -299,33 +220,4 @@ export class AddRowInTableUseCase extends AbstractUseCase { - try { - const foreignTableSettings = await this._dbContext.tableSettingsRepository.findTableSettings( - connectionId, - foreignKey.referenced_table_name, - ); - const foreignTableStructure = await dao.getTableStructure(foreignKey.referenced_table_name, userId); - - const columnNames = foreignTableStructure - .map((el) => el.column_name) - .filter((el) => foreignTableSettings?.autocomplete_columns.includes(el)); - - return { - ...foreignKey, - autocomplete_columns: columnNames, - }; - } catch (_e) { - return { - ...foreignKey, - autocomplete_columns: [], - }; - } - } } diff --git a/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts b/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts index 7a918d39c..63c62a68b 100644 --- a/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts @@ -7,10 +7,9 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { LogOperationTypeEnum } from '../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { UpdateRowsInTableDs } from '../application/data-structures/update-rows-in-table.ds.js'; @@ -46,24 +45,10 @@ export class BulkUpdateRowsInTableUseCase } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); const isView = await dao.isView(tableName, userEmail); if (isView) { diff --git a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts index 2e48d94ca..6ac00b794 100644 --- a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts @@ -8,10 +8,10 @@ import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; import { DeleteRowException } from '../../../exceptions/custom-exceptions/delete-row-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { compareArrayElements, isConnectionTypeAgent } from '../../../helpers/index.js'; +import { compareArrayElements } from '../../../helpers/index.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; import { AmplitudeService } from '../../amplitude/amplitude.service.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { TableActionActivationService } from '../../table-actions/table-actions-module/table-action-activation.service.js'; @@ -51,25 +51,10 @@ export class DeleteRowFromTableUseCase } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); const dao = getDataAccessObject(connection); - let userEmail: string; - - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); const isView = await dao.isView(tableName, userEmail); if (isView) { diff --git a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts index 2afaee3a0..011f65978 100644 --- a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts @@ -5,9 +5,9 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum } from '../../../enums/index.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { compareArrayElements, isConnectionTypeAgent } from '../../../helpers/index.js'; +import { compareArrayElements } from '../../../helpers/index.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; import { AmplitudeService } from '../../amplitude/amplitude.service.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; @@ -50,24 +50,10 @@ export class DeleteRowsFromTableUseCase ); } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); const isView = await dao.isView(tableName, userEmail); if (isView) { throw new HttpException( diff --git a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts index 969a0d4f3..ed19689d7 100644 --- a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts @@ -6,11 +6,11 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { LogOperationTypeEnum, OperationResultStatusEnum } from '../../../enums/index.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { hexToBinary, isBinary } from '../../../helpers/binary-to-hex.js'; import { slackPostMessage } from '../../../helpers/index.js'; import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; import { isObjectEmpty } from '../../../helpers/is-object-empty.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js'; @@ -37,28 +37,14 @@ export class ExportCSVFromTableUseCase // eslint-disable-next-line prefer-const let { connectionId, masterPwd, page, perPage, query, searchingFieldValue, tableName, userId, filters } = inputData; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); let operationResult = OperationResultStatusEnum.unknown; try { const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); // eslint-disable-next-line prefer-const let [tableSettings, tableStructure, personalTableSettings] = await Promise.all([ diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts index d58d8061f..27bf6888a 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts @@ -1,9 +1,7 @@ import { HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { TableDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table.ds.js'; -import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; import * as Sentry from '@sentry/node'; -import PQueue from 'p-queue'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; @@ -17,11 +15,10 @@ import { ConnectionEntity } from '../../connection/connection.entity.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { WinstonLogger } from '../../logging/winston-logger.js'; import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; -import { TableInfoEntity } from '../../table-info/table-info.entity.js'; -import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; import { FindTablesDs } from '../application/data-structures/find-tables.ds.js'; import { FoundTableDs, FoundTablesWithCategoriesDS } from '../application/data-structures/found-table.ds.js'; -import { buildTableFieldInfoEntity, buildTableInfoEntity } from '../utils/save-tables-info-in-database.util.js'; +import { saveTableInfoInDatabase } from '../utils/save-table-info-in-database-orchestrator.util.js'; +import { addDisplayNamesForTables } from '../utils/add-display-names-for-tables.util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IFindTablesInConnectionV2 } from './table-use-cases.interface.js'; @@ -107,7 +104,7 @@ export class FindTablesInConnectionV2UseCase operationResult && process.env.NODE_ENV !== 'test' ) { - this.saveTableInfoInDatabase(connection.id, userId, tables, masterPwd); + saveTableInfoInDatabase(connection.id, tables, masterPwd, this._dbContext); } } const tableNames = tables.map((t) => t.tableName); @@ -118,7 +115,8 @@ export class FindTablesInConnectionV2UseCase })); const foundConnectionProperties = await this._dbContext.connectionPropertiesRepository.findConnectionPropertiesWithTablesCategories(connectionId); - let tablesRO = await this.addDisplayNamesForTables(connectionId, tablesWithPermissions); + const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); + let tablesRO = addDisplayNamesForTables(tableSettings, tablesWithPermissions); if (foundConnectionProperties && foundConnectionProperties.hidden_tables.length > 0) { if (!hiddenTablesOption) { tablesRO = tablesRO.filter((tableRO) => { @@ -159,85 +157,4 @@ export class FindTablesInConnectionV2UseCase }; return responseObject; } - - private async addDisplayNamesForTables( - connectionId: string, - tablesObjArr: Array, - ): Promise> { - const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); - return tablesObjArr.map((tableObj: ITableAndViewPermissionData) => { - const foundTableSettings = - tableSettings[ - tableSettings.findIndex((el: TableSettingsEntity) => { - return el.table_name === tableObj.tableName; - }) - ]; - const displayName = foundTableSettings ? foundTableSettings.display_name : undefined; - const icon = foundTableSettings ? foundTableSettings.icon : undefined; - return { - table: tableObj.tableName, - isView: tableObj.isView || false, - permissions: tableObj.accessLevel, - display_name: displayName, - icon: icon, - }; - }); - } - - private async saveTableInfoInDatabase( - connectionId: string, - _userId: string, - tables: Array, - masterPwd: string, - ): Promise { - try { - const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); - if (!foundConnection) { - return; - } - const decryptedConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); - const tableNames: Array = tables.map((table) => table.tableName); - const queue = new PQueue({ concurrency: 2 }); - const dao = getDataAccessObject(decryptedConnection); - const tablesStructures: Array<{ - tableName: string; - structure: Array; - }> = (await Promise.all( - tableNames.map(async (tableName) => { - return await queue.add(async () => { - const structure = await dao.getTableStructure(tableName, undefined); - return { - tableName: tableName, - structure: structure, - }; - }); - }), - )) as Array<{ - tableName: string; - structure: Array; - }>; - foundConnection.tables_info = (await Promise.all( - tablesStructures.map(async (tableStructure) => { - return await queue.add(async () => { - const newTableInfo = buildTableInfoEntity(tableStructure.tableName, foundConnection); - const savedTableInfo = await this._dbContext.tableInfoRepository.save(newTableInfo); - const newTableFieldsInfos = tableStructure.structure.map((el) => - buildTableFieldInfoEntity(el, savedTableInfo), - ); - newTableInfo.table_fields_info = await this._dbContext.tableFieldInfoRepository.save(newTableFieldsInfos); - await this._dbContext.tableInfoRepository.save(newTableInfo); - return newTableInfo; - }); - }), - )) as Array; - foundConnection.saved_table_info = ++foundConnection.saved_table_info; - await this._dbContext.connectionRepository.saveUpdatedConnection(foundConnection); - } catch (e) { - Sentry.captureException(e); - console.error(e); - } - } } diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts index 429318e65..d549d49cc 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts @@ -2,9 +2,7 @@ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { TableDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table.ds.js'; -import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; import * as Sentry from '@sentry/node'; -import PQueue from 'p-queue'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; @@ -18,11 +16,10 @@ import { ConnectionEntity } from '../../connection/connection.entity.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { WinstonLogger } from '../../logging/winston-logger.js'; import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; -import { TableInfoEntity } from '../../table-info/table-info.entity.js'; -import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; import { FindTablesDs } from '../application/data-structures/find-tables.ds.js'; import { FoundTableDs } from '../application/data-structures/found-table.ds.js'; -import { buildTableFieldInfoEntity, buildTableInfoEntity } from '../utils/save-tables-info-in-database.util.js'; +import { saveTableInfoInDatabase } from '../utils/save-table-info-in-database-orchestrator.util.js'; +import { addDisplayNamesForTables } from '../utils/add-display-names-for-tables.util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IFindTablesInConnection } from './table-use-cases.interface.js'; @@ -111,7 +108,7 @@ export class FindTablesInConnectionUseCase operationResult && process.env.NODE_ENV !== 'test' ) { - this.saveTableInfoInDatabase(connection.id, userId, tables, masterPwd); + saveTableInfoInDatabase(connection.id, tables, masterPwd, this._dbContext); } } const tableNames = tables.map((t) => t.tableName); @@ -121,7 +118,8 @@ export class FindTablesInConnectionUseCase isView: tables.find((t) => t.tableName === perm.tableName)?.isView || false, })); const excludedTables = await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); - let tablesRO = await this.addDisplayNamesForTables(connectionId, tablesWithPermissions); + const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); + let tablesRO = addDisplayNamesForTables(tableSettings, tablesWithPermissions); if (excludedTables?.hidden_tables?.length) { if (!hiddenTablesOption) { tablesRO = tablesRO.filter((tableRO) => { @@ -142,103 +140,10 @@ export class FindTablesInConnectionUseCase } } } - return tablesRO.sort((tableRO1, tableRO2) => { - const display_name1 = tableRO1.display_name; - const display_name2 = tableRO2.display_name; - if (display_name1 && display_name2) { - return display_name1.localeCompare(display_name2); - } - if (!display_name1 && !display_name2) { - return tableRO1.table.localeCompare(tableRO2.table); - } - if (!display_name1 && display_name2) { - return tableRO1.table.localeCompare(display_name2); - } - if (display_name1 && !display_name2) { - return display_name1.localeCompare(tableRO2.table); - } - return 0; - }); - } - - private async addDisplayNamesForTables( - connectionId: string, - tablesObjArr: Array, - ): Promise> { - const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); - return tablesObjArr.map((tableObj: ITableAndViewPermissionData) => { - const foundTableSettings = - tableSettings[ - tableSettings.findIndex((el: TableSettingsEntity) => { - return el.table_name === tableObj.tableName; - }) - ]; - const displayName = foundTableSettings ? foundTableSettings.display_name : undefined; - const icon = foundTableSettings ? foundTableSettings.icon : undefined; - return { - table: tableObj.tableName, - isView: tableObj.isView || false, - permissions: tableObj.accessLevel, - display_name: displayName, - icon: icon, - }; + return tablesRO.sort((a, b) => { + const nameA = a.display_name || a.table; + const nameB = b.display_name || b.table; + return nameA.localeCompare(nameB); }); } - - private async saveTableInfoInDatabase( - connectionId: string, - _userId: string, - tables: Array, - masterPwd: string, - ): Promise { - try { - const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); - if (!foundConnection) { - return; - } - const decryptedConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); - const tableNames: Array = tables.map((table) => table.tableName); - const queue = new PQueue({ concurrency: 2 }); - const dao = getDataAccessObject(decryptedConnection); - const tablesStructures: Array<{ - tableName: string; - structure: Array; - }> = (await Promise.all( - tableNames.map(async (tableName) => { - return await queue.add(async () => { - const structure = await dao.getTableStructure(tableName, undefined); - return { - tableName: tableName, - structure: structure, - }; - }); - }), - )) as Array<{ - tableName: string; - structure: Array; - }>; - foundConnection.tables_info = (await Promise.all( - tablesStructures.map(async (tableStructure) => { - return await queue.add(async () => { - const newTableInfo = buildTableInfoEntity(tableStructure.tableName, foundConnection); - const savedTableInfo = await this._dbContext.tableInfoRepository.save(newTableInfo); - const newTableFieldsInfos = tableStructure.structure.map((el) => - buildTableFieldInfoEntity(el, savedTableInfo), - ); - newTableInfo.table_fields_info = await this._dbContext.tableFieldInfoRepository.save(newTableFieldsInfos); - await this._dbContext.tableInfoRepository.save(newTableInfo); - return newTableInfo; - }); - }), - )) as Array; - foundConnection.saved_table_info = ++foundConnection.saved_table_info; - await this._dbContext.connectionRepository.saveUpdatedConnection(foundConnection); - } catch (e) { - Sentry.captureException(e); - console.error(e); - } - } } diff --git a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts index 5063ccabb..28431f240 100644 --- a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts +++ b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts @@ -2,25 +2,18 @@ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; -import { ReferencedTableNamesAndColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/referenced-table-names-columns.ds.js'; import { buildDAOsTableSettingsDs } from '@rocketadmin/shared-code/dist/src/helpers/data-structures-builders/table-settings.ds.builder.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; -import JSON5 from 'json5'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { WidgetTypeEnum } from '../../../enums/index.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { compareArrayElements, isConnectionTypeAgent } from '../../../helpers/index.js'; +import { compareArrayElements } from '../../../helpers/index.js'; import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js'; import { GetRowByPrimaryKeyDs } from '../application/data-structures/get-row-by-primary-key.ds.js'; -import { ForeignKeyDSInfo, ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; +import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js'; import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js'; import { findAvailableFields } from '../utils/find-available-fields.utils.js'; @@ -28,6 +21,12 @@ import { formFullTableStructure } from '../utils/form-full-table-structure.js'; import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IGetRowByPrimaryKey } from './table-use-cases.interface.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; +import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; +import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; +import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { filterReferencedTablesByPermission, enrichReferencedTablesWithDisplayNames } from '../utils/process-referenced-tables.util.js'; +import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @Injectable() export class GetRowByPrimaryKeyUseCase @@ -54,25 +53,11 @@ export class GetRowByPrimaryKeyUseCase } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); await validateSchemaCache(dao, userEmail); @@ -113,54 +98,22 @@ export class GetRowByPrimaryKeyUseCase ); } - const foreignKeysFromWidgets: Array = tableWidgets - .filter((widget) => widget.widget_type === WidgetTypeEnum.Foreign_key) - .map((widget) => { - if (widget.widget_params) { - try { - const widgetParams = JSON5.parse(widget.widget_params) as ForeignKeyDSInfo; - return widgetParams; - } catch (_e) { - return null; - } - } - }) - .filter((el) => el !== null); + const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets); tableForeignKeys = tableForeignKeys.concat(foreignKeysFromWidgets); - const canUserReadForeignTables: Array<{ - tableName: string; - canRead: boolean; - }> = await Promise.all( - tableForeignKeys.map(async (foreignKey) => { - const cenTableRead = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - foreignKey.referenced_table_name, - masterPwd, - ); - return { - tableName: foreignKey.referenced_table_name, - canRead: cenTableRead, - }; - }), + tableForeignKeys = await filterForeignKeysByReadPermission( + tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions, ); - tableForeignKeys = tableForeignKeys.filter((foreignKey) => { - return canUserReadForeignTables.find((el) => { - return el.tableName === foreignKey.referenced_table_name && el.canRead; - }); - }); let foreignKeysWithAutocompleteColumns: Array = []; if (tableForeignKeys && tableForeignKeys.length > 0) { foreignKeysWithAutocompleteColumns = await Promise.all( - tableForeignKeys.map((el) => { - try { - return this.attachForeignColumnNames(el, userId, connectionId, dao); - } catch (_e) { - return el as ForeignKeyWithAutocompleteColumnsDS; - } - }), + tableForeignKeys.map((el) => + attachForeignColumnNames( + el, userEmail, connectionId, dao, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ).catch(() => el as ForeignKeyWithAutocompleteColumnsDS), + ), ); } let rowData: Record; @@ -183,47 +136,12 @@ export class GetRowByPrimaryKeyUseCase rowData = convertBinaryDataInRowUtil(rowData, tableStructure); const formedTableStructure = formFullTableStructure(tableStructure, tableSettings); - for (const referencedTable of referencedTableNamesAndColumns) { - referencedTable.referenced_by = await Promise.all( - referencedTable.referenced_by.map(async (referencedByTable) => { - const canUserReadTable = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - referencedByTable.table_name, - masterPwd, - ); - return canUserReadTable ? referencedByTable : null; - }), - ).then((results) => results.filter(Boolean)); - } - - const referencedTableNamesAndColumnsWithTablesDisplayNames: Array = - await Promise.all( - referencedTableNamesAndColumns.map(async (el: ReferencedTableNamesAndColumnsDS) => { - const { referenced_by, referenced_on_column_name } = el; - const responseObject: ReferencedTableNamesAndColumnsDs = { - referenced_on_column_name: referenced_on_column_name, - referenced_by: [], - }; - for (const element of referenced_by) { - const foundTableSettings = await this._dbContext.tableSettingsRepository.findTableSettings( - connectionId, - element.table_name, - ); - const displayName = foundTableSettings?.display_name ? foundTableSettings.display_name : null; - responseObject.referenced_by.push({ - ...element, - display_name: displayName, - }); - } - return responseObject; - }), - ); - const allowCsvExport = tableSettings?.allow_csv_export ?? true; - const allowCsvImport = tableSettings?.allow_csv_import ?? true; - const can_delete = tableSettings?.can_delete ?? true; - const can_update = tableSettings?.can_update ?? true; - const can_add = tableSettings?.can_add ?? true; + await filterReferencedTablesByPermission(referencedTableNamesAndColumns, userId, connectionId, masterPwd, this.cedarPermissions); + const referencedTableNamesAndColumnsWithTablesDisplayNames = await enrichReferencedTablesWithDisplayNames( + referencedTableNamesAndColumns, + connectionId, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ); //todo remove unnecessary fields return { row: rowData, @@ -243,52 +161,7 @@ export class GetRowByPrimaryKeyUseCase can_delete: tableSettings ? tableSettings.can_delete : true, can_update: tableSettings ? tableSettings.can_update : true, can_add: tableSettings ? tableSettings.can_add : true, - table_settings: { - sortable_by: builtDAOsTableSettings?.sortable_by?.length > 0 ? builtDAOsTableSettings.sortable_by : [], - ordering: builtDAOsTableSettings.ordering ? builtDAOsTableSettings.ordering : undefined, - identity_column: builtDAOsTableSettings.identity_column ? builtDAOsTableSettings.identity_column : null, - list_fields: builtDAOsTableSettings?.list_fields?.length > 0 ? builtDAOsTableSettings.list_fields : [], - allow_csv_export: allowCsvExport, - allow_csv_import: allowCsvImport, - can_delete: can_delete, - can_update: can_update, - can_add: can_add, - columns_view: builtDAOsTableSettings?.columns_view ? builtDAOsTableSettings.columns_view : [], - ordering_field: builtDAOsTableSettings.ordering_field ? builtDAOsTableSettings.ordering_field : undefined, - }, + table_settings: buildTableSettingsForResponse(builtDAOsTableSettings, tableSettings), }; } - - private async attachForeignColumnNames( - foreignKey: ForeignKeyDS, - userId: string, - connectionId: string, - dao: IDataAccessObject | IDataAccessObjectAgent, - ): Promise { - try { - const [foreignTableSettings, foreignTableStructure] = await Promise.all([ - this._dbContext.tableSettingsRepository.findTableSettings(connectionId, foreignKey.referenced_table_name), - dao.getTableStructure(foreignKey.referenced_table_name, userId), - ]); - - let columnNames = foreignTableStructure.map((el) => { - return el.column_name; - }); - if (foreignTableSettings && foreignTableSettings.autocomplete_columns.length > 0) { - columnNames = columnNames.filter((el) => { - const index = foreignTableSettings.autocomplete_columns.indexOf(el); - return index >= 0; - }); - } - return { - ...foreignKey, - autocomplete_columns: columnNames, - }; - } catch (_e) { - return { - ...foreignKey, - autocomplete_columns: [], - }; - } - } } diff --git a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts index 65b7db1d5..7a74b725e 100644 --- a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts @@ -10,7 +10,6 @@ import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/inte import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; import { FoundRowsDS } from '@rocketadmin/shared-code/src/data-access-layer/shared/data-structures/found-rows.ds.js'; import Sentry from '@sentry/minimal'; -import JSON5 from 'json5'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; @@ -18,15 +17,13 @@ import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum, - WidgetTypeEnum, } from '../../../enums/index.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { hexToBinary, isBinary } from '../../../helpers/binary-to-hex.js'; import { Constants } from '../../../helpers/constants/constants.js'; -import { isConnectionTypeAgent, isObjectEmpty } from '../../../helpers/index.js'; +import { isObjectEmpty } from '../../../helpers/index.js'; import { AmplitudeService } from '../../amplitude/amplitude.service.js'; import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js'; import { buildCreatedTableFilterRO } from '../../table-filters/utils/build-created-table-filters-response-object.util.js'; @@ -35,7 +32,7 @@ import { TableSettingsEntity } from '../../table-settings/common-table-settings/ import { PersonalTableSettingsEntity } from '../../table-settings/personal-table-settings/personal-table-settings.entity.js'; import { FoundTableRowsDs } from '../application/data-structures/found-table-rows.ds.js'; import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js'; -import { FilteringFieldsDs, ForeignKeyDSInfo } from '../table-datastructures.js'; +import { FilteringFieldsDs } from '../table-datastructures.js'; import { findAutocompleteFieldsUtil } from '../utils/find-autocomplete-fields.util.js'; import { findAvailableFields } from '../utils/find-available-fields.utils.js'; import { findFilteringFieldsUtil, parseFilteringFieldsFromBodyData } from '../utils/find-filtering-fields.util.js'; @@ -45,6 +42,11 @@ import { isHexString } from '../utils/is-hex-string.js'; import { processRowsUtil } from '../utils/process-found-rows-util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IGetTableRows } from './table-use-cases.interface.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; +import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; +import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; +import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @Injectable() export class GetTableRowsUseCase extends AbstractUseCase implements IGetTableRows { @@ -64,18 +66,7 @@ export class GetTableRowsUseCase extends AbstractUseCase = tableWidgets - .filter((widget) => widget.widget_type === WidgetTypeEnum.Foreign_key) - .reduce>((acc, widget) => { - if (widget.widget_params) { - try { - const widgetParams = JSON5.parse(widget.widget_params) as ForeignKeyDSInfo; - acc.push(widgetParams); - } catch (_e) { - // - } - } - return acc; - }, []); + const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets); tableForeignKeys = [...tableForeignKeys, ...foreignKeysFromWidgets]; - const canUserReadForeignTables = await Promise.all( - tableForeignKeys.map((foreignKey) => - this.cedarPermissions - .improvedCheckTableRead(userId, connectionId, foreignKey.referenced_table_name, masterPwd) - .then((canRead) => ({ - tableName: foreignKey.referenced_table_name, - canRead, - })), - ), - ); - - const readableForeignTables = new Set( - canUserReadForeignTables.filter(({ canRead }) => canRead).map(({ tableName }) => tableName), - ); - - tableForeignKeys = tableForeignKeys.filter(({ referenced_table_name }) => - readableForeignTables.has(referenced_table_name), + tableForeignKeys = await filterForeignKeysByReadPermission( + tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions, ); if (tableForeignKeys && tableForeignKeys.length > 0) { tableForeignKeys = await Promise.all( - tableForeignKeys.map((el) => this.attachForeignColumnNames(el, userId, connectionId, dao).catch(() => el)), + tableForeignKeys.map((el) => + attachForeignColumnNames( + el, userEmail, connectionId, dao, + this._dbContext.tableSettingsRepository.findTableSettingsPure.bind(this._dbContext.tableSettingsRepository), + ).catch(() => el), + ), ); } @@ -253,22 +219,10 @@ export class GetTableRowsUseCase extends AbstractUseCase 0 ? builtDAOsTableSettings.sortable_by : [], - ordering: builtDAOsTableSettings.ordering ? builtDAOsTableSettings.ordering : undefined, - identity_column: builtDAOsTableSettings.identity_column ? builtDAOsTableSettings.identity_column : null, - list_fields: builtDAOsTableSettings?.list_fields?.length > 0 ? builtDAOsTableSettings.list_fields : [], - allow_csv_export: allowCsvExport, - allow_csv_import: allowCsvImport, - can_delete: can_delete, - can_update: can_update, - can_add: can_add, - columns_view: builtDAOsTableSettings?.columns_view ? builtDAOsTableSettings.columns_view : [], - ordering_field: builtDAOsTableSettings.ordering_field ? builtDAOsTableSettings.ordering_field : undefined, - }, + table_settings: buildTableSettingsForResponse(builtDAOsTableSettings, tableSettings), }; - const identitiesMap = new Map(); + const identitiesMap = new Map>>(); if (tableForeignKeys?.length > 0) { const uniqueReferencedTables = [...new Set(tableForeignKeys.map((fk) => fk.referenced_table_name))]; @@ -402,34 +356,6 @@ export class GetTableRowsUseCase extends AbstractUseCase { - try { - const [foreignTableSettings, foreignTableStructure] = await Promise.all([ - this._dbContext.tableSettingsRepository.findTableSettingsPure(connectionId, foreignKey.referenced_table_name), - dao.getTableStructure(foreignKey.referenced_table_name, userId), - ]); - - const columnNames = foreignTableStructure - .map((el) => el.column_name) - .filter((el) => foreignTableSettings?.autocomplete_columns.includes(el)); - - return { - ...foreignKey, - autocomplete_columns: columnNames, - }; - } catch (_e) { - return { - ...foreignKey, - autocomplete_columns: [], - }; - } - } - private chunkArray(array: T[], chunkSize: number): T[][] { const results = []; for (let i = 0; i < array.length; i += chunkSize) { diff --git a/backend/src/entities/table/use-cases/get-table-structure.use.case.ts b/backend/src/entities/table/use-cases/get-table-structure.use.case.ts index 411c4da1d..308ca42fa 100644 --- a/backend/src/entities/table/use-cases/get-table-structure.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-structure.use.case.ts @@ -1,26 +1,23 @@ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; -import JSON5 from 'json5'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { WidgetTypeEnum } from '../../../enums/index.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { isConnectionTypeAgent } from '../../../helpers/index.js'; import { buildFoundTableWidgetDs } from '../../widget/utils/build-found-table-widget-ds.js'; import { GetTableStructureDs } from '../application/data-structures/get-table-structure-ds.js'; -import { ForeignKeyDSInfo, TableStructureDs } from '../table-datastructures.js'; +import { TableStructureDs } from '../table-datastructures.js'; import { formFullTableStructure } from '../utils/form-full-table-structure.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IGetTableStructure } from './table-use-cases.interface.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; +import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; +import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; +import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; @Injectable() export class GetTableStructureUseCase @@ -41,18 +38,7 @@ export class GetTableStructureUseCase connectionId, masterPwd, ); - if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (foundConnection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(foundConnection); try { const dao = getDataAccessObject(foundConnection); @@ -65,10 +51,7 @@ export class GetTableStructureUseCase HttpStatus.BAD_REQUEST, ); } - let userEmail: string; - if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(foundConnection, userId, this._dbContext.userRepository); await validateSchemaCache(dao, userEmail); @@ -82,54 +65,22 @@ export class GetTableStructureUseCase dao.getTableStructure(tableName, userEmail), this._dbContext.tableWidgetsRepository.findTableWidgets(connectionId, tableName), ]); - const foreignKeysFromWidgets: Array = tableWidgets - .filter((widget) => widget.widget_type === WidgetTypeEnum.Foreign_key) - .map((widget) => { - if (widget.widget_params) { - try { - const widgetParams = JSON5.parse(widget.widget_params) as ForeignKeyDSInfo; - return widgetParams; - } catch (_e) { - return null; - } - } - }) - .filter((el) => el !== null); + const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets); tableForeignKeys = tableForeignKeys.concat(foreignKeysFromWidgets); let transformedTableForeignKeys: Array = []; - const canUserReadForeignTables: Array<{ - tableName: string; - canRead: boolean; - }> = await Promise.all( - tableForeignKeys.map(async (foreignKey) => { - const cenTableRead = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - foreignKey.referenced_table_name, - masterPwd, - ); - return { - tableName: foreignKey.referenced_table_name, - canRead: cenTableRead, - }; - }), + tableForeignKeys = await filterForeignKeysByReadPermission( + tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions, ); - tableForeignKeys = tableForeignKeys.filter((foreignKey) => { - return canUserReadForeignTables.find((el) => { - return el.tableName === foreignKey.referenced_table_name && el.canRead; - }); - }); if (tableForeignKeys && tableForeignKeys.length > 0) { transformedTableForeignKeys = await Promise.all( - tableForeignKeys.map((el) => { - try { - return this.attachForeignColumnNames(el, userId, connectionId, dao); - } catch (_e) { - return el as ForeignKeyWithAutocompleteColumnsDS; - } - }), + tableForeignKeys.map((el) => + attachForeignColumnNames( + el, userEmail, connectionId, dao, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ).catch(() => el as ForeignKeyWithAutocompleteColumnsDS), + ), ); } const readonly_fields = tableSettings?.readonly_fields?.length > 0 ? tableSettings.readonly_fields : []; @@ -151,37 +102,4 @@ export class GetTableStructureUseCase throw new UnknownSQLException(e.message, ExceptionOperations.FAILED_TO_GET_TABLE_STRUCTURE); } } - - private async attachForeignColumnNames( - foreignKey: ForeignKeyDS, - userId: string, - connectionId: string, - dao: IDataAccessObject | IDataAccessObjectAgent, - ): Promise { - try { - const [foreignTableSettings, foreignTableStructure] = await Promise.all([ - this._dbContext.tableSettingsRepository.findTableSettings(connectionId, foreignKey.referenced_table_name), - dao.getTableStructure(foreignKey.referenced_table_name, userId), - ]); - - let columnNames = foreignTableStructure.map((el) => { - return el.column_name; - }); - if (foreignTableSettings && foreignTableSettings.autocomplete_columns.length > 0) { - columnNames = columnNames.filter((el) => { - const index = foreignTableSettings.autocomplete_columns.indexOf(el); - return index >= 0; - }); - } - return { - ...foreignKey, - autocomplete_columns: columnNames, - }; - } catch (_e) { - return { - ...foreignKey, - autocomplete_columns: [], - }; - } - } } diff --git a/backend/src/entities/table/use-cases/import-csv-in-table-user.case.ts b/backend/src/entities/table/use-cases/import-csv-in-table-user.case.ts index 4a69da468..80175a425 100644 --- a/backend/src/entities/table/use-cases/import-csv-in-table-user.case.ts +++ b/backend/src/entities/table/use-cases/import-csv-in-table-user.case.ts @@ -3,9 +3,8 @@ import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-acce import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; import { ImportCSVInTableDs } from '../application/data-structures/import-scv-in-table.ds.js'; import { IImportCSVFinTable } from './table-use-cases.interface.js'; @@ -22,20 +21,9 @@ export class ImportCSVInTableUseCase } protected async implementation(inputData: ImportCSVInTableDs): Promise { - const { file, tableName, connectionId, materPwd, userId } = inputData; - const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, materPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + const { file, tableName, connectionId, masterPwd, userId } = inputData; + const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); + validateConnection(connection); if (connection.isTestConnection) { throw new HttpException( @@ -59,10 +47,7 @@ export class ImportCSVInTableUseCase try { const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); await dao.importCSVInTable(file, tableName, userEmail); return true; } catch (error) { diff --git a/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts b/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts index 683210f84..0f64be8a3 100644 --- a/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts @@ -1,13 +1,8 @@ /* eslint-disable prefer-const */ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; -import { ReferencedTableNamesAndColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/referenced-table-names-columns.ds.js'; import { buildDAOsTableSettingsDs } from '@rocketadmin/shared-code/dist/src/helpers/data-structures-builders/table-settings.ds.builder.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; -import JSON5 from 'json5'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; @@ -15,16 +10,13 @@ import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum, - WidgetTypeEnum, } from '../../../enums/index.js'; import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; -import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { compareArrayElements, - isConnectionTypeAgent, isObjectEmpty, toPrettyErrorsMsg, } from '../../../helpers/index.js'; @@ -33,7 +25,7 @@ import { isTestConnectionUtil } from '../../connection/utils/is-test-connection- import { TableActionActivationService } from '../../table-actions/table-actions-module/table-action-activation.service.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { UpdateRowInTableDs } from '../application/data-structures/update-row-in-table.ds.js'; -import { ForeignKeyDSInfo, ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; +import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js'; import { formFullTableStructure } from '../utils/form-full-table-structure.js'; import { hashPasswordsInRowUtil } from '../utils/hash-passwords-in-row.util.js'; @@ -41,6 +33,12 @@ import { processUuidsInRowUtil } from '../utils/process-uuids-in-row-util.js'; import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.util.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IUpdateRowInTable } from './table-use-cases.interface.js'; +import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js'; +import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; +import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; +import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { filterReferencedTablesByPermission, enrichReferencedTablesWithDisplayNames } from '../utils/process-referenced-tables.util.js'; +import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @Injectable() export class UpdateRowInTableUseCase @@ -68,25 +66,11 @@ export class UpdateRowInTableUseCase } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); - } - - if (connection.is_frozen) { - throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); - } + validateConnection(connection); const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); const isView = await dao.isView(tableName, userEmail); if (isView) { throw new HttpException( @@ -117,42 +101,12 @@ export class UpdateRowInTableUseCase const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); - for (const referencedTable of referencedTableNamesAndColumns) { - referencedTable.referenced_by = await Promise.all( - referencedTable.referenced_by.map(async (referencedByTable) => { - const canUserReadTable = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - referencedByTable.table_name, - masterPwd, - ); - return canUserReadTable ? referencedByTable : null; - }), - ).then((results) => results.filter(Boolean)); - } - - const referencedTableNamesAndColumnsWithTablesDisplayNames: Array = - await Promise.all( - referencedTableNamesAndColumns.map(async (el: ReferencedTableNamesAndColumnsDS) => { - const { referenced_by, referenced_on_column_name } = el; - const responseObject: ReferencedTableNamesAndColumnsDs = { - referenced_on_column_name: referenced_on_column_name, - referenced_by: [], - }; - for (const element of referenced_by) { - const foundTableSettings = await this._dbContext.tableSettingsRepository.findTableSettings( - connectionId, - element.table_name, - ); - const displayName = foundTableSettings?.display_name ? foundTableSettings.display_name : null; - responseObject.referenced_by.push({ - ...element, - display_name: displayName, - }); - } - return responseObject; - }), - ); + await filterReferencedTablesByPermission(referencedTableNamesAndColumns, userId, connectionId, masterPwd, this.cedarPermissions); + const referencedTableNamesAndColumnsWithTablesDisplayNames = await enrichReferencedTablesWithDisplayNames( + referencedTableNamesAndColumns, + connectionId, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ); if (tableSettings && !tableSettings?.can_update) { throw new HttpException( @@ -172,55 +126,23 @@ export class UpdateRowInTableUseCase ); } - const foreignKeysFromWidgets: Array = tableWidgets - .filter((widget) => widget.widget_type === WidgetTypeEnum.Foreign_key) - .map((widget) => { - if (widget.widget_params) { - try { - const widgetParams = JSON5.parse(widget.widget_params) as ForeignKeyDSInfo; - return widgetParams; - } catch (_e) { - return null; - } - } - }) - .filter((el) => el !== null); + const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets); tableForeignKeys = tableForeignKeys.concat(foreignKeysFromWidgets); let foreignKeysWithAutocompleteColumns: Array = []; - const canUserReadForeignTables: Array<{ - tableName: string; - canRead: boolean; - }> = await Promise.all( - tableForeignKeys.map(async (foreignKey) => { - const cenTableRead = await this.cedarPermissions.improvedCheckTableRead( - userId, - connectionId, - foreignKey.referenced_table_name, - masterPwd, - ); - return { - tableName: foreignKey.referenced_table_name, - canRead: cenTableRead, - }; - }), + tableForeignKeys = await filterForeignKeysByReadPermission( + tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions, ); - tableForeignKeys = tableForeignKeys.filter((foreignKey) => { - return canUserReadForeignTables.find((el) => { - return el.tableName === foreignKey.referenced_table_name && el.canRead; - }); - }); if (tableForeignKeys && tableForeignKeys.length > 0) { foreignKeysWithAutocompleteColumns = await Promise.all( - tableForeignKeys.map((el) => { - try { - return this.attachForeignColumnNames(el, userId, connectionId, dao); - } catch (_e) { - return el as ForeignKeyWithAutocompleteColumnsDS; - } - }), + tableForeignKeys.map((el) => + attachForeignColumnNames( + el, userEmail, connectionId, dao, + this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository), + ).catch(() => el as ForeignKeyWithAutocompleteColumnsDS), + ), ); } @@ -279,11 +201,6 @@ export class UpdateRowInTableUseCase let updatedRow = await dao.getRowByPrimaryKey(tableName, futurePrimaryKey, builtDAOsTableSettings, userEmail); updatedRow = removePasswordsFromRowsUtil(updatedRow, tableWidgets); updatedRow = convertBinaryDataInRowUtil(updatedRow, tableStructure); - const allowCsvExport = tableSettings?.allow_csv_export ?? true; - const allowCsvImport = tableSettings?.allow_csv_import ?? true; - const can_delete = tableSettings?.can_delete ?? true; - const can_update = tableSettings?.can_update ?? true; - const can_add = tableSettings?.can_add ?? true; return { row: updatedRow, foreignKeys: foreignKeysWithAutocompleteColumns, @@ -299,19 +216,7 @@ export class UpdateRowInTableUseCase can_delete: tableSettings ? tableSettings.can_delete : true, can_update: tableSettings ? tableSettings.can_update : true, can_add: tableSettings ? tableSettings.can_add : true, - table_settings: { - sortable_by: builtDAOsTableSettings?.sortable_by?.length > 0 ? builtDAOsTableSettings.sortable_by : [], - ordering: builtDAOsTableSettings.ordering ? builtDAOsTableSettings.ordering : undefined, - identity_column: builtDAOsTableSettings.identity_column ? builtDAOsTableSettings.identity_column : null, - list_fields: builtDAOsTableSettings?.list_fields?.length > 0 ? builtDAOsTableSettings.list_fields : [], - allow_csv_export: allowCsvExport, - allow_csv_import: allowCsvImport, - can_delete: can_delete, - can_update: can_update, - can_add: can_add, - columns_view: builtDAOsTableSettings?.columns_view ? builtDAOsTableSettings.columns_view : [], - ordering_field: builtDAOsTableSettings.ordering_field ? builtDAOsTableSettings.ordering_field : undefined, - }, + table_settings: buildTableSettingsForResponse(builtDAOsTableSettings, tableSettings), }; } catch (e) { operationResult = OperationResultStatusEnum.unsuccessfully; @@ -348,36 +253,4 @@ export class UpdateRowInTableUseCase } } - private async attachForeignColumnNames( - foreignKey: ForeignKeyDS, - userId: string, - connectionId: string, - dao: IDataAccessObject | IDataAccessObjectAgent, - ): Promise { - try { - const [foreignTableSettings, foreignTableStructure] = await Promise.all([ - this._dbContext.tableSettingsRepository.findTableSettings(connectionId, foreignKey.referenced_table_name), - dao.getTableStructure(foreignKey.referenced_table_name, userId), - ]); - - let columnNames = foreignTableStructure.map((el) => { - return el.column_name; - }); - if (foreignTableSettings && foreignTableSettings.autocomplete_columns.length > 0) { - columnNames = columnNames.filter((el) => { - const index = foreignTableSettings.autocomplete_columns.indexOf(el); - return index >= 0; - }); - } - return { - ...foreignKey, - autocomplete_columns: columnNames, - }; - } catch (_e) { - return { - ...foreignKey, - autocomplete_columns: [], - }; - } - } } diff --git a/backend/src/entities/table/utils/add-display-names-for-tables.util.ts b/backend/src/entities/table/utils/add-display-names-for-tables.util.ts new file mode 100644 index 000000000..8a671064e --- /dev/null +++ b/backend/src/entities/table/utils/add-display-names-for-tables.util.ts @@ -0,0 +1,21 @@ +import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; +import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; +import { FoundTableDs } from '../application/data-structures/found-table.ds.js'; + +export function addDisplayNamesForTables( + tableSettings: Array, + tablesObjArr: Array, +): Array { + return tablesObjArr.map((tableObj: ITableAndViewPermissionData) => { + const foundTableSettings = tableSettings.find((el: TableSettingsEntity) => el.table_name === tableObj.tableName); + const displayName = foundTableSettings ? foundTableSettings.display_name : undefined; + const icon = foundTableSettings ? foundTableSettings.icon : undefined; + return { + table: tableObj.tableName, + isView: tableObj.isView || false, + permissions: tableObj.accessLevel, + display_name: displayName, + icon: icon, + }; + }); +} diff --git a/backend/src/entities/table/utils/attach-foreign-column-names.util.ts b/backend/src/entities/table/utils/attach-foreign-column-names.util.ts new file mode 100644 index 000000000..59aa59846 --- /dev/null +++ b/backend/src/entities/table/utils/attach-foreign-column-names.util.ts @@ -0,0 +1,37 @@ +import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; +import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; +import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; +import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; + +export async function attachForeignColumnNames( + foreignKey: ForeignKeyDS, + userEmail: string, + connectionId: string, + dao: IDataAccessObject | IDataAccessObjectAgent, + findTableSettings: ( + connectionId: string, + tableName: string, + ) => Promise<{ autocomplete_columns?: Array } | null>, +): Promise { + try { + const [foreignTableSettings, foreignTableStructure] = await Promise.all([ + findTableSettings(connectionId, foreignKey.referenced_table_name), + dao.getTableStructure(foreignKey.referenced_table_name, userEmail), + ]); + + let columnNames = foreignTableStructure.map((el) => el.column_name); + if (foreignTableSettings?.autocomplete_columns?.length > 0) { + columnNames = columnNames.filter((el) => foreignTableSettings.autocomplete_columns.includes(el)); + } + + return { + ...foreignKey, + autocomplete_columns: columnNames, + }; + } catch (_e) { + return { + ...foreignKey, + autocomplete_columns: [], + }; + } +} diff --git a/backend/src/entities/table/utils/build-table-settings-for-response.util.ts b/backend/src/entities/table/utils/build-table-settings-for-response.util.ts new file mode 100644 index 000000000..3188b0b5e --- /dev/null +++ b/backend/src/entities/table/utils/build-table-settings-for-response.util.ts @@ -0,0 +1,21 @@ +import { TableSettingsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-settings.ds.js'; +import { TableSettingsInRowsDS } from '../application/data-structures/found-table-rows.ds.js'; + +export function buildTableSettingsForResponse( + builtDAOsTableSettings: TableSettingsDS, + tableSettings?: { allow_csv_export?: boolean; allow_csv_import?: boolean; can_delete?: boolean; can_update?: boolean; can_add?: boolean } | null, +): TableSettingsInRowsDS { + return { + sortable_by: builtDAOsTableSettings?.sortable_by?.length > 0 ? builtDAOsTableSettings.sortable_by : [], + ordering: builtDAOsTableSettings.ordering ? builtDAOsTableSettings.ordering : undefined, + identity_column: builtDAOsTableSettings.identity_column ? builtDAOsTableSettings.identity_column : null, + list_fields: builtDAOsTableSettings?.list_fields?.length > 0 ? builtDAOsTableSettings.list_fields : [], + allow_csv_export: tableSettings?.allow_csv_export ?? true, + allow_csv_import: tableSettings?.allow_csv_import ?? true, + can_delete: tableSettings?.can_delete ?? true, + can_update: tableSettings?.can_update ?? true, + can_add: tableSettings?.can_add ?? true, + columns_view: builtDAOsTableSettings?.columns_view ? builtDAOsTableSettings.columns_view : [], + ordering_field: builtDAOsTableSettings.ordering_field ? builtDAOsTableSettings.ordering_field : undefined, + }; +} diff --git a/backend/src/entities/table/utils/extract-foreign-keys-from-widgets.util.ts b/backend/src/entities/table/utils/extract-foreign-keys-from-widgets.util.ts new file mode 100644 index 000000000..6ba2ce453 --- /dev/null +++ b/backend/src/entities/table/utils/extract-foreign-keys-from-widgets.util.ts @@ -0,0 +1,19 @@ +import JSON5 from 'json5'; +import { WidgetTypeEnum } from '../../../enums/index.js'; +import { TableWidgetEntity } from '../../widget/table-widget.entity.js'; +import { ForeignKeyDSInfo } from '../table-datastructures.js'; + +export function extractForeignKeysFromWidgets(tableWidgets: Array): Array { + return tableWidgets + .filter((widget) => widget.widget_type === WidgetTypeEnum.Foreign_key) + .reduce>((acc, widget) => { + if (widget.widget_params) { + try { + acc.push(JSON5.parse(widget.widget_params) as ForeignKeyDSInfo); + } catch (_e) { + // skip malformed widget params + } + } + return acc; + }, []); +} diff --git a/backend/src/entities/table/utils/filter-foreign-keys-by-permission.util.ts b/backend/src/entities/table/utils/filter-foreign-keys-by-permission.util.ts new file mode 100644 index 000000000..03e7259c7 --- /dev/null +++ b/backend/src/entities/table/utils/filter-foreign-keys-by-permission.util.ts @@ -0,0 +1,19 @@ +import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; +import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; + +export async function filterForeignKeysByReadPermission( + foreignKeys: Array, + userId: string, + connectionId: string, + masterPwd: string, + cedarPermissions: CedarPermissionsService, +): Promise> { + const canReadResults = await Promise.all( + foreignKeys.map(async (fk) => ({ + tableName: fk.referenced_table_name, + canRead: await cedarPermissions.improvedCheckTableRead(userId, connectionId, fk.referenced_table_name, masterPwd), + })), + ); + const readableSet = new Set(canReadResults.filter((r) => r.canRead).map((r) => r.tableName)); + return foreignKeys.filter((fk) => readableSet.has(fk.referenced_table_name)); +} diff --git a/backend/src/entities/table/utils/find-filtering-fields.util.ts b/backend/src/entities/table/utils/find-filtering-fields.util.ts index 03dcdfc6e..f16af041b 100644 --- a/backend/src/entities/table/utils/find-filtering-fields.util.ts +++ b/backend/src/entities/table/utils/find-filtering-fields.util.ts @@ -92,22 +92,23 @@ export function findFilteringFieldsUtil( } export function parseFilteringFieldsFromBodyData( - filtersDataFromBody: Record, + filtersDataFromBody: Record, tableStructure: Array, ): Array { const filteringItems: Array = []; const rowNames = tableStructure.map((el) => el.column_name); rowNames.forEach((rowName) => { if (isObjectPropertyExists(filtersDataFromBody, rowName)) { - for (const key in filtersDataFromBody[rowName]) { + const filterData = filtersDataFromBody[rowName] as Record; + for (const key in filterData) { if (!validateStringWithEnum(key, FilterCriteriaEnum)) { throw new Error(`Invalid filter criteria: "${key}".`); } - const isValueNull = filtersDataFromBody[rowName][key] === null && key === FilterCriteriaEnum.eq; + const isValueNull = filterData[key] === null && key === FilterCriteriaEnum.eq; filteringItems.push({ field: rowName, criteria: isValueNull ? FilterCriteriaEnum.empty : (key as FilterCriteriaEnum), - value: filtersDataFromBody[rowName][key], + value: filterData[key], }); } } diff --git a/backend/src/entities/table/utils/find-ordering-field.util.ts b/backend/src/entities/table/utils/find-ordering-field.util.ts index ef1a2c735..775e6b22e 100644 --- a/backend/src/entities/table/utils/find-ordering-field.util.ts +++ b/backend/src/entities/table/utils/find-ordering-field.util.ts @@ -4,13 +4,13 @@ import { QueryOrderingEnum } from '../../../enums/index.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isObjectPropertyExists } from '../../../helpers/validators/is-object-property-exists-validator.js'; import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; -import { OrderingFiledDs } from '../application/data-structures/found-table-rows.ds.js'; +import { OrderingFieldDs } from '../application/data-structures/found-table-rows.ds.js'; export function findOrderingFieldUtil( query: Record, tableStructure: Array, tableSettings: TableSettingsEntity, -): OrderingFiledDs | undefined { +): OrderingFieldDs | undefined { if (!isObjectPropertyExists(query, 'sort_by') || !isObjectPropertyExists(query, 'sort_order')) { return undefined; } diff --git a/backend/src/entities/table/utils/hash-passwords-in-row.util.ts b/backend/src/entities/table/utils/hash-passwords-in-row.util.ts index 6896fc7be..92cb4c536 100644 --- a/backend/src/entities/table/utils/hash-passwords-in-row.util.ts +++ b/backend/src/entities/table/utils/hash-passwords-in-row.util.ts @@ -23,7 +23,7 @@ export async function hashPasswordsInRowUtil( } if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '' && widgetParams.encrypt) { - row[widget.field_name] = await Encryptor.processDataWithAlgorithm(fieldValue as any, widgetParams.algorithm); + row[widget.field_name] = await Encryptor.processDataWithAlgorithm(fieldValue as string, widgetParams.algorithm); } } catch (e) { console.log('-> Error in password widget encryption processing', e); diff --git a/backend/src/entities/table/utils/process-referenced-tables.util.ts b/backend/src/entities/table/utils/process-referenced-tables.util.ts new file mode 100644 index 000000000..2c9010a01 --- /dev/null +++ b/backend/src/entities/table/utils/process-referenced-tables.util.ts @@ -0,0 +1,58 @@ +import { ReferencedTableNamesAndColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/referenced-table-names-columns.ds.js'; +import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; +import { ReferencedTableNamesAndColumnsDs } from '../table-datastructures.js'; + +export async function filterReferencedTablesByPermission( + referencedTables: Array, + userId: string, + connectionId: string, + masterPwd: string, + cedarPermissions: CedarPermissionsService, +): Promise { + await Promise.all( + referencedTables.map(async (referencedTable) => { + referencedTable.referenced_by = ( + await Promise.all( + referencedTable.referenced_by.map(async (ref) => { + const canRead = await cedarPermissions.improvedCheckTableRead( + userId, + connectionId, + ref.table_name, + masterPwd, + ); + return canRead ? ref : null; + }), + ) + ).filter(Boolean); + }), + ); +} + +export async function enrichReferencedTablesWithDisplayNames( + referencedTables: Array, + connectionId: string, + findTableSettings: (connectionId: string, tableName: string) => Promise<{ display_name?: string } | null>, +): Promise> { + const allTableNames = new Set(); + for (const rt of referencedTables) { + for (const ref of rt.referenced_by) { + allTableNames.add(ref.table_name); + } + } + + const settingsEntries = await Promise.all( + Array.from(allTableNames).map(async (tableName) => { + const settings = await findTableSettings(connectionId, tableName); + return [tableName, settings?.display_name || null] as const; + }), + ); + const displayNameMap = new Map(settingsEntries); + + return referencedTables.map((el) => ({ + referenced_on_column_name: el.referenced_on_column_name, + referenced_by: el.referenced_by.map((ref) => ({ + ...ref, + display_name: displayNameMap.get(ref.table_name) || null, + })), + })); +} diff --git a/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts b/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts new file mode 100644 index 000000000..e90093444 --- /dev/null +++ b/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts @@ -0,0 +1,65 @@ +import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; +import { TableDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table.ds.js'; +import { TableStructureDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-structure.ds.js'; +import * as Sentry from '@sentry/node'; +import PQueue from 'p-queue'; +import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; +import { TableInfoEntity } from '../../table-info/table-info.entity.js'; +import { buildTableFieldInfoEntity, buildTableInfoEntity } from './save-tables-info-in-database.util.js'; + +export async function saveTableInfoInDatabase( + connectionId: string, + tables: Array, + masterPwd: string, + dbContext: IGlobalDatabaseContext, +): Promise { + try { + const foundConnection = await dbContext.connectionRepository.findOne({ where: { id: connectionId } }); + if (!foundConnection) { + return; + } + const decryptedConnection = await dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPwd, + ); + const tableNames: Array = tables.map((table) => table.tableName); + const queue = new PQueue({ concurrency: 2 }); + const dao = getDataAccessObject(decryptedConnection); + const tablesStructures: Array<{ + tableName: string; + structure: Array; + }> = (await Promise.all( + tableNames.map(async (tableName) => { + return await queue.add(async () => { + const structure = await dao.getTableStructure(tableName, undefined); + return { + tableName: tableName, + structure: structure, + }; + }); + }), + )) as Array<{ + tableName: string; + structure: Array; + }>; + foundConnection.tables_info = (await Promise.all( + tablesStructures.map(async (tableStructure) => { + return await queue.add(async () => { + const newTableInfo = buildTableInfoEntity(tableStructure.tableName, foundConnection); + const savedTableInfo = await dbContext.tableInfoRepository.save(newTableInfo); + const newTableFieldsInfos = tableStructure.structure.map((el) => + buildTableFieldInfoEntity(el, savedTableInfo), + ); + newTableInfo.table_fields_info = await dbContext.tableFieldInfoRepository.save(newTableFieldsInfos); + await dbContext.tableInfoRepository.save(newTableInfo); + return newTableInfo; + }); + }), + )) as Array; + foundConnection.saved_table_info = ++foundConnection.saved_table_info; + await dbContext.connectionRepository.saveUpdatedConnection(foundConnection); + } catch (e) { + Sentry.captureException(e); + console.error(e); + } +} diff --git a/backend/src/entities/table/utils/validate-connection.util.ts b/backend/src/entities/table/utils/validate-connection.util.ts new file mode 100644 index 000000000..9fe935a85 --- /dev/null +++ b/backend/src/entities/table/utils/validate-connection.util.ts @@ -0,0 +1,30 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; +import { Messages } from '../../../exceptions/text/messages.js'; +import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; +import { ConnectionEntity } from '../../connection/connection.entity.js'; + +export function validateConnection(connection: ConnectionEntity | null): asserts connection is ConnectionEntity { + if (!connection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.BAD_REQUEST, + ); + } + if (connection.is_frozen) { + throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); + } +} + +export async function getUserEmailForAgent( + connection: ConnectionEntity, + userId: string, + userRepository: { getUserEmailOrReturnNull(userId: string): Promise }, +): Promise { + if (isConnectionTypeAgent(connection.type)) { + return userRepository.getUserEmailOrReturnNull(userId); + } + return undefined; +} diff --git a/backend/src/entities/table/utils/validate-table-row.util.ts b/backend/src/entities/table/utils/validate-table-row.util.ts index d25316bda..ff3f7bc84 100644 --- a/backend/src/entities/table/utils/validate-table-row.util.ts +++ b/backend/src/entities/table/utils/validate-table-row.util.ts @@ -3,10 +3,7 @@ import { Messages } from '../../../exceptions/text/messages.js'; export function validateTableRowUtil(row: Record, structure: Array): Array { const errors = []; - const keys = Object.keys(row); - keys.map((key) => { - key.toLowerCase(); - }); + const keys = Object.keys(row).map((key) => key.toLowerCase()); for (let i = 0; i < structure.length; i++) { try {