because I got bored of customising my CV for every job
at main 130 lines 4.4 kB view raw
1import { 2 AI_PROVIDER, 3 type AIProvider, 4 registeredProviderTypes, 5} from "@cv/ai-provider"; 6import { AdminGuard, JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; 7import { Inject, Optional, UseGuards } from "@nestjs/common"; 8import { PageInfo } from "@cv/system"; 9import { Args, Int, Query, Resolver } from "@nestjs/graphql"; 10import { 11 AiCallLogEntryType, 12 AiProviderStatus, 13 QueueMessageConnection, 14 QueueStats, 15 SystemStatus, 16 WorkerHealth, 17} from "./admin.type"; 18import { AiCallLogService } from "./ai-call-log.service"; 19import { QueueMonitorService } from "./queue-monitor.service"; 20 21@Resolver() 22@UseGuards(JwtAuthGuard, VerifiedScopeGuard, AdminGuard) 23export class AdminResolver { 24 constructor( 25 @Inject(AI_PROVIDER) 26 @Optional() 27 private readonly globalProvider: AIProvider | undefined, 28 private readonly aiCallLogService: AiCallLogService, 29 private readonly queueMonitorService: QueueMonitorService, 30 ) {} 31 32 @Query(() => SystemStatus) 33 async systemStatus(): Promise<SystemStatus> { 34 const status = new SystemStatus(); 35 status.serverUptime = process.uptime(); 36 status.registeredProviderTypes = registeredProviderTypes(); 37 38 if (!this.globalProvider?.getStatus) { 39 status.platformProvider = this.globalProvider 40 ? { 41 healthy: await this.globalProvider.isHealthy(), 42 providerName: this.globalProvider.name, 43 detailsJson: JSON.stringify(null), 44 } 45 : null; 46 47 return status; 48 } 49 50 const providerStatus = await this.globalProvider.getStatus(); 51 const platformProvider = new AiProviderStatus(); 52 platformProvider.healthy = providerStatus.healthy; 53 platformProvider.providerName = providerStatus.providerName; 54 platformProvider.detailsJson = JSON.stringify( 55 providerStatus.details ?? null, 56 ); 57 status.platformProvider = platformProvider; 58 59 return status; 60 } 61 62 @Query(() => [AiCallLogEntryType]) 63 aiCallLog( 64 @Args("limit", { type: () => Int, nullable: true }) limit?: number, 65 @Args("status", { type: () => String, nullable: true }) status?: string, 66 @Args("providerName", { type: () => String, nullable: true }) 67 providerName?: string, 68 ): AiCallLogEntryType[] { 69 const entries = this.aiCallLogService.getEntries(limit ?? undefined); 70 71 return entries.filter( 72 (e) => 73 (!status || e.status === status) && 74 (!providerName || e.providerName === providerName), 75 ); 76 } 77 78 @Query(() => [AiCallLogEntryType]) 79 async aiCallLogHistory( 80 @Args("limit", { type: () => Int, nullable: true }) limit?: number, 81 @Args("status", { type: () => String, nullable: true }) status?: string, 82 @Args("providerName", { type: () => String, nullable: true }) 83 providerName?: string, 84 ): Promise<AiCallLogEntryType[]> { 85 const rows = await this.aiCallLogService.queryHistory({ 86 ...(limit != null ? { limit } : {}), 87 ...(status != null ? { status } : {}), 88 ...(providerName != null ? { providerName } : {}), 89 }); 90 91 return rows.map((r) => ({ 92 id: r.id, 93 timestamp: r.createdAt.toISOString(), 94 providerName: r.providerName, 95 durationMs: r.durationMs, 96 promptTokens: r.promptTokens ?? undefined, 97 completionTokens: r.completionTokens ?? undefined, 98 model: r.model ?? undefined, 99 finishReason: r.finishReason ?? undefined, 100 status: r.status, 101 error: r.error ?? undefined, 102 userId: r.userId ?? undefined, 103 source: r.source ?? undefined, 104 })); 105 } 106 107 @Query(() => QueueStats) 108 async queueStats(): Promise<QueueStats> { 109 const result = await this.queueMonitorService.getStats(); 110 return QueueStats.fromDomain(result); 111 } 112 113 @Query(() => QueueMessageConnection) 114 async queueMessages( 115 @Args("limit", { type: () => Int, nullable: true }) limit?: number, 116 ): Promise<InstanceType<typeof QueueMessageConnection>> { 117 const result = await this.queueMonitorService.getMessages(limit ?? undefined); 118 return QueueMessageConnection.fromPaginationResult({ 119 edges: result.map((r, i) => ({ node: r, cursor: String(i) })), 120 pageInfo: new PageInfo(false, false, null, null), 121 totalCount: result.length, 122 }); 123 } 124 125 @Query(() => [WorkerHealth]) 126 async workerHealth(): Promise<WorkerHealth[]> { 127 const results = await this.queueMonitorService.getWorkers(); 128 return results.map(WorkerHealth.fromDomain); 129 } 130}