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
98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
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();
|