Add MixpanelService with hashed phoneNumber in spamBlocked() (FRE-4519)
Create MixpanelService that uses FieldEncryptionService.hashPhoneNumber() to SHA-256 hash phone numbers before sending to Mixpanel analytics. - Implement spamBlocked() method with phone number hashing - Add 16 unit tests verifying hash correctness and API behavior - Export service from package index Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export * from './services/spamshield.service';
|
||||
export * from './services/mixpanel.service';
|
||||
export * from './circuit-breaker';
|
||||
export * from './config/spamshield.config';
|
||||
export * from './utils/phone-validation';
|
||||
|
||||
116
services/spamshield/src/services/mixpanel.service.ts
Normal file
116
services/spamshield/src/services/mixpanel.service.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { FieldEncryptionService } from '@shieldai/db';
|
||||
|
||||
export interface SpamBlockedEvent {
|
||||
phoneNumber: string;
|
||||
decision: 'BLOCK' | 'FLAG';
|
||||
confidence: number;
|
||||
ruleMatches?: string[];
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface MixpanelEventProperties {
|
||||
$event_name: string;
|
||||
phoneNumberHash: string;
|
||||
decision: 'BLOCK' | 'FLAG';
|
||||
confidence: number;
|
||||
ruleMatches?: string[];
|
||||
timestamp: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface MixpanelConfig {
|
||||
token: string;
|
||||
apiHost: string;
|
||||
enableLogging?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: Required<MixpanelConfig> = {
|
||||
token: process.env.MIXPANEL_TOKEN || '',
|
||||
apiHost: 'api.mixpanel.com',
|
||||
enableLogging: true,
|
||||
};
|
||||
|
||||
export class MixpanelService {
|
||||
private readonly config: Required<MixpanelConfig>;
|
||||
private readonly events: MixpanelEventProperties[] = [];
|
||||
|
||||
constructor(config?: MixpanelConfig) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
async spamBlocked(event: SpamBlockedEvent): Promise<MixpanelEventProperties> {
|
||||
const phoneNumberHash = FieldEncryptionService.hashPhoneNumber(event.phoneNumber);
|
||||
|
||||
const properties: MixpanelEventProperties = {
|
||||
$event_name: 'spam_blocked',
|
||||
phoneNumberHash,
|
||||
decision: event.decision,
|
||||
confidence: event.confidence,
|
||||
ruleMatches: event.ruleMatches,
|
||||
timestamp: event.timestamp.toISOString(),
|
||||
};
|
||||
|
||||
this.events.push(properties);
|
||||
|
||||
if (this.config.enableLogging) {
|
||||
console.log(
|
||||
`[Mixpanel] Event: spam_blocked, phoneNumberHash: ${phoneNumberHash}, decision: ${event.decision}`
|
||||
);
|
||||
}
|
||||
|
||||
const response = await this.track('spam_blocked', properties);
|
||||
|
||||
return {
|
||||
...properties,
|
||||
...response,
|
||||
};
|
||||
}
|
||||
|
||||
async track(eventName: string, properties: Record<string, any>): Promise<Record<string, any>> {
|
||||
const url = `https://${this.config.apiHost}/track`;
|
||||
|
||||
const payload = {
|
||||
event: eventName,
|
||||
properties: {
|
||||
...properties,
|
||||
$token: this.config.token,
|
||||
time: properties.timestamp ? new Date(properties.timestamp).getTime() / 1000 : Date.now() / 1000,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ data: JSON.stringify(payload) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (this.config.enableLogging) {
|
||||
console.log(`[Mixpanel] Track failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { status: response.status };
|
||||
} catch (error) {
|
||||
if (this.config.enableLogging) {
|
||||
console.log(`[Mixpanel] Track error:`, error);
|
||||
}
|
||||
return { status: 0, error: String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
getEvents(): MixpanelEventProperties[] {
|
||||
return [...this.events];
|
||||
}
|
||||
|
||||
clearEvents(): void {
|
||||
this.events.length = 0;
|
||||
}
|
||||
|
||||
getConfig(): Required<MixpanelConfig> {
|
||||
return { ...this.config };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user