feat: caller cache invalidation endpoint + worklist auth fix

- POST /api/caller/invalidate — clears Redis cache for a phone number
- WorklistController: resolves agent name from login cache (avoids currentUser query)
- AuthController: caches agent name in Redis during login (keyed by token suffix)
- WorklistModule: imports AuthModule (forwardRef for circular dep)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 12:14:56 +05:30
parent d0df6618b5
commit 1d1f27607f
4 changed files with 27 additions and 2 deletions

View File

@@ -166,6 +166,12 @@ export class AuthController {
} }
} }
// Cache agent name for worklist resolution (avoids re-querying currentUser with user JWT)
const agentFullName = `${workspaceMember?.name?.firstName ?? ''} ${workspaceMember?.name?.lastName ?? ''}`.trim();
if (agentFullName) {
await this.sessionService.setCache(`agent:name:${accessToken.slice(-16)}`, agentFullName, 86400);
}
return { return {
accessToken, accessToken,
refreshToken: tokens.refreshToken.token, refreshToken: tokens.refreshToken.token,

View File

@@ -23,4 +23,14 @@ export class CallerResolutionController {
const result = await this.resolution.resolve(phone, auth); const result = await this.resolution.resolve(phone, auth);
return result; return result;
} }
@Post('invalidate')
async invalidate(@Body('phone') phone: string) {
if (!phone) {
throw new HttpException('phone is required', HttpStatus.BAD_REQUEST);
}
this.logger.log(`[RESOLVE] Invalidating cache for: ${phone}`);
await this.resolution.invalidate(phone);
return { status: 'ok' };
}
} }

View File

@@ -2,6 +2,7 @@ import { Controller, Get, Patch, Headers, Param, Body, HttpException, Logger } f
import { PlatformGraphqlService } from '../platform/platform-graphql.service'; import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { WorklistService } from './worklist.service'; import { WorklistService } from './worklist.service';
import { MissedQueueService } from './missed-queue.service'; import { MissedQueueService } from './missed-queue.service';
import { SessionService } from '../auth/session.service';
@Controller('api/worklist') @Controller('api/worklist')
export class WorklistController { export class WorklistController {
@@ -11,6 +12,7 @@ export class WorklistController {
private readonly worklist: WorklistService, private readonly worklist: WorklistService,
private readonly missedQueue: MissedQueueService, private readonly missedQueue: MissedQueueService,
private readonly platform: PlatformGraphqlService, private readonly platform: PlatformGraphqlService,
private readonly session: SessionService,
) {} ) {}
@Get() @Get()
@@ -44,6 +46,12 @@ export class WorklistController {
} }
private async resolveAgentName(authHeader: string): Promise<string> { private async resolveAgentName(authHeader: string): Promise<string> {
// Check cached name from login (avoids currentUser query that CC agents can't access)
const token = authHeader.replace(/^Bearer\s+/i, '');
const cached = await this.session.getCache(`agent:name:${token.slice(-16)}`);
if (cached) return cached;
// Fallback: try querying platform (works for admin/supervisor tokens)
try { try {
const data = await this.platform.queryWithAuth<any>( const data = await this.platform.queryWithAuth<any>(
`{ currentUser { workspaceMember { name { firstName lastName } } } }`, `{ currentUser { workspaceMember { name { firstName lastName } } } }`,
@@ -54,7 +62,7 @@ export class WorklistController {
const full = `${name?.firstName ?? ''} ${name?.lastName ?? ''}`.trim(); const full = `${name?.firstName ?? ''} ${name?.lastName ?? ''}`.trim();
if (full) return full; if (full) return full;
} catch (err) { } catch (err) {
this.logger.warn(`Failed to resolve agent name: ${err}`); this.logger.warn(`Failed to resolve agent name via platform: ${err}`);
} }
throw new HttpException('Could not determine agent identity', 400); throw new HttpException('Could not determine agent identity', 400);
} }

View File

@@ -1,6 +1,7 @@
import { Module, forwardRef } from '@nestjs/common'; import { Module, forwardRef } from '@nestjs/common';
import { PlatformModule } from '../platform/platform.module'; import { PlatformModule } from '../platform/platform.module';
import { OzonetelAgentModule } from '../ozonetel/ozonetel-agent.module'; import { OzonetelAgentModule } from '../ozonetel/ozonetel-agent.module';
import { AuthModule } from '../auth/auth.module';
import { RulesEngineModule } from '../rules-engine/rules-engine.module'; import { RulesEngineModule } from '../rules-engine/rules-engine.module';
import { WorklistController } from './worklist.controller'; import { WorklistController } from './worklist.controller';
import { WorklistService } from './worklist.service'; import { WorklistService } from './worklist.service';
@@ -9,7 +10,7 @@ import { MissedCallWebhookController } from './missed-call-webhook.controller';
import { KookooCallbackController } from './kookoo-callback.controller'; import { KookooCallbackController } from './kookoo-callback.controller';
@Module({ @Module({
imports: [PlatformModule, forwardRef(() => OzonetelAgentModule), RulesEngineModule], imports: [PlatformModule, forwardRef(() => OzonetelAgentModule), forwardRef(() => AuthModule), RulesEngineModule],
controllers: [WorklistController, MissedCallWebhookController, KookooCallbackController], controllers: [WorklistController, MissedCallWebhookController, KookooCallbackController],
providers: [WorklistService, MissedQueueService], providers: [WorklistService, MissedQueueService],
exports: [MissedQueueService], exports: [MissedQueueService],