@@ -624,6 +624,7 @@ export interface AdvisoryRow {
624624 osvId : string
625625 severity : string
626626 resolution : 'open' | 'patched' | null
627+ isCritical : boolean
627628}
628629
629630export async function getPackageDetailByPurl (
@@ -783,35 +784,86 @@ export async function listPackagesForScatter(
783784export async function getAdvisoriesByPackageId (
784785 qx : QueryExecutor ,
785786 packageId : string ,
786- ) : Promise < AdvisoryRow [ ] > {
787- return qx . select (
788- `
789- SELECT
790- a.osv_id AS "osvId",
791- LOWER(a.severity) AS severity,
792- CASE
793- WHEN p.latest_version IS NULL THEN NULL
794- WHEN COUNT(ar.id) = 0 THEN NULL
795- -- TODO: text comparison is lexicographic, not semver — '1.9.0' >= '1.10.0' is TRUE here.
796- -- Replace with a proper semver comparison function when one is available in the DB.
797- WHEN BOOL_AND(
798- CASE
799- WHEN ar.fixed_version IS NULL AND ar.last_affected IS NULL THEN FALSE
800- WHEN ar.fixed_version IS NOT NULL AND p.latest_version >= ar.fixed_version THEN TRUE
801- WHEN ar.fixed_version IS NOT NULL THEN FALSE
802- WHEN ar.last_affected IS NOT NULL AND p.latest_version > ar.last_affected THEN TRUE
803- ELSE FALSE
804- END
805- ) THEN 'patched'
806- ELSE 'open'
807- END AS resolution
808- FROM advisory_packages ap
809- JOIN advisories a ON a.id = ap.advisory_id
810- LEFT JOIN advisory_affected_ranges ar ON ar.advisory_package_id = ap.id
811- JOIN packages p ON p.id = ap.package_id
812- WHERE ap.package_id = $(packageId)::bigint
813- GROUP BY a.osv_id, a.severity, p.latest_version
814- ` ,
815- { packageId } ,
816- )
787+ opts ?: {
788+ page : number
789+ pageSize : number
790+ severities ?: string [ ]
791+ resolutions ?: ( 'open' | 'patched' ) [ ]
792+ critical ?: boolean
793+ } ,
794+ ) : Promise < { rows : AdvisoryRow [ ] ; total : number } > {
795+ const cte = `
796+ WITH advisory_data AS (
797+ SELECT
798+ a.osv_id AS "osvId",
799+ LOWER(a.severity) AS severity,
800+ a.is_critical AS "isCritical",
801+ CASE
802+ WHEN p.latest_version IS NULL THEN NULL
803+ WHEN COUNT(ar.id) = 0 THEN NULL
804+ -- TODO: text comparison is lexicographic, not semver — '1.9.0' >= '1.10.0' is TRUE here.
805+ -- Replace with a proper semver comparison function when one is available in the DB.
806+ WHEN BOOL_AND(
807+ CASE
808+ WHEN ar.fixed_version IS NULL AND ar.last_affected IS NULL THEN FALSE
809+ WHEN ar.fixed_version IS NOT NULL AND p.latest_version >= ar.fixed_version THEN TRUE
810+ WHEN ar.fixed_version IS NOT NULL THEN FALSE
811+ WHEN ar.last_affected IS NOT NULL AND p.latest_version > ar.last_affected THEN TRUE
812+ ELSE FALSE
813+ END
814+ ) THEN 'patched'
815+ ELSE 'open'
816+ END AS resolution
817+ FROM advisory_packages ap
818+ JOIN advisories a ON a.id = ap.advisory_id
819+ LEFT JOIN advisory_affected_ranges ar ON ar.advisory_package_id = ap.id
820+ JOIN packages p ON p.id = ap.package_id
821+ WHERE ap.package_id = $(packageId)::bigint
822+ GROUP BY a.osv_id, a.severity, a.is_critical, p.latest_version
823+ )
824+ `
825+
826+ const conditions : string [ ] = [ ]
827+ if ( opts ?. severities ?. length ) {
828+ conditions . push ( 'severity = ANY($(severities)::text[])' )
829+ }
830+ if ( opts ?. resolutions ?. length ) {
831+ conditions . push ( 'resolution = ANY($(resolutions)::text[])' )
832+ }
833+ if ( opts ?. critical !== undefined ) {
834+ conditions . push ( '"isCritical" = $(critical)' )
835+ }
836+
837+ const whereClause = conditions . length ? `WHERE ${ conditions . join ( ' AND ' ) } ` : ''
838+ const paginationClause = opts ? `LIMIT $(limit) OFFSET $(offset)` : ''
839+ const params = {
840+ packageId,
841+ severities : opts ?. severities ?? null ,
842+ resolutions : opts ?. resolutions ?? null ,
843+ critical : opts ?. critical ?? null ,
844+ limit : opts ?. pageSize ,
845+ offset : opts ? ( opts . page - 1 ) * opts . pageSize : 0 ,
846+ }
847+
848+ const rows = ( await qx . select (
849+ `${ cte } SELECT * FROM advisory_data
850+ ${ whereClause }
851+ ORDER BY
852+ CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'moderate' THEN 3 WHEN 'low' THEN 4 ELSE 5 END,
853+ CASE resolution WHEN 'open' THEN 1 WHEN 'patched' THEN 2 ELSE 3 END,
854+ "osvId"
855+ ${ paginationClause } ` ,
856+ params ,
857+ ) ) as AdvisoryRow [ ]
858+
859+ if ( ! opts ) {
860+ return { rows, total : rows . length }
861+ }
862+
863+ const countResult = ( await qx . selectOne (
864+ `${ cte } SELECT COUNT(*) AS total FROM advisory_data ${ whereClause } ` ,
865+ params ,
866+ ) ) as { total : string }
867+
868+ return { rows, total : Number ( countResult . total ) }
817869}
0 commit comments