|
| 1 | +import { HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; |
| 2 | +import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; |
| 3 | +import { TableDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table.ds.js'; |
| 4 | +import * as Sentry from '@sentry/node'; |
| 5 | +import AbstractUseCase from '../../../common/abstract-use.case.js'; |
| 6 | +import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; |
| 7 | +import { BaseType } from '../../../common/data-injection.tokens.js'; |
| 8 | +import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; |
| 9 | +import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; |
| 10 | +import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; |
| 11 | +import { Messages } from '../../../exceptions/text/messages.js'; |
| 12 | +import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; |
| 13 | +import { isObjectPropertyExists } from '../../../helpers/validators/is-object-property-exists-validator.js'; |
| 14 | +import { ConnectionEntity } from '../../connection/connection.entity.js'; |
| 15 | +import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; |
| 16 | +import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; |
| 17 | +import { FindTablesDs } from '../../table/application/data-structures/find-tables.ds.js'; |
| 18 | +import { FoundTableDs } from '../../table/application/data-structures/found-table.ds.js'; |
| 19 | +import { FoundTableCategoriesWithTablesRo } from '../dto/found-table-categories-with-tables.ro.js'; |
| 20 | +import { IFindTableCategoriesWithTables } from './table-categories-use-cases.interface.js'; |
| 21 | + |
| 22 | +@Injectable({ scope: Scope.REQUEST }) |
| 23 | +export class FindTableCategoriesWithTablesUseCase |
| 24 | + extends AbstractUseCase<FindTablesDs, Array<FoundTableCategoriesWithTablesRo>> |
| 25 | + implements IFindTableCategoriesWithTables |
| 26 | +{ |
| 27 | + constructor( |
| 28 | + @Inject(BaseType.GLOBAL_DB_CONTEXT) |
| 29 | + protected _dbContext: IGlobalDatabaseContext, |
| 30 | + ) { |
| 31 | + super(); |
| 32 | + } |
| 33 | + |
| 34 | + protected async implementation(inputData: FindTablesDs): Promise<Array<FoundTableCategoriesWithTablesRo>> { |
| 35 | + const { connectionId, masterPwd, userId } = inputData; |
| 36 | + let connection: ConnectionEntity; |
| 37 | + try { |
| 38 | + connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); |
| 39 | + } catch (error) { |
| 40 | + if (error.message === Messages.MASTER_PASSWORD_MISSING) { |
| 41 | + throw new HttpException( |
| 42 | + { |
| 43 | + message: Messages.MASTER_PASSWORD_MISSING, |
| 44 | + type: 'no_master_key', |
| 45 | + }, |
| 46 | + HttpStatus.BAD_REQUEST, |
| 47 | + ); |
| 48 | + } |
| 49 | + if (error.message === Messages.MASTER_PASSWORD_INCORRECT) { |
| 50 | + throw new HttpException( |
| 51 | + { |
| 52 | + message: Messages.MASTER_PASSWORD_INCORRECT, |
| 53 | + type: 'invalid_master_key', |
| 54 | + }, |
| 55 | + HttpStatus.BAD_REQUEST, |
| 56 | + ); |
| 57 | + } |
| 58 | + } |
| 59 | + if (!connection) { |
| 60 | + throw new HttpException( |
| 61 | + { |
| 62 | + message: Messages.CONNECTION_NOT_FOUND, |
| 63 | + }, |
| 64 | + HttpStatus.BAD_REQUEST, |
| 65 | + ); |
| 66 | + } |
| 67 | + const dao = getDataAccessObject(connection); |
| 68 | + let userEmail: string; |
| 69 | + |
| 70 | + if (isConnectionTypeAgent(connection.type)) { |
| 71 | + userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); |
| 72 | + } |
| 73 | + let tables: Array<TableDS>; |
| 74 | + try { |
| 75 | + tables = await dao.getTablesFromDB(userEmail); |
| 76 | + } catch (e) { |
| 77 | + Sentry.captureException(e); |
| 78 | + throw new UnknownSQLException(e.message, ExceptionOperations.FAILED_TO_GET_TABLES); |
| 79 | + } |
| 80 | + |
| 81 | + const tablesWithPermissions = await this.getUserPermissionsForAvailableTables(userId, connectionId, tables); |
| 82 | + const excludedTables = await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); |
| 83 | + let tablesRO = await this.addDisplayNamesForTables(connectionId, tablesWithPermissions); |
| 84 | + if (excludedTables?.hidden_tables?.length) { |
| 85 | + tablesRO = tablesRO.filter((tableRO) => { |
| 86 | + return !excludedTables.hidden_tables.includes(tableRO.table); |
| 87 | + }); |
| 88 | + } |
| 89 | + |
| 90 | + const foundTableCategories = |
| 91 | + await this._dbContext.tableCategoriesRepository.findTableCategoriesForConnection(connectionId); |
| 92 | + |
| 93 | + const sortedTables = tablesRO.sort((tableRO1, tableRO2) => { |
| 94 | + const name1 = tableRO1.display_name || tableRO1.table; |
| 95 | + const name2 = tableRO2.display_name || tableRO2.table; |
| 96 | + return name1.localeCompare(name2); |
| 97 | + }); |
| 98 | + |
| 99 | + const allTableCategory: FoundTableCategoriesWithTablesRo = { |
| 100 | + category_id: null, |
| 101 | + category_color: null, |
| 102 | + category_name: 'All tables', |
| 103 | + tables: sortedTables, |
| 104 | + }; |
| 105 | + const foundTableCategoriesRO = foundTableCategories.map((category) => { |
| 106 | + const tablesInCategory = sortedTables.filter((tableRO) => { |
| 107 | + return category.tables.includes(tableRO.table); |
| 108 | + }); |
| 109 | + return { |
| 110 | + category_id: category.category_id, |
| 111 | + category_color: category.category_color, |
| 112 | + category_name: category.category_name, |
| 113 | + tables: tablesInCategory, |
| 114 | + }; |
| 115 | + }); |
| 116 | + return [allTableCategory, ...foundTableCategoriesRO]; |
| 117 | + } |
| 118 | + |
| 119 | + private async addDisplayNamesForTables( |
| 120 | + connectionId: string, |
| 121 | + tablesObjArr: Array<ITableAndViewPermissionData>, |
| 122 | + ): Promise<Array<FoundTableDs>> { |
| 123 | + const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); |
| 124 | + return tablesObjArr.map((tableObj: ITableAndViewPermissionData) => { |
| 125 | + const foundTableSettings = |
| 126 | + tableSettings[ |
| 127 | + tableSettings.findIndex((el: TableSettingsEntity) => { |
| 128 | + return el.table_name === tableObj.tableName; |
| 129 | + }) |
| 130 | + ]; |
| 131 | + const displayName = foundTableSettings ? foundTableSettings.display_name : undefined; |
| 132 | + const icon = foundTableSettings ? foundTableSettings.icon : undefined; |
| 133 | + return { |
| 134 | + table: tableObj.tableName, |
| 135 | + isView: tableObj.isView || false, |
| 136 | + permissions: tableObj.accessLevel, |
| 137 | + display_name: displayName, |
| 138 | + icon: icon, |
| 139 | + }; |
| 140 | + }); |
| 141 | + } |
| 142 | + |
| 143 | + private async getUserPermissionsForAvailableTables( |
| 144 | + userId: string, |
| 145 | + connectionId: string, |
| 146 | + tables: Array<TableDS>, |
| 147 | + ): Promise<Array<ITableAndViewPermissionData>> { |
| 148 | + const connectionEdit = await this._dbContext.userAccessRepository.checkUserConnectionEdit(userId, connectionId); |
| 149 | + if (connectionEdit) { |
| 150 | + return tables.map((table) => { |
| 151 | + return { |
| 152 | + tableName: table.tableName, |
| 153 | + isView: table.isView, |
| 154 | + accessLevel: { |
| 155 | + visibility: true, |
| 156 | + readonly: false, |
| 157 | + add: true, |
| 158 | + delete: true, |
| 159 | + edit: true, |
| 160 | + }, |
| 161 | + }; |
| 162 | + }); |
| 163 | + } |
| 164 | + |
| 165 | + const allTablePermissions = |
| 166 | + await this._dbContext.permissionRepository.getAllUserPermissionsForAllTablesInConnection(userId, connectionId); |
| 167 | + const tablesAndAccessLevels = {}; |
| 168 | + tables.map((table) => { |
| 169 | + if (table.tableName !== '__proto__') { |
| 170 | + tablesAndAccessLevels[table.tableName] = []; |
| 171 | + } |
| 172 | + }); |
| 173 | + tables.map((table) => { |
| 174 | + allTablePermissions.map((permission) => { |
| 175 | + if ( |
| 176 | + permission.tableName === table.tableName && |
| 177 | + isObjectPropertyExists(tablesAndAccessLevels, table.tableName) |
| 178 | + ) { |
| 179 | + tablesAndAccessLevels[table.tableName].push(permission.accessLevel); |
| 180 | + } |
| 181 | + }); |
| 182 | + }); |
| 183 | + const tablesWithPermissions: Array<ITableAndViewPermissionData> = []; |
| 184 | + for (const key in tablesAndAccessLevels) { |
| 185 | + // eslint-disable-next-line security/detect-object-injection |
| 186 | + const addPermission = tablesAndAccessLevels[key].includes(AccessLevelEnum.add); |
| 187 | + // eslint-disable-next-line security/detect-object-injection |
| 188 | + const deletePermission = tablesAndAccessLevels[key].includes(AccessLevelEnum.delete); |
| 189 | + // eslint-disable-next-line security/detect-object-injection |
| 190 | + const editPermission = tablesAndAccessLevels[key].includes(AccessLevelEnum.edit); |
| 191 | + |
| 192 | + const readOnly = !(addPermission || deletePermission || editPermission); |
| 193 | + tablesWithPermissions.push({ |
| 194 | + tableName: key, |
| 195 | + isView: tables.find((table) => table.tableName === key).isView, |
| 196 | + accessLevel: { |
| 197 | + // eslint-disable-next-line security/detect-object-injection |
| 198 | + visibility: tablesAndAccessLevels[key].includes(AccessLevelEnum.visibility), |
| 199 | + // eslint-disable-next-line security/detect-object-injection |
| 200 | + readonly: tablesAndAccessLevels[key].includes(AccessLevelEnum.readonly) && !readOnly, |
| 201 | + add: addPermission, |
| 202 | + delete: deletePermission, |
| 203 | + edit: editPermission, |
| 204 | + }, |
| 205 | + }); |
| 206 | + } |
| 207 | + return tablesWithPermissions.filter((tableWithPermission: ITableAndViewPermissionData) => { |
| 208 | + return !!tableWithPermission.accessLevel.visibility; |
| 209 | + }); |
| 210 | + } |
| 211 | +} |
0 commit comments