import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { z } from 'zod' import { api } from '../client' import type { SecretLeak, VulnerabilityFinding } from '../../types/api' // ── Zod schemas ─────────────────────────────────────────────────────────────── const secretLeakSchema = z.object({ id: z.number(), repoId: z.number(), commitSha: z.string(), ref: z.string(), patternName: z.string(), description: z.string(), severity: z.string(), matchSample: z.string(), dismissed: z.boolean(), dismissedBy: z.string().optional(), dismissedAt: z.string().nullable().optional(), detectedAt: z.string(), }) const vulnerabilityFindingSchema = z.object({ id: z.number(), repoId: z.number(), vulnId: z.string(), purl: z.string(), version: z.string(), summary: z.string(), details: z.string().optional(), cvssScore: z.number(), fixedVersion: z.string(), dismissed: z.boolean(), dismissedBy: z.string().optional(), dismissedAt: z.string().nullable().optional(), detectedAt: z.string(), }) // ── Secret Leak queries ─────────────────────────────────────────────────────── /** Active secret leaks for a repo. */ export function useSecretLeaks(owner: string, repo: string) { return useQuery({ queryKey: ['repos', owner, repo, 'secrets', 'leaks'], queryFn: () => api.get( `/api/v1/repos/${owner}/${repo}/secrets/leaks`, z.array(secretLeakSchema), ), enabled: Boolean(owner && repo), refetchInterval: 30_000, }) } /** Dismiss a secret leak. */ export function useDismissSecretLeak(owner: string, repo: string) { const qc = useQueryClient() return useMutation({ mutationFn: (leakId: number) => api.post( `/api/v1/repos/${owner}/${repo}/secrets/leaks/${leakId}/dismiss`, z.unknown(), undefined, ), onSuccess: () => { qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'secrets', 'leaks'] }) }, }) } // ── Vulnerability queries ───────────────────────────────────────────────────── /** Active vulnerability findings for a repo. */ export function useVulnerabilities(owner: string, repo: string) { return useQuery({ queryKey: ['repos', owner, repo, 'vulnerabilities'], queryFn: () => api.get( `/api/v1/repos/${owner}/${repo}/vulnerabilities`, z.array(vulnerabilityFindingSchema), ), enabled: Boolean(owner && repo), refetchInterval: 30_000, }) } /** Trigger a vulnerability scan. */ export function useScanVulnerabilities(owner: string, repo: string) { const qc = useQueryClient() return useMutation({ mutationFn: () => api.post( `/api/v1/repos/${owner}/${repo}/vulnerabilities/scan`, z.array(vulnerabilityFindingSchema), undefined, ), onSuccess: () => { qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'vulnerabilities'] }) }, }) } /** Dismiss a vulnerability finding. */ export function useDismissVulnerability(owner: string, repo: string) { const qc = useQueryClient() return useMutation({ mutationFn: (findingId: number) => api.post( `/api/v1/repos/${owner}/${repo}/vulnerabilities/${findingId}/dismiss`, z.unknown(), undefined, ), onSuccess: () => { qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'vulnerabilities'] }) }, }) }