Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ jobs:
- name: Run Biome
if: steps.changed.outputs.files != ''
run: biome ci --formatter-enabled=false --assist-enabled=false ${{ steps.changed.outputs.files }}
- name: Typecheck backend
if: steps.changed.outputs.files != ''
run: pnpm --filter ./backend typecheck
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"typecheck": "tsc --noEmit --incremental false -p tsconfig.src.json && tsc --noEmit --incremental false -p tsconfig.json",
"lint": "biome lint src test",
"start": "nest start",
"start:dev": "nest start --watch --preserveWatchOutput",
"start:debug": "nest start --debug --watch",
Expand Down
7 changes: 4 additions & 3 deletions backend/src/entities/ai/ai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { cleanAIJsonResponse } from '../../ai-core/tools/query-validators.js';
import { QueryOrderingEnum } from '../../enums/query-ordering.enum.js';
import { WidgetTypeEnum } from '../../enums/widget-type.enum.js';
import { checkFieldAutoincrement } from '../../helpers/check-field-autoincrement.js';
import { getErrorMessage } from '../../helpers/get-error-message.js';
import { TableSettingsEntity } from '../table-settings/common-table-settings/table-settings.entity.js';
import { TableWidgetEntity } from '../widget/table-widget.entity.js';
import { TableInformation } from './ai-data-entities/types/ai-module-types.js';
Expand Down Expand Up @@ -78,13 +79,13 @@ export class AiService {
const batchSettings = await this.processTablesBatch(batch);
allSettings.push(...batchSettings);
} catch (error) {
console.warn(`Batch processing failed, falling back to individual table processing: ${error.message}`);
console.warn(`Batch processing failed, falling back to individual table processing: ${getErrorMessage(error)}`);
for (const tableInfo of batch) {
try {
const singleTableSettings = await this.processTablesBatch([tableInfo]);
allSettings.push(...singleTableSettings);
} catch (singleError) {
console.error(`Error processing AI for table "${tableInfo.table_name}": ${singleError.message}`);
console.error(`Error processing AI for table "${tableInfo.table_name}": ${getErrorMessage(singleError)}`);
}
}
}
Expand Down Expand Up @@ -289,7 +290,7 @@ IMPORTANT:
try {
return JSON.parse(cleanedResponse) as AIResponse;
} catch (error) {
throw new Error(`Failed to parse AI response for tables [${tableNames.join(', ')}]: ${error.message}`);
throw new Error(`Failed to parse AI response for tables [${tableNames.join(', ')}]: ${getErrorMessage(error)}`);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class RequestInfoFromTableWithAIUseCaseV7

response.end();
} catch (error) {
await slackPostMessage(error?.message);
await slackPostMessage((error as Error)?.message);
Sentry.captureException(error);
if (!response.headersSent) {
response.status(500).send({ error: 'An error occurred while processing your request.' });
Expand Down Expand Up @@ -192,7 +192,7 @@ export class RequestInfoFromTableWithAIUseCaseV7

depth++;
} catch (loopError) {
this.logger.error(`Error in tool loop at depth ${depth + 1}: ${loopError.message}`);
this.logger.error(`Error in tool loop at depth ${depth + 1}: ${(loopError as Error).message}`);
throw loopError;
}
}
Expand Down Expand Up @@ -290,8 +290,9 @@ export class RequestInfoFromTableWithAIUseCaseV7
result = encodeError({ error: `Unknown tool: ${toolCall.name}` });
}
} catch (error) {
this.logger.error(`Tool call ${toolCall.name} (${toolCall.id}) failed: ${error.message}`);
result = encodeError({ error: error.message });
const errMessage = (error as Error).message;
this.logger.error(`Tool call ${toolCall.name} (${toolCall.id}) failed: ${errMessage}`);
result = encodeError({ error: errMessage });
}

results.push({ toolCallId: toolCall.id, result });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
resource: { type: 'RocketAdmin::Connection', id: 'test' },
context: {},
policies: { staticPolicies: 'permit(principal, action, resource);' },
entities: [],
entities: [] as unknown[],
schema: schema,
};
const result = cedarWasm.isAuthorized(testCall as Parameters<typeof cedarWasm.isAuthorized>[0]);
Expand All @@ -198,7 +198,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
}
} catch (e) {
if (e instanceof HttpException) throw e;
throw new HttpException({ message: `Invalid cedar schema: ${e.message}` }, HttpStatus.BAD_REQUEST);
throw new HttpException({ message: `Invalid cedar schema: ${(e as Error).message}` }, HttpStatus.BAD_REQUEST);
}
}

Expand Down Expand Up @@ -291,12 +291,12 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
resource: { type: 'RocketAdmin::Connection', id: 'test' },
context: {},
policies: { staticPolicies: policyText },
entities: [],
entities: [] as unknown[],
schema: this.schema,
};
cedarWasm.isAuthorized(testCall as Parameters<typeof cedarWasm.isAuthorized>[0]);
} catch (e) {
throw new HttpException({ message: `Invalid cedar policy: ${e.message}` }, HttpStatus.BAD_REQUEST);
throw new HttpException({ message: `Invalid cedar policy: ${(e as Error).message}` }, HttpStatus.BAD_REQUEST);
}
}

Expand Down
6 changes: 3 additions & 3 deletions backend/src/entities/cedar-authorization/cedar-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const CEDAR_SCHEMA = {
RocketAdmin: {
entityTypes: {
User: {
memberOfTypes: [],
memberOfTypes: [] as string[],
shape: {
type: 'Record',
attributes: {
Expand All @@ -11,7 +11,7 @@ export const CEDAR_SCHEMA = {
},
},
Group: {
memberOfTypes: [],
memberOfTypes: [] as string[],
shape: {
type: 'Record',
attributes: {
Expand All @@ -21,7 +21,7 @@ export const CEDAR_SCHEMA = {
},
},
Connection: {
memberOfTypes: [],
memberOfTypes: [] as string[],
shape: {
type: 'Record',
attributes: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class FindAllConnectionsUseCase
return Object.keys(connection).reduce((acc, key) => {
if (allowedKeys.includes(key)) {
// eslint-disable-next-line security/detect-object-injection
acc[key] = connection[key];
(acc as Record<string, unknown>)[key] = (connection as unknown as Record<string, unknown>)[key];
}
return acc;
}, {} as FilteredConnection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class FindOneConnectionUseCase
return Object.keys(connection).reduce((acc, key) => {
if (allowedKeys.includes(key)) {
// eslint-disable-next-line security/detect-object-injection
acc[key] = connection[key];
(acc as Record<string, unknown>)[key] = (connection as unknown as Record<string, unknown>)[key];
}
return acc;
}, {} as FilteredConnection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class GetConnectionDiagramUseCase
try {
tables = await dao.getTablesFromDB(userEmail);
} catch (e) {
throw new UnknownSQLException(e.message, ExceptionOperations.FAILED_TO_GET_TABLES);
throw new UnknownSQLException((e as Error).message, ExceptionOperations.FAILED_TO_GET_TABLES);
}

const realTables = tables.filter((t) => !t.isView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class PreviewConnectionDiagramUseCase
try {
tables = await dao.getTablesFromDB(userEmail);
} catch (e) {
throw new UnknownSQLException(e.message, ExceptionOperations.FAILED_TO_GET_TABLES);
throw new UnknownSQLException((e as Error).message, ExceptionOperations.FAILED_TO_GET_TABLES);
}

const realTables = tables.filter((t) => !t.isView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ export class TestConnectionUseCase
}

['password', 'privateSSHKey', 'cert'].forEach((key) => {
const data = connectionData as unknown as Record<string, unknown>;
// eslint-disable-next-line security/detect-object-injection
if (!connectionData[key]) {
if (!data[key]) {
// eslint-disable-next-line security/detect-object-injection
delete connectionData[key];
delete data[key];
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ export class UpdateConnectionUseCase
.map(([key, _]) => key);

const keysToKeep = ['title', 'schema', ...booleanKeys];
const params = connection_parameters as unknown as Record<string, unknown>;
connection_parameters = Object.keys(connection_parameters).reduce(
(acc, key) => {
const accRec = acc as unknown as Record<string, unknown>;
// eslint-disable-next-line security/detect-object-injection
if (connection_parameters[key] || keysToKeep.includes(key)) {
if (params[key] || keysToKeep.includes(key)) {
// eslint-disable-next-line security/detect-object-injection
acc[key] = connection_parameters[key];
accRec[key] = params[key];
}
return acc;
},
Expand Down
13 changes: 8 additions & 5 deletions backend/src/entities/cron-jobs/cron-jobs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Mail from 'nodemailer/lib/mailer/index.js';
import { Repository } from 'typeorm';
import { UseCaseType } from '../../common/data-injection.tokens.js';
import { Constants } from '../../helpers/constants/constants.js';
import { getErrorMessage } from '../../helpers/get-error-message.js';
import { slackPostMessage } from '../../helpers/slack/slack-post-message.js';
import { ValidationHelper } from '../../helpers/validators/validation-helper.js';
import { EmailService, ICronMessagingResults } from '../email/email/email.service.js';
Expand Down Expand Up @@ -79,7 +80,7 @@ export class CronJobsService {
const batchResults = await this.emailService.sendRemindersToUsers(emailsBatch);
allMailingResults.push(...batchResults);
} catch (error) {
console.error(`Error processing batch ${Math.floor(i / batchSize) + 1}: ${error.message}`);
console.error(`Error processing batch ${Math.floor(i / batchSize) + 1}: ${getErrorMessage(error)}`);
Sentry.captureException(error);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
Expand All @@ -94,15 +95,17 @@ export class CronJobsService {
await slackPostMessage(`morning cron finished at ${this.getCurrentTime()}`, Constants.EXCEPTIONS_CHANNELS);
} catch (innerError) {
console.error('Detailed error in email processing:', innerError);
const errorMessage = innerError.stack
? `${innerError.message}\n${innerError.stack.split('\n').slice(0, 5).join('\n')}`
: innerError.message;
const err = innerError instanceof Error ? innerError : new Error(String(innerError));
const errorMessage = err.stack
? `${err.message}\n${err.stack.split('\n').slice(0, 5).join('\n')}`
: err.message;
await slackPostMessage(`Error in email processing: ${errorMessage}`, Constants.EXCEPTIONS_CHANNELS);
Sentry.captureException(innerError);
}
} catch (e) {
console.error('Main cron handler error:', e);
const errorMessage = e.stack ? `${e.message}\n${e.stack.split('\n').slice(0, 5).join('\n')}` : e.message;
const err = e instanceof Error ? e : new Error(String(e));
const errorMessage = err.stack ? `${err.message}\n${err.stack.split('\n').slice(0, 5).join('\n')}` : err.message;
await slackPostMessage(`Error in morning cron: ${errorMessage}`, Constants.EXCEPTIONS_CHANNELS);
Sentry.captureException(e);
} finally {
Expand Down
6 changes: 3 additions & 3 deletions backend/src/entities/demo-data/demo-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class DemoDataService {
return await this.createDemoData(userId);
} catch (error) {
console.error(`Error during demo data creation for user with ID ${userId}:`, error);
await slackPostMessage(`Error during demo data creation for user with ID ${userId}: ${error.message}`);
await slackPostMessage(`Error during demo data creation for user with ID ${userId}: ${(error as Error).message}`);
}
Comment on lines 44 to 47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not swallow demo-data creation failures.

createDemoDataForUser currently resolves with undefined on error, which can silently mask provisioning failures for callers expecting Array<ConnectionEntity>. Re-throw after reporting.

💡 Suggested fix
 	} catch (error) {
 		console.error(`Error during demo data creation for user with ID ${userId}:`, error);
-		await slackPostMessage(`Error during demo data creation for user with ID ${userId}: ${(error as Error).message}`);
+		const message = error instanceof Error ? error.message : String(error);
+		await slackPostMessage(`Error during demo data creation for user with ID ${userId}: ${message}`);
+		throw error;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/entities/demo-data/demo-data.service.ts` around lines 44 - 47,
The catch block in createDemoDataForUser currently logs and posts to Slack but
swallows the error, causing the function to resolve with undefined instead of
propagating failures; update the catch in createDemoDataForUser to re-throw the
caught error (after console.error and await slackPostMessage) so callers receive
the exception and the function's contract (Array<ConnectionEntity>) isn’t
silently broken.

}

Expand Down Expand Up @@ -519,7 +519,7 @@ export class DemoDataService {
savedEmptyActionRules.push(savedActionRule);
}
const foundSavedUserBlacklistActionRule = savedEmptyActionRules.find(
(rule) => rule.table_name === 'user' && rule.rule_title === 'Blacklist',
(rule) => rule.table_name === 'user' && rule.title === 'Blacklist',
);
if (foundSavedUserBlacklistActionRule) {
const createActionEventData: CreateTableActionEventDS = {
Expand All @@ -533,7 +533,7 @@ export class DemoDataService {
await this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(newActionEvent);
}
const foundSavedUserNotificationActionRule = savedEmptyActionRules.find(
(rule) => rule.table_name === 'user' && rule.rule_title === 'Notification on new user',
(rule) => rule.table_name === 'user' && rule.title === 'Notification on new user',
);
if (foundSavedUserNotificationActionRule) {
const createActionEventData: Array<CreateTableActionEventDS> = [
Expand Down
2 changes: 1 addition & 1 deletion backend/src/entities/email/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class EmailService {
});
mailingResults.push(result);
} catch (error) {
this.logger.error(`Failed to send reminder to ${email}: ${error.message}`);
this.logger.error(`Failed to send reminder to ${email}: ${(error as Error).message}`);
Sentry.captureException(error);
mailingResults.push(null);
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/entities/shared-jobs/shared-jobs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class SharedJobsService {
foreignKeys,
};
} catch (error) {
console.error(`Error getting table information for "${table.tableName}": ${error.message}`);
console.error(`Error getting table information for "${table.tableName}": ${(error as Error).message}`);
return null;
}
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const actionRulesCustomRepositoryExtension: IActionRulesRepository = {
.where('connection.id = :connectionId', { connectionId })
.andWhere('action_rules.table_name = :tableName', { tableName })
.getMany();
const connections = results.flatMap((r) => (r.connection ? [r.connection] : []));
const connections = results.flatMap((r: ActionRulesEntity) => (r.connection ? [r.connection] : []));
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand Down Expand Up @@ -50,7 +50,7 @@ export const actionRulesCustomRepositoryExtension: IActionRulesRepository = {
.andWhere('action_rules.table_name = :tableName', { tableName })
.andWhere('action_events.event = :event', { event: TableActionEventEnum.CUSTOM })
.getMany();
const connections = results.flatMap((r) => (r.connection ? [r.connection] : []));
const connections = results.flatMap((r: ActionRulesEntity) => (r.connection ? [r.connection] : []));
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ export class ActivateActionsInEventUseCase
} catch (e) {
operationResult = OperationResultStatusEnum.unsuccessfully;
activationResults.push({ actionId: action.id, result: operationResult });
const err = e as Error & { response?: { status?: number } };
throw new HttpException(
{
message: e.message,
message: err.message,
},
e.response?.status || HttpStatus.BAD_REQUEST,
err.response?.status || HttpStatus.BAD_REQUEST,
);
} finally {
const eventTitle = action.action_rule.action_events?.find((e) => e.id === event_id)?.title ?? null;
Expand All @@ -90,7 +91,7 @@ export class ActivateActionsInEventUseCase
operationType: LogOperationTypeEnum.actionActivated,
operationStatusResult: operationResult,
row: primaryKey,
old_data: null,
old_data: null as Record<string, unknown> | null,
table_primary_key: primaryKey,
operation_custom_action_name: eventTitle,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const tableActionsCustomRepositoryExtension: ITableActionRepository = {
.where('connection.id = :connectionId', { connectionId })
.andWhere('action_rule.table_name = :tableName', { tableName });
const results = await qb.getMany();
const connections = results.flatMap((r) => (r.action_rule?.connection ? [r.action_rule.connection] : []));
const connections = results.flatMap((r: TableActionEntity) =>
r.action_rule?.connection ? [r.action_rule.connection] : [],
);
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand All @@ -32,7 +34,9 @@ export const tableActionsCustomRepositoryExtension: ITableActionRepository = {
.andWhere('action_rule.table_name = :tableName', { tableName })
.andWhere('action_events.event = :eventType', { eventType: TableActionEventEnum.ADD_ROW });
const results = await qb.getMany();
const connections = results.flatMap((r) => (r.action_rule?.connection ? [r.action_rule.connection] : []));
const connections = results.flatMap((r: TableActionEntity) =>
r.action_rule?.connection ? [r.action_rule.connection] : [],
);
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand All @@ -50,7 +54,9 @@ export const tableActionsCustomRepositoryExtension: ITableActionRepository = {
.andWhere('action_rule.table_name = :tableName', { tableName })
.andWhere('action_events.event = :eventType', { eventType: TableActionEventEnum.UPDATE_ROW });
const results = await qb.getMany();
const connections = results.flatMap((r) => (r.action_rule?.connection ? [r.action_rule.connection] : []));
const connections = results.flatMap((r: TableActionEntity) =>
r.action_rule?.connection ? [r.action_rule.connection] : [],
);
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand All @@ -68,7 +74,9 @@ export const tableActionsCustomRepositoryExtension: ITableActionRepository = {
.andWhere('action_rule.table_name = :tableName', { tableName })
.andWhere('action_events.event = :eventType', { eventType: TableActionEventEnum.DELETE_ROW });
const results = await qb.getMany();
const connections = results.flatMap((r) => (r.action_rule?.connection ? [r.action_rule.connection] : []));
const connections = results.flatMap((r: TableActionEntity) =>
r.action_rule?.connection ? [r.action_rule.connection] : [],
);
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand All @@ -85,7 +93,9 @@ export const tableActionsCustomRepositoryExtension: ITableActionRepository = {
.andWhere('action_events.id = :eventId', { eventId })
.andWhere('action_events.event = :eventType', { eventType: TableActionEventEnum.CUSTOM });
const results = await qb.getMany();
const connections = results.flatMap((r) => (r.action_rule?.connection ? [r.action_rule.connection] : []));
const connections = results.flatMap((r: TableActionEntity) =>
r.action_rule?.connection ? [r.action_rule.connection] : [],
);
await decryptConnectionsCredentialsAsync(connections);
return results;
},
Expand Down
Loading
Loading