FRE-4533: Merge apps/{api,web,mobile} and shared-db into ShieldAI repo
Merge FrenoCorp apps into ShieldAI packages/: - packages/api: merged routes (notifications), middleware (auth, rate-limit, error, logging), config, services (darkwatch, spamshield, voiceprint), tests - packages/web: new SolidJS web app stub - packages/mobile: new SolidJS mobile app stub - packages/shared-db: new Prisma DB package (separate from existing packages/db) - pnpm-workspace.yaml: restored (apps/* removed, already covered by packages/*) Next: reconcile packages/shared-db with packages/db, and fix server.ts correlationRoutes import
This commit is contained in:
97
packages/api/src/services/darkwatch/watchlist.service.ts
Normal file
97
packages/api/src/services/darkwatch/watchlist.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { prisma, WatchlistType } from '@shieldsai/shared-db';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export function normalizeValue(type: WatchlistType, value: string): string {
|
||||
const trimmed = value.trim().toLowerCase();
|
||||
switch (type) {
|
||||
case WatchlistType.email:
|
||||
return trimmed.replace(/\s+/g, '');
|
||||
case WatchlistType.phoneNumber:
|
||||
return trimmed.replace(/[\s\-\(\)]/g, '');
|
||||
case WatchlistType.ssn:
|
||||
return trimmed.replace(/-/g, '');
|
||||
case WatchlistType.address:
|
||||
return trimmed;
|
||||
case WatchlistType.domain:
|
||||
return trimmed.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
|
||||
default:
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
export function hashValue(value: string): string {
|
||||
return createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
|
||||
export class WatchlistService {
|
||||
async addItem(
|
||||
subscriptionId: string,
|
||||
type: WatchlistType,
|
||||
value: string,
|
||||
maxItems: number
|
||||
) {
|
||||
const normalized = normalizeValue(type, value);
|
||||
const itemHash = hashValue(normalized);
|
||||
|
||||
const currentCount = await prisma.watchlistItem.count({
|
||||
where: { subscriptionId, isActive: true },
|
||||
});
|
||||
|
||||
if (currentCount >= maxItems) {
|
||||
throw new Error(
|
||||
`Watchlist limit reached (${maxItems} items). Upgrade your plan to add more.`
|
||||
);
|
||||
}
|
||||
|
||||
const existing = await prisma.watchlistItem.findFirst({
|
||||
where: { subscriptionId, type, hash: itemHash },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
if (!existing.isActive) {
|
||||
return prisma.watchlistItem.update({
|
||||
where: { id: existing.id },
|
||||
data: { isActive: true },
|
||||
});
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
return prisma.watchlistItem.create({
|
||||
data: {
|
||||
subscriptionId,
|
||||
type,
|
||||
value: normalized,
|
||||
hash: itemHash,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getItems(subscriptionId: string) {
|
||||
return prisma.watchlistItem.findMany({
|
||||
where: { subscriptionId, isActive: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async removeItem(id: string, subscriptionId: string) {
|
||||
return prisma.watchlistItem.update({
|
||||
where: { id },
|
||||
data: { isActive: false },
|
||||
});
|
||||
}
|
||||
|
||||
async getActiveItemsForScan(subscriptionId: string) {
|
||||
return prisma.watchlistItem.findMany({
|
||||
where: { subscriptionId, isActive: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getItemCount(subscriptionId: string) {
|
||||
return prisma.watchlistItem.count({
|
||||
where: { subscriptionId, isActive: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const watchlistService = new WatchlistService();
|
||||
Reference in New Issue
Block a user