Skip to content

Commit 15f25eb

Browse files
guguclaude
andcommitted
Rewrite binary widgets and pass binary as Buffer-JSON end-to-end
- Rewrite edit/view/table-display/filter binary widgets as hex-only UI with clean signal-based state. Edit emits {type:'Buffer', data:number[]} and converts incoming hex strings to the byte-array shape on init so the save payload is always binary even if the user does not touch the field. - Parent db-table-row-edit.updateField now replaces a primitive field value when the incoming update is an object, so Buffer-JSON emissions actually land in tableRowValues. - Stop converting Buffer to hex strings on reads: delete convertBinaryDataInRowUtil and drop the binary->hex loop from processRowsUtil. Buffer values flow from the DAO into the HTTP response and Nest serializes them as {type:'Buffer', data:[...]} natively. - Accept Buffer-JSON (and Buffer/Uint8Array/hex) on writes via shared toBinaryBuffer helper used by convertHexDataInRowUtil and convertHexDataInPrimaryKeyUtil. Wire binary conversion into bulk-update-rows-in-table so bulk updates go through the same path as single-row updates. - Update Postgres binary e2e tests to derive hex for the search query from the returned Buffer-JSON and compare rows with deepEqual. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bb812fe commit 15f25eb

29 files changed

Lines changed: 692 additions & 128 deletions

backend/src/entities/table/use-cases/add-row-in-table.use.case.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,33 @@ import { buildDAOsTableSettingsDs } from '@rocketadmin/shared-code/dist/src/help
55
import AbstractUseCase from '../../../common/abstract-use.case.js';
66
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
77
import { BaseType } from '../../../common/data-injection.tokens.js';
8-
import {
9-
AmplitudeEventTypeEnum,
10-
LogOperationTypeEnum,
11-
OperationResultStatusEnum,
12-
} from '../../../enums/index.js';
8+
import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum } from '../../../enums/index.js';
139
import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js';
1410
import { Messages } from '../../../exceptions/text/messages.js';
1511
import { isObjectEmpty, toPrettyErrorsMsg } from '../../../helpers/index.js';
1612
import { AmplitudeService } from '../../amplitude/amplitude.service.js';
13+
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
1714
import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js';
1815
import { TableActionActivationService } from '../../table-actions/table-actions-module/table-action-activation.service.js';
1916
import { TableLogsService } from '../../table-logs/table-logs.service.js';
2017
import { AddRowInTableDs } from '../application/data-structures/add-row-in-table.ds.js';
2118
import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js';
22-
import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js';
19+
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
20+
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
2321
import { convertHexDataInRowUtil } from '../utils/convert-hex-data-in-row.util.js';
22+
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
23+
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
2424
import { formFullTableStructure } from '../utils/form-full-table-structure.js';
2525
import { hashPasswordsInRowUtil } from '../utils/hash-passwords-in-row.util.js';
26+
import {
27+
enrichReferencedTablesWithDisplayNames,
28+
filterReferencedTablesByPermission,
29+
} from '../utils/process-referenced-tables.util.js';
2630
import { processUuidsInRowUtil } from '../utils/process-uuids-in-row-util.js';
2731
import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.util.js';
32+
import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js';
2833
import { validateTableRowUtil } from '../utils/validate-table-row.util.js';
29-
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
3034
import { IAddRowInTable } from './table-use-cases.interface.js';
31-
import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js';
32-
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
33-
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
34-
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
35-
import { filterReferencedTablesByPermission, enrichReferencedTablesWithDisplayNames } from '../utils/process-referenced-tables.util.js';
36-
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
3735

3836
@Injectable()
3937
export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, TableRowRODs> implements IAddRowInTable {
@@ -98,7 +96,13 @@ export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, Table
9896
dao.getReferencedTableNamesAndColumns(tableName, userEmail),
9997
]);
10098

101-
await filterReferencedTablesByPermission(referencedTableNamesAndColumns, userId, connectionId, masterPwd, this.cedarPermissions);
99+
await filterReferencedTablesByPermission(
100+
referencedTableNamesAndColumns,
101+
userId,
102+
connectionId,
103+
masterPwd,
104+
this.cedarPermissions,
105+
);
102106
const referencedTableNamesAndColumnsWithTablesDisplayNames = await enrichReferencedTablesWithDisplayNames(
103107
referencedTableNamesAndColumns,
104108
connectionId,
@@ -121,14 +125,21 @@ export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, Table
121125
let foreignKeysWithAutocompleteColumns: Array<ForeignKeyWithAutocompleteColumnsDS> = [];
122126

123127
foreignKeysWithKeysFromWidgets = await filterForeignKeysByReadPermission(
124-
foreignKeysWithKeysFromWidgets, userId, connectionId, masterPwd, this.cedarPermissions,
128+
foreignKeysWithKeysFromWidgets,
129+
userId,
130+
connectionId,
131+
masterPwd,
132+
this.cedarPermissions,
125133
);
126134

127135
if (foreignKeysWithKeysFromWidgets?.length > 0) {
128136
foreignKeysWithAutocompleteColumns = await Promise.all(
129137
foreignKeysWithKeysFromWidgets.map((el) =>
130138
attachForeignColumnNames(
131-
el, userEmail, connectionId, dao,
139+
el,
140+
userEmail,
141+
connectionId,
142+
dao,
132143
this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository),
133144
).catch(() => el as ForeignKeyWithAutocompleteColumnsDS),
134145
),
@@ -158,7 +169,6 @@ export class AddRowInTableUseCase extends AbstractUseCase<AddRowInTableDs, Table
158169
operationResult = OperationResultStatusEnum.successfully;
159170
addedRow = await dao.getRowByPrimaryKey(tableName, addedRowPrimaryKey, builtDAOsTableSettings, userEmail);
160171
addedRow = removePasswordsFromRowsUtil(addedRow, tableWidgets);
161-
addedRow = convertBinaryDataInRowUtil(addedRow, tableStructure);
162172
return {
163173
row: addedRow,
164174
foreignKeys: foreignKeysWithAutocompleteColumns,

backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import { OperationResultStatusEnum } from '../../../enums/operation-result-statu
99
import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js';
1010
import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js';
1111
import { Messages } from '../../../exceptions/text/messages.js';
12-
import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js';
1312
import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js';
1413
import { TableLogsService } from '../../table-logs/table-logs.service.js';
1514
import { UpdateRowsInTableDs } from '../application/data-structures/update-rows-in-table.ds.js';
1615
import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js';
16+
import { convertHexDataInRowUtil } from '../utils/convert-hex-data-in-row.util.js';
1717
import { hashPasswordsInRowUtil } from '../utils/hash-passwords-in-row.util.js';
1818
import { processUuidsInRowUtil } from '../utils/process-uuids-in-row-util.js';
19+
import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js';
1920
import { IBulkUpdateRowsInTable } from './table-use-cases.interface.js';
2021

2122
@Injectable()
@@ -94,6 +95,7 @@ export class BulkUpdateRowsInTableUseCase
9495
try {
9596
let processedNewValues = await hashPasswordsInRowUtil(newValues, tableWidgets);
9697
processedNewValues = processUuidsInRowUtil(processedNewValues, tableWidgets);
98+
processedNewValues = convertHexDataInRowUtil(processedNewValues, tableStructure);
9799
await dao.bulkUpdateRowsInTable(tableName, processedNewValues, primaryKeys, userEmail);
98100
operationResult = OperationResultStatusEnum.successfully;
99101
return {

backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ import { ExceptionOperations } from '../../../exceptions/custom-exceptions/excep
1111
import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js';
1212
import { Messages } from '../../../exceptions/text/messages.js';
1313
import { compareArrayElements } from '../../../helpers/index.js';
14+
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
1415
import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js';
1516
import { GetRowByPrimaryKeyDs } from '../application/data-structures/get-row-by-primary-key.ds.js';
1617
import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js';
17-
import { convertBinaryDataInRowUtil } from '../utils/convert-binary-data-in-row.util.js';
18+
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
19+
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
1820
import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js';
21+
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
22+
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
1923
import { findAvailableFields } from '../utils/find-available-fields.utils.js';
2024
import { formFullTableStructure } from '../utils/form-full-table-structure.js';
25+
import {
26+
enrichReferencedTablesWithDisplayNames,
27+
filterReferencedTablesByPermission,
28+
} from '../utils/process-referenced-tables.util.js';
2129
import { removePasswordsFromRowsUtil } from '../utils/remove-password-from-row.util.js';
22-
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
30+
import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js';
2331
import { IGetRowByPrimaryKey } from './table-use-cases.interface.js';
24-
import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js';
25-
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
26-
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
27-
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
28-
import { filterReferencedTablesByPermission, enrichReferencedTablesWithDisplayNames } from '../utils/process-referenced-tables.util.js';
29-
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
3032

3133
@Injectable()
3234
export class GetRowByPrimaryKeyUseCase
@@ -102,15 +104,22 @@ export class GetRowByPrimaryKeyUseCase
102104

103105
tableForeignKeys = tableForeignKeys.concat(foreignKeysFromWidgets);
104106
tableForeignKeys = await filterForeignKeysByReadPermission(
105-
tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions,
107+
tableForeignKeys,
108+
userId,
109+
connectionId,
110+
masterPwd,
111+
this.cedarPermissions,
106112
);
107113

108114
let foreignKeysWithAutocompleteColumns: Array<ForeignKeyWithAutocompleteColumnsDS> = [];
109115
if (tableForeignKeys && tableForeignKeys.length > 0) {
110116
foreignKeysWithAutocompleteColumns = await Promise.all(
111117
tableForeignKeys.map((el) =>
112118
attachForeignColumnNames(
113-
el, userEmail, connectionId, dao,
119+
el,
120+
userEmail,
121+
connectionId,
122+
dao,
114123
this._dbContext.tableSettingsRepository.findTableSettings.bind(this._dbContext.tableSettingsRepository),
115124
).catch(() => el as ForeignKeyWithAutocompleteColumnsDS),
116125
),
@@ -133,10 +142,15 @@ export class GetRowByPrimaryKeyUseCase
133142
);
134143
}
135144
rowData = removePasswordsFromRowsUtil(rowData, tableWidgets);
136-
rowData = convertBinaryDataInRowUtil(rowData, tableStructure);
137145
const formedTableStructure = formFullTableStructure(tableStructure, tableSettings);
138146

139-
await filterReferencedTablesByPermission(referencedTableNamesAndColumns, userId, connectionId, masterPwd, this.cedarPermissions);
147+
await filterReferencedTablesByPermission(
148+
referencedTableNamesAndColumns,
149+
userId,
150+
connectionId,
151+
masterPwd,
152+
this.cedarPermissions,
153+
);
140154
const referencedTableNamesAndColumnsWithTablesDisplayNames = await enrichReferencedTablesWithDisplayNames(
141155
referencedTableNamesAndColumns,
142156
connectionId,

backend/src/entities/table/use-cases/get-table-rows.use.case.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@ import Sentry from '@sentry/minimal';
1313
import AbstractUseCase from '../../../common/abstract-use.case.js';
1414
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
1515
import { BaseType } from '../../../common/data-injection.tokens.js';
16-
import {
17-
AmplitudeEventTypeEnum,
18-
LogOperationTypeEnum,
19-
OperationResultStatusEnum,
20-
} from '../../../enums/index.js';
16+
import { AmplitudeEventTypeEnum, LogOperationTypeEnum, OperationResultStatusEnum } from '../../../enums/index.js';
2117
import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js';
2218
import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js';
2319
import { Messages } from '../../../exceptions/text/messages.js';
2420
import { hexToBinary, isBinary } from '../../../helpers/binary-to-hex.js';
2521
import { Constants } from '../../../helpers/constants/constants.js';
2622
import { isObjectEmpty } from '../../../helpers/index.js';
2723
import { AmplitudeService } from '../../amplitude/amplitude.service.js';
24+
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
2825
import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js';
2926
import { buildCreatedTableFilterRO } from '../../table-filters/utils/build-created-table-filters-response-object.util.js';
3027
import { TableLogsService } from '../../table-logs/table-logs.service.js';
@@ -33,20 +30,19 @@ import { PersonalTableSettingsEntity } from '../../table-settings/personal-table
3330
import { FoundTableRowsDs } from '../application/data-structures/found-table-rows.ds.js';
3431
import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js';
3532
import { FilteringFieldsDs } from '../table-datastructures.js';
33+
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
34+
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
35+
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
36+
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
3637
import { findAutocompleteFieldsUtil } from '../utils/find-autocomplete-fields.util.js';
3738
import { findAvailableFields } from '../utils/find-available-fields.utils.js';
3839
import { findFilteringFieldsUtil, parseFilteringFieldsFromBodyData } from '../utils/find-filtering-fields.util.js';
3940
import { findOrderingFieldUtil } from '../utils/find-ordering-field.util.js';
4041
import { formFullTableStructure } from '../utils/form-full-table-structure.js';
4142
import { isHexString } from '../utils/is-hex-string.js';
4243
import { processRowsUtil } from '../utils/process-found-rows-util.js';
43-
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
44+
import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js';
4445
import { IGetTableRows } from './table-use-cases.interface.js';
45-
import { validateConnection, getUserEmailForAgent } from '../utils/validate-connection.util.js';
46-
import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js';
47-
import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js';
48-
import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js';
49-
import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js';
5046

5147
@Injectable()
5248
export class GetTableRowsUseCase extends AbstractUseCase<GetTableRowsDs, FoundTableRowsDs> implements IGetTableRows {
@@ -166,22 +162,31 @@ export class GetTableRowsUseCase extends AbstractUseCase<GetTableRowsDs, FoundTa
166162
Sentry.captureException(e);
167163
throw new UnknownSQLException(e.message, ExceptionOperations.FAILED_TO_GET_ROWS_FROM_TABLE);
168164
}
169-
rows = processRowsUtil(rows, tableWidgets, tableStructure, tableCustomFields);
165+
rows = processRowsUtil(rows, tableWidgets, tableCustomFields);
170166

171167
const foreignKeysFromWidgets = extractForeignKeysFromWidgets(tableWidgets);
172168

173169
tableForeignKeys = [...tableForeignKeys, ...foreignKeysFromWidgets];
174170

175171
tableForeignKeys = await filterForeignKeysByReadPermission(
176-
tableForeignKeys, userId, connectionId, masterPwd, this.cedarPermissions,
172+
tableForeignKeys,
173+
userId,
174+
connectionId,
175+
masterPwd,
176+
this.cedarPermissions,
177177
);
178178

179179
if (tableForeignKeys && tableForeignKeys.length > 0) {
180180
tableForeignKeys = await Promise.all(
181181
tableForeignKeys.map((el) =>
182182
attachForeignColumnNames(
183-
el, userEmail, connectionId, dao,
184-
this._dbContext.tableSettingsRepository.findTableSettingsPure.bind(this._dbContext.tableSettingsRepository),
183+
el,
184+
userEmail,
185+
connectionId,
186+
dao,
187+
this._dbContext.tableSettingsRepository.findTableSettingsPure.bind(
188+
this._dbContext.tableSettingsRepository,
189+
),
185190
).catch(() => el),
186191
),
187192
);

0 commit comments

Comments
 (0)