feat: add hosted database password update webhook endpoint#1690
Conversation
Add POST /saas/connection/hosted/password endpoint that rocketadmin-saas calls when a hosted database password is reset, to update the stored connection credentials in the backend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a hosted-connection password update feature: new DTO, use-case interface and implementation, controller endpoint, module/provider registration, and a UseCaseType enum token; also adjusts related create-connection response types. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Controller as SaasController
participant UseCase as UpdateHostedConnectionPasswordUseCase
participant CompanyRepo as CompanyInfoRepository
participant ConnRepo as ConnectionRepository
Client->>Controller: POST /connection/hosted/password (UpdateHostedConnectionPasswordDto)
Controller->>UseCase: execute(updatePasswordData)
UseCase->>CompanyRepo: findCompanyInfoByCompanyIdWithoutConnections(companyId)
CompanyRepo-->>UseCase: Company | null
alt Company not found
UseCase-->>Controller: throw NotFoundException
Controller-->>Client: 404
else Company found
UseCase->>ConnRepo: find({where:{company:{id}}})
ConnRepo-->>UseCase: [connections]
alt No matching connection
UseCase-->>Controller: throw NotFoundException
Controller-->>Client: 404
else Matching connection
UseCase->>UseCase: set connection.password = password
UseCase->>ConnRepo: saveUpdatedConnection(connection)
ConnRepo-->>UseCase: success
UseCase-->>Controller: {success:true}
Controller-->>Client: 200 {success:true}
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new SaaS webhook endpoint that allows rocketadmin-saas to notify the backend when a hosted database password is reset, so the backend can update stored connection credentials.
Changes:
- Added
POST /saas/connection/hosted/passwordendpoint wired throughSaasController+SaasModule. - Introduced
UpdateHostedConnectionPasswordUseCaseand its DTO/interface wiring. - Registered a new DI token
SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts | Implements the password update use case (currently has a lookup issue with encrypted fields). |
| backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts | Adds the use-case interface for password updates. |
| backend/src/microservices/saas-microservice/saas.module.ts | Registers the new use case provider and includes the new route in SaaS auth middleware routing. |
| backend/src/microservices/saas-microservice/saas.controller.ts | Adds the new webhook endpoint handler and injects the use case. |
| backend/src/microservices/saas-microservice/data-structures/update-hosted-connection-password.dto.ts | Defines request payload for the password update webhook. |
| backend/src/common/data-injection.tokens.ts | Adds a new UseCaseType token for DI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @ApiProperty({ | ||
| description: 'Database name', | ||
| example: 'my_database', | ||
| }) | ||
| @IsNotEmpty() | ||
| @IsString() | ||
| databaseName: string; |
There was a problem hiding this comment.
Using databaseName as the identifier for the hosted connection is ambiguous (a company can have multiple connections with the same DB name) and, because the database column is encrypted at rest, it can’t be used for direct DB lookups. Consider including a hostedDatabaseId/connectionId in this DTO (consistent with DeleteConnectionForHostedDbDto) and using that to target the exact connection.
| const connection = await this._dbContext.connectionRepository.findOne({ | ||
| where: { company: { id: companyId }, database: databaseName }, | ||
| }); |
There was a problem hiding this comment.
ConnectionEntity.database is stored encrypted (see ConnectionEntity @BeforeInsert/@BeforeUpdate), so querying with where: { database: databaseName } will not match any rows (encryption uses random salt/IV). This will cause this endpoint to consistently return CONNECTION_NOT_FOUND. Use a lookup that doesn’t rely on encrypted fields (e.g., accept hostedDatabaseId/connection id and query by id + companyId, or fetch all connections for the company and match connection.database after entities are loaded/decrypted).
| const connection = await this._dbContext.connectionRepository.findOne({ | |
| where: { company: { id: companyId }, database: databaseName }, | |
| }); | |
| const companyConnections = await this._dbContext.connectionRepository.find({ | |
| where: { company: { id: companyId } }, | |
| }); | |
| const connection = companyConnections.find((conn) => conn.database === databaseName); |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`:
- Around line 31-36: The findOne call in
update-hosted-connection-password.use.case.ts uses where: { company: { id:
companyId }, database: databaseName } which will never match because the
database column is stored encrypted and only decrypted in `@AfterLoad`; instead,
fetch the connection by company (e.g., use connectionRepository.findOne({ where:
{ company: { id: companyId } } }) or implement/call the custom repo method
pattern used in delete-connection-for-hosted-db.use.case.ts), then compare the
decrypted connection.database to the plaintext databaseName in code and throw
NotFoundException if it doesn’t match; alternatively add or reuse a repository
method that performs encrypted-field-aware lookup and call that from this use
case.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cefe6840-70d1-4507-9d79-1dae84753232
📒 Files selected for processing (6)
backend/src/common/data-injection.tokens.tsbackend/src/microservices/saas-microservice/data-structures/update-hosted-connection-password.dto.tsbackend/src/microservices/saas-microservice/saas.controller.tsbackend/src/microservices/saas-microservice/saas.module.tsbackend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.tsbackend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts
.../src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts
Outdated
Show resolved
Hide resolved
…ionId from create endpoint The database column is stored encrypted and only decrypted in @afterload, so querying by plaintext value would never match. Now fetches connections by company and filters by decrypted database name in code. Also simplifies create hosted connection response to return just the connectionId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
backend/src/microservices/saas-microservice/saas.controller.ts (1)
317-328: Consider using PUT or PATCH instead of POST for update operations.The endpoint updates an existing resource's password, which semantically aligns better with
PUTorPATCHHTTP methods.POSTtypically indicates resource creation. Additionally, consider changing the@ApiResponsestatus to200for updates rather than201.♻️ Suggested change
`@ApiOperation`({ summary: 'Update password of hosted database connection' }) `@ApiBody`({ type: UpdateHostedConnectionPasswordDto }) `@ApiResponse`({ - status: 201, + status: 200, type: SuccessResponse, }) -@Post('/connection/hosted/password') +@Put('/connection/hosted/password') async updateHostedConnectionPassword( `@Body`() updatePasswordData: UpdateHostedConnectionPasswordDto, ): Promise<SuccessResponse> { return await this.updateHostedConnectionPasswordUseCase.execute(updatePasswordData); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/microservices/saas-microservice/saas.controller.ts` around lines 317 - 328, The endpoint updateHostedConnectionPassword currently uses `@Post`('/connection/hosted/password') and returns a 201 ApiResponse; change the HTTP verb to a more appropriate updater such as `@Put` or `@Patch` (choose `@Patch` if only the password is being modified) and update the `@ApiResponse` status from 201 to 200 (SuccessResponse) accordingly; keep the method name updateHostedConnectionPassword and the DTO UpdateHostedConnectionPasswordDto unchanged so only the decorators and response status are adjusted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`:
- Around line 34-37: The current lookup using companyConnections.find((conn) =>
conn.database === databaseName) can pick the wrong record when multiple
connections share the same database name; change the use case to disambiguate by
requiring and validating a hostname: update the incoming DTO to include
hostname, validate it's present, then replace the find predicate with one that
matches both conn.database === databaseName && conn.host === hostname (or
conn.hostname), and keep throwing
NotFoundException(Messages.CONNECTION_NOT_FOUND) when no exact match is found;
also update any callers/tests to supply the hostname.
---
Nitpick comments:
In `@backend/src/microservices/saas-microservice/saas.controller.ts`:
- Around line 317-328: The endpoint updateHostedConnectionPassword currently
uses `@Post`('/connection/hosted/password') and returns a 201 ApiResponse; change
the HTTP verb to a more appropriate updater such as `@Put` or `@Patch` (choose
`@Patch` if only the password is being modified) and update the `@ApiResponse`
status from 201 to 200 (SuccessResponse) accordingly; keep the method name
updateHostedConnectionPassword and the DTO UpdateHostedConnectionPasswordDto
unchanged so only the decorators and response status are adjusted.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2795e3f1-5cdc-4d67-9d04-e5a74a59b4b6
📒 Files selected for processing (5)
backend/src/microservices/saas-microservice/data-structures/common-responce.ds.tsbackend/src/microservices/saas-microservice/saas.controller.tsbackend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.tsbackend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.tsbackend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts
✅ Files skipped from review due to trivial changes (1)
- backend/src/microservices/saas-microservice/data-structures/common-responce.ds.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
| const connection = companyConnections.find((conn) => conn.database === databaseName); | ||
| if (!connection) { | ||
| throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's a unique constraint on company+database
echo "=== Checking for unique constraints on connection entity ==="
rg -n "unique|@Unique|@Index" backend/src/entities/connection/connection.entity.ts -A 2 -B 2
echo "=== Checking how hosted connections are created ==="
rg -n "databaseName|database" backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.tsRepository: rocket-admin/rocketadmin
Length of output: 456
🏁 Script executed:
cat -n backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts | head -50Repository: rocket-admin/rocketadmin
Length of output: 2199
🏁 Script executed:
cat -n backend/src/entities/connection/connection.entity.tsRepository: rocket-admin/rocketadmin
Length of output: 10808
🏁 Script executed:
# Check for database migrations or constraints
fd -t f "migration" backend/src --type f | head -10Repository: rocket-admin/rocketadmin
Length of output: 124
🏁 Script executed:
head -200 backend/src/migrations/1594021894551-InitialMigration.ts | grep -A 5 -B 5 "connection\|unique\|UNIQUE" -iRepository: rocket-admin/rocketadmin
Length of output: 4151
🏁 Script executed:
# Also check if there are any other migration files that might define this constraint
find backend/src/migrations -type f -name "*.ts" | xargs grep -l "connection\|database" | head -5Repository: rocket-admin/rocketadmin
Length of output: 373
🏁 Script executed:
# Check for companyId in the connection table and any unique constraints added later
grep -n "companyId\|UNIQUE.*database\|UNIQUE.*company" backend/src/migrations/*.tsRepository: rocket-admin/rocketadmin
Length of output: 8417
🏁 Script executed:
# Also check the create-connection-for-hosted-db to see the full picture
cat -n backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.tsRepository: rocket-admin/rocketadmin
Length of output: 5350
Potential ambiguity if multiple connections share the same database name.
The lookup uses only databaseName to identify the connection within a company. If a company has multiple connections with the same database value (e.g., pointing to different hosts), Array.find() returns only the first match, potentially updating the wrong connection.
The database schema does not enforce a unique constraint on (companyId, databaseName). Consider either:
- Adding
hostnameto the DTO for disambiguation, or - Enforcing
(companyId, databaseName)as unique at the database level.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`
around lines 34 - 37, The current lookup using companyConnections.find((conn) =>
conn.database === databaseName) can pick the wrong record when multiple
connections share the same database name; change the use case to disambiguate by
requiring and validating a hostname: update the incoming DTO to include
hostname, validate it's present, then replace the find predicate with one that
matches both conn.database === databaseName && conn.host === hostname (or
conn.hostname), and keep throwing
NotFoundException(Messages.CONNECTION_NOT_FOUND) when no exact match is found;
also update any callers/tests to supply the hostname.
Add POST /saas/connection/hosted/password endpoint that rocketadmin-saas calls when a hosted database password is reset, to update the stored connection credentials in the backend.
Summary by CodeRabbit
New Features
API Changes