118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
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<SecretLeak[]>(
|
|
`/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<VulnerabilityFinding[]>(
|
|
`/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<VulnerabilityFinding[]>(
|
|
`/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'] })
|
|
},
|
|
})
|
|
}
|