Skip to content

Commit 0237c9d

Browse files
Merge branch 'main' into profile-structure-ux
2 parents 4f5bbd9 + 7a1008f commit 0237c9d

File tree

7 files changed

+362
-1
lines changed

7 files changed

+362
-1
lines changed

backend/src/common/data-injection.tokens.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export enum UseCaseType {
174174

175175
CREATE_UPDATE_TABLE_CATEGORIES = 'CREATE_UPDATE_TABLE_CATEGORIES',
176176
FIND_TABLE_CATEGORIES = 'FIND_TABLE_CATEGORIES',
177+
FIND_TABLE_CATEGORIES_WITH_TABLES = 'FIND_TABLE_CATEGORIES_WITH_TABLES',
177178

178179
CREATE_SECRET = 'CREATE_SECRET',
179180
GET_SECRETS = 'GET_SECRETS',
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { FoundTableDs } from '../../table/application/data-structures/found-table.ds.js';
3+
4+
export class FoundTableCategoriesWithTablesRo {
5+
@ApiProperty({ type: String })
6+
category_id: string;
7+
8+
@ApiProperty({ type: String })
9+
category_name: string;
10+
11+
@ApiProperty({ type: String })
12+
category_color: string;
13+
14+
@ApiProperty({ isArray: true, type: FoundTableDs })
15+
tables: Array<FoundTableDs>;
16+
}

backend/src/entities/table-categories/table-categories.controller.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
1111
import { CreateOrUpdateTableCategoriesDS } from './data-sctructures/create-or-update-table-categories.ds.js';
1212
import { CreateTableCategoryDto } from './dto/create-table-category.dto.js';
1313
import { FoundTableCategoryRo } from './dto/found-table-category.ro.js';
14-
import { ICreateTableCategories, IFindTableCategories } from './use-cases/table-categories-use-cases.interface.js';
14+
import {
15+
ICreateTableCategories,
16+
IFindTableCategories,
17+
IFindTableCategoriesWithTables,
18+
} from './use-cases/table-categories-use-cases.interface.js';
19+
import { FoundTableCategoriesWithTablesRo } from './dto/found-table-categories-with-tables.ro.js';
20+
import { UserId } from '../../decorators/user-id.decorator.js';
21+
import { FindTablesDs } from '../table/application/data-structures/find-tables.ds.js';
1522

1623
@UseInterceptors(SentryInterceptor)
1724
@Timeout()
@@ -25,6 +32,8 @@ export class TableCategoriesController {
2532
private readonly createTableCategoriesUseCase: ICreateTableCategories,
2633
@Inject(UseCaseType.FIND_TABLE_CATEGORIES)
2734
private readonly findTableCategoriesUseCase: IFindTableCategories,
35+
@Inject(UseCaseType.FIND_TABLE_CATEGORIES_WITH_TABLES)
36+
private readonly findTableCategoriesWithTablesUseCase: IFindTableCategoriesWithTables,
2837
) {}
2938

3039
@ApiOperation({ summary: 'Add new table categories' })
@@ -64,4 +73,28 @@ export class TableCategoriesController {
6473
async findTableCategories(@SlugUuid('connectionId') connectionId: string): Promise<Array<FoundTableCategoryRo>> {
6574
return await this.findTableCategoriesUseCase.execute(connectionId);
6675
}
76+
77+
@ApiOperation({ summary: 'Find table categories with tables' })
78+
@ApiResponse({
79+
status: 200,
80+
description: 'Table categories with tables found.',
81+
type: FoundTableCategoriesWithTablesRo,
82+
isArray: true,
83+
})
84+
@ApiParam({ name: 'connectionId', required: true })
85+
@UseGuards(TablesReceiveGuard)
86+
@Get('/v2/:connectionId')
87+
async findTableCategoriesWithTAbles(
88+
@SlugUuid('connectionId') connectionId: string,
89+
@UserId() userId: string,
90+
@MasterPassword() masterPwd: string,
91+
): Promise<Array<FoundTableCategoriesWithTablesRo>> {
92+
const inputData: FindTablesDs = {
93+
connectionId: connectionId,
94+
hiddenTablesOption: false,
95+
masterPwd: masterPwd,
96+
userId: userId,
97+
};
98+
return await this.findTableCategoriesWithTablesUseCase.execute(inputData);
99+
}
67100
}

backend/src/entities/table-categories/table-categories.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { UserEntity } from '../user/user.entity.js';
88
import { TableCategoriesController } from './table-categories.controller.js';
99
import { CreateOrUpdateTableCategoriesUseCase } from './use-cases/create-or-update-table-categories.use.case.js';
1010
import { FindTableCategoriesUseCase } from './use-cases/find-table-categories.use.case.js';
11+
import { FindTableCategoriesWithTablesUseCase } from './use-cases/find-table-categories-with-tables.use.case.js';
1112

1213
@Module({
1314
imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])],
@@ -24,6 +25,10 @@ import { FindTableCategoriesUseCase } from './use-cases/find-table-categories.us
2425
provide: UseCaseType.CREATE_UPDATE_TABLE_CATEGORIES,
2526
useClass: CreateOrUpdateTableCategoriesUseCase,
2627
},
28+
{
29+
provide: UseCaseType.FIND_TABLE_CATEGORIES_WITH_TABLES,
30+
useClass: FindTableCategoriesWithTablesUseCase,
31+
},
2732
],
2833
controllers: [TableCategoriesController],
2934
})
@@ -34,6 +39,7 @@ export class TableCategoriesModule {
3439
.forRoutes(
3540
{ path: '/table-categories/:connectionId/', method: RequestMethod.GET },
3641
{ path: '/table-categories/:connectionId/', method: RequestMethod.PUT },
42+
{ path: '/table-categories/v2/:connectionId/', method: RequestMethod.GET },
3743
);
3844
}
3945
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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+
}

backend/src/entities/table-categories/use-cases/table-categories-use-cases.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { InTransactionEnum } from '../../../enums/in-transaction.enum.js';
2+
import { FindTablesDs } from '../../table/application/data-structures/find-tables.ds.js';
23
import { CreateOrUpdateTableCategoriesDS } from '../data-sctructures/create-or-update-table-categories.ds.js';
4+
import { FoundTableCategoriesWithTablesRo } from '../dto/found-table-categories-with-tables.ro.js';
35
import { FoundTableCategoryRo } from '../dto/found-table-category.ro.js';
46

57
export interface ICreateTableCategories {
@@ -12,3 +14,7 @@ export interface ICreateTableCategories {
1214
export interface IFindTableCategories {
1315
execute(connectionId: string): Promise<Array<FoundTableCategoryRo>>;
1416
}
17+
18+
export interface IFindTableCategoriesWithTables {
19+
execute(inputData: FindTablesDs): Promise<Array<FoundTableCategoriesWithTablesRo>>;
20+
}

0 commit comments

Comments
 (0)