Skip to content

Commit f589680

Browse files
committed
feat: add filters
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 3c2fe11 commit f589680

4 files changed

Lines changed: 83 additions & 19 deletions

File tree

backend/src/api/public/v1/akrites/openapi.yaml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,18 +399,22 @@ components:
399399

400400
Advisory:
401401
type: object
402-
required: [osvId, severity, resolution]
402+
required: [osvId, severity, resolution, isCritical]
403403
properties:
404404
osvId:
405405
type: string
406406
example: GHSA-xxxx-xxxx-xxxx
407407
severity:
408408
type: string
409-
enum: [critical, high, medium, low]
409+
enum: [critical, high, moderate, low]
410410
nullable: true
411411
resolution:
412412
type: string
413+
enum: [open, patched]
413414
nullable: true
415+
isCritical:
416+
type: boolean
417+
description: True when CVSS score >= 7.0.
414418

415419
PackageHistoryEvent:
416420
type: object
@@ -1000,6 +1004,30 @@ paths:
10001004
minimum: 1
10011005
maximum: 100
10021006
default: 20
1007+
- name: severity
1008+
in: query
1009+
required: false
1010+
description: Filter by severity. Accepts comma-separated values or multiple params.
1011+
schema:
1012+
type: array
1013+
items:
1014+
type: string
1015+
enum: [critical, high, moderate, low]
1016+
- name: resolution
1017+
in: query
1018+
required: false
1019+
description: Filter by resolution status. Accepts comma-separated values or multiple params.
1020+
schema:
1021+
type: array
1022+
items:
1023+
type: string
1024+
enum: [open, patched]
1025+
- name: critical
1026+
in: query
1027+
required: false
1028+
description: Filter by criticality flag (CVSS >= 7.0).
1029+
schema:
1030+
type: boolean
10031031
responses:
10041032
'200':
10051033
description: Paginated advisory list.

backend/src/api/public/v1/packages/getPackage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export async function getPackage(req: Request, res: Response): Promise<void> {
7474
osvId: a.osvId,
7575
severity: a.severity,
7676
resolution: a.resolution,
77+
isCritical: a.isCritical,
7778
})),
7879
cvd: {
7980
isPvrEnabled: null,

backend/src/api/public/v1/packages/getPackageAdvisories.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,39 @@ const DEFAULT_PAGE_SIZE = 20
1414
const MAX_PAGE_SIZE = 100
1515

1616
const SEVERITY_VALUES = ['critical', 'high', 'moderate', 'low'] as const
17+
const RESOLUTION_VALUES = ['open', 'patched'] as const
18+
19+
function toStringArray(v: unknown): unknown {
20+
if (!v) return undefined
21+
const vals = Array.isArray(v) ? v : [v]
22+
return vals
23+
.flatMap((s: unknown) => String(s).split(','))
24+
.map((s) => s.trim())
25+
.filter(Boolean)
26+
}
1727

1828
const querySchema = purlQuerySchema.extend({
1929
page: z.coerce.number().int().min(1).default(1),
2030
pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE),
21-
severity: z
22-
.preprocess(
23-
(v) => {
24-
if (!v) return undefined
25-
const vals = Array.isArray(v) ? v : [v]
26-
return vals.flatMap((s: unknown) => String(s).split(','))
27-
},
28-
z.array(z.enum(SEVERITY_VALUES)).optional(),
29-
)
31+
severity: z.preprocess(toStringArray, z.array(z.enum(SEVERITY_VALUES)).optional()).optional(),
32+
resolution: z.preprocess(toStringArray, z.array(z.enum(RESOLUTION_VALUES)).optional()).optional(),
33+
critical: z
34+
.preprocess((v) => {
35+
if (v === 'true') return true
36+
if (v === 'false') return false
37+
return v
38+
}, z.boolean().optional())
3039
.optional(),
3140
})
3241

3342
export async function getPackageAdvisories(req: Request, res: Response): Promise<void> {
3443
const rawPurl = typeof req.query.purl === 'string' ? req.query.purl : ''
3544
const version = extractPurlVersion(rawPurl)
3645

37-
const { purl, page, pageSize, severity } = validateOrThrow(querySchema, req.query)
46+
const { purl, page, pageSize, severity, resolution, critical } = validateOrThrow(
47+
querySchema,
48+
req.query,
49+
)
3850

3951
const qx = await getPackagesQx()
4052
const pkg = await getPackageDetailByPurl(qx, purl)
@@ -48,6 +60,8 @@ export async function getPackageAdvisories(req: Request, res: Response): Promise
4860
pageSize,
4961
version: version ?? undefined,
5062
severities: severity,
63+
resolutions: resolution,
64+
critical,
5165
})
5266

5367
ok(res, {
@@ -58,6 +72,7 @@ export async function getPackageAdvisories(req: Request, res: Response): Promise
5872
osvId: a.osvId,
5973
severity: a.severity,
6074
resolution: a.resolution,
75+
isCritical: a.isCritical,
6176
})),
6277
})
6378
}

services/libs/data-access-layer/src/osspckgs/api.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ export interface AdvisoryRow {
616616
osvId: string
617617
severity: string
618618
resolution: 'open' | 'patched' | null
619+
isCritical: boolean
619620
}
620621

621622
export async function getPackageDetailByPurl(
@@ -775,13 +776,21 @@ export async function listPackagesForScatter(
775776
export async function getAdvisoriesByPackageId(
776777
qx: QueryExecutor,
777778
packageId: string,
778-
opts?: { page: number; pageSize: number; version?: string; severities?: string[] },
779+
opts?: {
780+
page: number
781+
pageSize: number
782+
version?: string
783+
severities?: string[]
784+
resolutions?: ('open' | 'patched')[]
785+
critical?: boolean
786+
},
779787
): Promise<{ rows: AdvisoryRow[]; total: number }> {
780788
const cte = `
781789
WITH advisory_data AS (
782790
SELECT
783791
a.osv_id AS "osvId",
784792
LOWER(a.severity) AS severity,
793+
a.is_critical AS "isCritical",
785794
CASE
786795
WHEN COALESCE($(version), p.latest_version) IS NULL THEN NULL
787796
WHEN COUNT(ar.id) = 0 THEN NULL
@@ -803,25 +812,36 @@ export async function getAdvisoriesByPackageId(
803812
LEFT JOIN advisory_affected_ranges ar ON ar.advisory_package_id = ap.id
804813
JOIN packages p ON p.id = ap.package_id
805814
WHERE ap.package_id = $(packageId)::bigint
806-
GROUP BY a.osv_id, a.severity, p.latest_version
815+
GROUP BY a.osv_id, a.severity, a.is_critical, p.latest_version
807816
)
808817
`
809818

810-
const severityClause = opts?.severities?.length
811-
? `WHERE severity = ANY($(severities)::text[])`
812-
: ''
819+
const conditions: string[] = []
820+
if (opts?.severities?.length) {
821+
conditions.push('severity = ANY($(severities)::text[])')
822+
}
823+
if (opts?.resolutions?.length) {
824+
conditions.push('resolution = ANY($(resolutions)::text[])')
825+
}
826+
if (opts?.critical !== undefined) {
827+
conditions.push('"isCritical" = $(critical)')
828+
}
829+
830+
const whereClause = conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''
813831
const paginationClause = opts ? `LIMIT $(limit) OFFSET $(offset)` : ''
814832
const params = {
815833
packageId,
816834
version: opts?.version ?? null,
817835
severities: opts?.severities ?? null,
836+
resolutions: opts?.resolutions ?? null,
837+
critical: opts?.critical ?? null,
818838
limit: opts?.pageSize,
819839
offset: opts ? (opts.page - 1) * opts.pageSize : 0,
820840
}
821841

822842
const rows = (await qx.select(
823843
`${cte} SELECT * FROM advisory_data
824-
${severityClause}
844+
${whereClause}
825845
ORDER BY
826846
CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'moderate' THEN 3 WHEN 'low' THEN 4 ELSE 5 END,
827847
CASE resolution WHEN 'open' THEN 1 WHEN 'patched' THEN 2 ELSE 3 END,
@@ -835,7 +855,7 @@ export async function getAdvisoriesByPackageId(
835855
}
836856

837857
const countResult = (await qx.selectOne(
838-
`${cte} SELECT COUNT(*) AS total FROM advisory_data ${severityClause}`,
858+
`${cte} SELECT COUNT(*) AS total FROM advisory_data ${whereClause}`,
839859
params,
840860
)) as { total: string }
841861

0 commit comments

Comments
 (0)