import { Collection, Filter, ObjectId } from "mongodb"; import { getDb } from "../index"; import { DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT, SCAN_RESOURCES_COLLECTION, SCAN_RESOURCE_BOARD_PROJECTION, SCAN_RESOURCE_PAGE_SUMMARY_PROJECTION, URL_TASK_STATUSES, } from "./scanResources.constants"; import type { PagedResult, ScanPagesQuery, ScanResource, ScanResourceBoardProjection, ScanResourceDoc, ScanResourcePageSummary, UrlTaskStatus, } from "./scanResources.types"; import { decodeCursor, encodeCursor, escapeRegex, statusesForColumn, } from "./scanResources.utils"; export const getScanResourcesCollection = (): Collection => getDb().collection(SCAN_RESOURCES_COLLECTION); export const getScanKpiCounts = async ( scanId: string, ): Promise>> => { const rows = await getScanResourcesCollection() .aggregate<{ _id: UrlTaskStatus; count: number }>([ { $match: { scanId } }, { $group: { _id: "$status", count: { $sum: 1 } } }, ]) .toArray(); return rows.reduce>>((acc, row) => { acc[row._id] = row.count; return acc; }, {}); }; export const getScanResourceCountsByScanIds = async ( scanIds: string[], ): Promise> => { if (scanIds.length === 0) return {}; const rows = await getScanResourcesCollection() .aggregate<{ _id: string; count: number }>([ { $match: { scanId: { $in: scanIds } } }, { $group: { _id: "$scanId", count: { $sum: 1 } } }, ]) .toArray(); return Object.fromEntries(rows.map((row) => [row._id, row.count])); }; export const listPageSummariesByScanIds = async ( scanIds: string[], ): Promise => { if (scanIds.length === 0) return []; return getScanResourcesCollection() .find( { scanId: { $in: scanIds }, status: URL_TASK_STATUSES.URL_COMPLETED, }, { projection: SCAN_RESOURCE_PAGE_SUMMARY_PROJECTION }, ) .toArray() as Promise; }; export const getScanPages = async ( query: ScanPagesQuery, ): Promise> => { const limit = Math.min(query.limit ?? DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT); const filter: Filter = { scanId: query.scanId, status: { $in: [...statusesForColumn(query.column)] }, }; if (query.cursor) { filter._id = { $gt: decodeCursor(query.cursor) }; } if (query.search) { const searchPattern = escapeRegex(query.search); filter.$or = [ { url: { $regex: searchPattern, $options: "i" } }, { title: { $regex: searchPattern, $options: "i" } }, ]; } if (query.since) { filter.updatedAt = { $gte: query.since }; } const docs = await getScanResourcesCollection() .find(filter, { projection: SCAN_RESOURCE_BOARD_PROJECTION, sort: { _id: 1 }, limit: limit + 1, }) .toArray(); const hasMore = docs.length > limit; const items = ( hasMore ? docs.slice(0, limit) : docs ) as ScanResourceBoardProjection[]; const nextCursor = hasMore ? encodeCursor(items[items.length - 1]._id) : null; return { items, nextCursor }; }; export const getScanResourceById = async ( scanId: string, pageId: string, ): Promise => getScanResourcesCollection().findOne({ scanId, _id: new ObjectId(pageId) }); export const deleteScanResourcesByScanId = async ( scanId: string, ): Promise => { const result = await getScanResourcesCollection().deleteMany({ scanId }); return result.deletedCount; };