import * as plugins from '../../plugins.js'; import { DcRouterDb } from '../classes.dcrouter-db.js'; import type { IIpIntelligenceRecord, TSecurityBlockRuleMatchMode, } from '../../../ts_interfaces/data/security-policy.js'; const getDb = () => DcRouterDb.getInstance().getDb(); @plugins.smartdata.Collection(() => getDb()) export class IpIntelligenceDoc extends plugins.smartdata.SmartDataDbDoc implements IIpIntelligenceRecord { @plugins.smartdata.unI() @plugins.smartdata.svDb() public ipAddress!: string; @plugins.smartdata.svDb() public asn: number | null = null; @plugins.smartdata.svDb() public asnOrg: string | null = null; @plugins.smartdata.svDb() public registrantOrg: string | null = null; @plugins.smartdata.svDb() public registrantCountry: string | null = null; @plugins.smartdata.svDb() public networkRange: string | null = null; @plugins.smartdata.svDb() public networkCidrs: string[] | null = null; @plugins.smartdata.svDb() public abuseContact: string | null = null; @plugins.smartdata.svDb() public country: string | null = null; @plugins.smartdata.svDb() public countryCode: string | null = null; @plugins.smartdata.svDb() public city: string | null = null; @plugins.smartdata.svDb() public latitude: number | null = null; @plugins.smartdata.svDb() public longitude: number | null = null; @plugins.smartdata.svDb() public accuracyRadius: number | null = null; @plugins.smartdata.svDb() public timezone: string | null = null; @plugins.smartdata.svDb() public firstSeenAt: number = Date.now(); @plugins.smartdata.svDb() public lastSeenAt: number = Date.now(); @plugins.smartdata.svDb() public updatedAt: number = Date.now(); @plugins.smartdata.svDb() public seenCount: number = 0; constructor() { super(); } public static async findByIp(ipAddress: string): Promise { return await IpIntelligenceDoc.getInstance({ ipAddress }); } public static async findByAsn(asn: number): Promise { return await IpIntelligenceDoc.getInstances({ asn }); } public static async findByOrganization( organization: string, matchMode: TSecurityBlockRuleMatchMode, ): Promise { const normalizedOrganization = organization.trim(); if (!normalizedOrganization) return []; const escapedOrganization = normalizedOrganization.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = matchMode === 'exact' ? `^${escapedOrganization}$` : escapedOrganization; return await IpIntelligenceDoc.getInstances({ $or: [ { asnOrg: { $regex: regex, $options: 'i' } }, { registrantOrg: { $regex: regex, $options: 'i' } }, ], }); } public static async findAll(): Promise { return await IpIntelligenceDoc.getInstances({}); } public static async findRecent(options: { ipAddresses?: string[]; limit: number; }): Promise { if (!Number.isSafeInteger(options.limit) || options.limit <= 0) { throw new Error('IpIntelligenceDoc.findRecent requires a positive integer limit'); } const filter = options.ipAddresses ? { ipAddress: { $in: options.ipAddresses } } : {}; const collection = (IpIntelligenceDoc as typeof IpIntelligenceDoc & { collection: plugins.smartdata.SmartdataCollection; }).collection; const cursor = await collection.getCursor( filter, IpIntelligenceDoc as unknown as typeof plugins.smartdata.SmartDataDbDoc, ); cursor.mongodbCursor.sort({ lastSeenAt: -1, _id: -1 }); cursor.mongodbCursor.limit(options.limit); try { return await cursor.toArray(); } finally { await cursor.close(); } } public static async countAll(): Promise { return await DcRouterDb.getInstance() .getDb() .mongoDb .collection('IpIntelligenceDoc') .countDocuments({}); } public static async findOldestIds(options: { lastSeenBefore?: number; limit: number; }): Promise { if (!Number.isSafeInteger(options.limit) || options.limit <= 0) { throw new Error('IpIntelligenceDoc.findOldestIds requires a positive integer limit'); } const collection = DcRouterDb.getInstance() .getDb() .mongoDb .collection('IpIntelligenceDoc'); const filter = options.lastSeenBefore === undefined ? {} : { lastSeenAt: { $lt: options.lastSeenBefore } }; const cursor = collection .find(filter, { projection: { _id: 1 } }) .sort({ lastSeenAt: 1, _id: 1 }) .limit(options.limit); try { const rows = await cursor.toArray(); return rows.map((row) => row._id); } finally { await cursor.close(); } } public static async deleteByDocumentIds(documentIds: unknown[]): Promise { if (documentIds.length === 0) return 0; if (documentIds.length > 500 || documentIds.some((id) => id === undefined || id === null)) { throw new Error('IpIntelligenceDoc.deleteByDocumentIds requires 1 to 500 document ids'); } const deleteFilter: Record = { _id: { $in: documentIds } }; const result = await DcRouterDb.getInstance() .getDb() .mongoDb .collection('IpIntelligenceDoc') .deleteMany(deleteFilter); return result.deletedCount; } }