fix(api-key): handle missing role gracefully in role resolve field

https://sonarly.com/issue/27027?type=bug

The `GetApiKey` GraphQL query throws `API_KEY_NO_ROLE_ASSIGNED` when viewing an API key that has no corresponding `role_target` record in the database, causing the API key details page to fail.

Fix: Made the `role` ResolveField on `ApiKeyResolver` nullable again (`{ nullable: true }`) and added a targeted catch for the `API_KEY_NO_ROLE_ASSIGNED` exception, returning `null` instead of propagating the error.

**Why this approach:**
- When the `role` field was first introduced (commit `4cd2a87833`), it was nullable — this was the original design.
- Commit `4ba1bd24f0` made it non-nullable, assuming the backfill command would assign roles to all existing API keys. That assumption doesn't hold for edge cases (missed backfill, deleted role_target records).
- The catch is narrowly scoped: only `ApiKeyException` with code `API_KEY_NO_ROLE_ASSIGNED` returns null. All other exceptions (including other `ApiKeyException` codes) are re-thrown normally.
- This matches the error handling pattern used by other methods in the same resolver (`apiKey()` at line 51-62, `assignRoleToApiKey()` at line 110-121) which wrap calls in try-catch.

**What this fixes:**
- API keys with missing `role_target` records no longer crash the `GetApiKey` query
- The API key details page (`/settings/api-webhooks/apis/<id>`) loads successfully, showing `null` for the role field
- The frontend can then display a "No role assigned" state or prompt the user to assign one
This commit is contained in:
Sonarly Claude Code
2026-04-16 10:04:32 +00:00
parent 381f3ba7d9
commit 2767ddac44
@@ -121,14 +121,25 @@ export class ApiKeyResolver {
}
}
@ResolveField(() => RoleDTO)
@ResolveField(() => RoleDTO, { nullable: true })
async role(
@Parent() apiKey: ApiKeyEntity,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<RoleDTO> {
return this.apiKeyRoleService.getRoleDtoByApiKeyId({
apiKeyId: apiKey.id,
workspaceId: workspace.id,
});
): Promise<RoleDTO | null> {
try {
return await this.apiKeyRoleService.getRoleDtoByApiKeyId({
apiKeyId: apiKey.id,
workspaceId: workspace.id,
});
} catch (error) {
if (
error instanceof ApiKeyException &&
error.code === ApiKeyExceptionCode.API_KEY_NO_ROLE_ASSIGNED
) {
return null;
}
throw error;
}
}
}