FRE-4529: Transfer ShieldAI code from FrenoCorp repo

Transferred ShieldAI-related files mistakenly placed in ~/code/FrenoCorp:
- Services: spamshield (feature-flags, audit-logger, error-handler), voiceprint (config, service, feature-flags), darkwatch (pipeline, scan, scheduler, watchlist, webhook)
- Packages: shared-analytics, shared-auth, shared-ui, shared-utils (new); shared-billing, jobs supplemented with unique FC files
- Server: alerts (FC version newer), routes (spamshield, darkwatch, voiceprint)
- Config: turbo.json, tsconfig.base.json, vite/vitest configs, drizzle, Dockerfile
- VoicePrint ML service
- Examples

Pending: apps/{api,web,mobile}/ structured merge, shared-db/db mapping

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-02 10:13:13 -04:00
parent 8687868632
commit 1e42c4a5c2
45 changed files with 4837 additions and 562 deletions

View File

@@ -1,94 +1,257 @@
import { FastifyInstance } from "fastify";
import { VoiceEnrollmentService } from "@shieldai/voiceprint";
import { AnalysisService } from "@shieldai/voiceprint";
import { BatchAnalysisService } from "@shieldai/voiceprint";
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import {
voiceEnrollmentService,
analysisService,
batchAnalysisService,
voicePrintEnv,
AnalysisJobStatus,
} from '../services/voiceprint';
export function voiceprintRoutes(fastify: FastifyInstance) {
const enrollmentService = new VoiceEnrollmentService();
const analysisService = new AnalysisService();
const batchService = new BatchAnalysisService();
export async function voiceprintRoutes(fastify: FastifyInstance) {
// Enroll a new voice profile
fastify.post('/enroll', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
fastify.post("/enroll", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
const body = request.body as { label: string; audio: string; sampleRate?: number };
const audioBuffer = Buffer.from(body.audio, "base64");
const enrollment = await enrollmentService.enroll(
{ label: body.label, audioBuffer, sampleRate: body.sampleRate },
userId
);
return reply.code(201).send(enrollment);
});
fastify.get("/enrollments", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
const enrollments = await enrollmentService.listEnrollments(userId);
return reply.send(enrollments);
});
fastify.delete("/enrollments/:id", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
const enrollmentId = (request.params as { id: string }).id;
const result = await enrollmentService.removeEnrollment(userId, enrollmentId);
return reply.send({ removed: result });
});
fastify.post("/analyze", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
const body = request.body as { audio: string; sampleRate?: number; analysisType?: string };
const audioBuffer = Buffer.from(body.audio, "base64");
const result = await analysisService.analyze(
{ audioBuffer, sampleRate: body.sampleRate, analysisType: body.analysisType },
userId
);
return reply.code(201).send(result);
});
fastify.get("/results/:id", async (request, reply) => {
const jobId = (request.params as { id: string }).id;
const result = await analysisService.getResult(jobId);
if (!result) return reply.code(404).send({ error: "Analysis result not found" });
return reply.send(result);
});
fastify.get("/results", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
const limit = parseInt((request.query as { limit?: string }).limit || "20", 10);
const results = await analysisService.getUserResults(userId, limit);
return reply.send(results);
});
fastify.post("/batch", async (request, reply) => {
const userId = (request.user as { id: string })?.id;
if (!userId) return reply.code(401).send({ error: "User not authenticated" });
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const body = request.body as {
files: Array<{ name: string; audio: string; sampleRate?: number }>;
analysisType?: string;
name: string;
audio: Buffer;
};
const audioBuffers = body.files.map((f) => ({
name: f.name,
buffer: Buffer.from(f.audio, "base64"),
sampleRate: f.sampleRate,
}));
if (!body.name || !body.audio) {
return reply.code(400).send({ error: 'name and audio are required' });
}
const result = await batchService.analyzeBatch(
{ audioBuffers, analysisType: body.analysisType },
userId
);
return reply.code(201).send(result);
try {
const enrollment = await voiceEnrollmentService.enroll(
userId,
body.name,
body.audio
);
return reply.code(201).send({
enrollment: {
id: enrollment.id,
name: enrollment.name,
isActive: enrollment.isActive,
createdAt: enrollment.createdAt,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Enrollment failed';
return reply.code(422).send({ error: message });
}
});
// List user's voice enrollments
fastify.get('/enrollments', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const isActive = request.query as { isActive?: string };
const limit = request.query as { limit?: string };
const offset = request.query as { offset?: string };
const enrollments = await voiceEnrollmentService.listEnrollments(userId, {
isActive: isActive.isActive !== undefined
? isActive.isActive === 'true'
: undefined,
limit: limit.limit ? parseInt(limit.limit, 10) : undefined,
offset: offset.offset ? parseInt(offset.offset, 10) : undefined,
});
return reply.send({
enrollments: enrollments.map((e) => ({
id: e.id,
name: e.name,
isActive: e.isActive,
createdAt: e.createdAt,
})),
});
});
// Remove an enrollment
fastify.delete('/enrollments/:id', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const enrollmentId = (request.params as { id: string }).id;
try {
const enrollment = await voiceEnrollmentService.removeEnrollment(
enrollmentId,
userId
);
return reply.send({
enrollment: {
id: enrollment.id,
name: enrollment.name,
isActive: enrollment.isActive,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Removal failed';
return reply.code(404).send({ error: message });
}
});
// Analyze a single audio file
fastify.post('/analyze', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const body = request.body as {
audio: Buffer;
enrollmentId?: string;
audioUrl?: string;
};
if (!body.audio) {
return reply.code(400).send({ error: 'audio is required' });
}
try {
const result = await analysisService.analyze(userId, body.audio, {
enrollmentId: body.enrollmentId,
audioUrl: body.audioUrl,
});
return reply.code(201).send({
analysis: {
id: result.id,
isSynthetic: result.isSynthetic,
confidence: result.confidence,
analysisResult: result.analysisResult,
createdAt: result.createdAt,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Analysis failed';
return reply.code(422).send({ error: message });
}
});
// Get analysis result by ID
fastify.get('/results/:id', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const analysisId = (request.params as { id: string }).id;
const result = await analysisService.getResult(analysisId, userId);
if (!result) {
return reply.code(404).send({ error: 'Analysis not found' });
}
return reply.send({
analysis: {
id: result.id,
isSynthetic: result.isSynthetic,
confidence: result.confidence,
analysisResult: result.analysisResult,
createdAt: result.createdAt,
},
});
});
// Get analysis history
fastify.get('/history', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const query = request.query as {
limit?: string;
offset?: string;
isSynthetic?: string;
};
const results = await analysisService.getHistory(userId, {
limit: query.limit ? parseInt(query.limit, 10) : undefined,
offset: query.offset ? parseInt(query.offset, 10) : undefined,
isSynthetic: query.isSynthetic !== undefined
? query.isSynthetic === 'true'
: undefined,
});
return reply.send({
analyses: results.map((r) => ({
id: r.id,
isSynthetic: r.isSynthetic,
confidence: r.confidence,
createdAt: r.createdAt,
})),
});
});
// Batch analyze multiple audio files
fastify.post('/batch', async (request: FastifyRequest, reply: FastifyReply) => {
const authReq = request as FastifyRequest & { user?: { id: string } };
const userId = authReq.user?.id;
if (!userId) {
return reply.code(401).send({ error: 'User ID required' });
}
const body = request.body as {
files: Array<{
name: string;
audio: Buffer;
audioUrl?: string;
}>;
enrollmentId?: string;
};
if (!body.files || body.files.length === 0) {
return reply.code(400).send({ error: 'files array is required' });
}
try {
const result = await batchAnalysisService.analyzeBatch(
userId,
body.files.map((f) => ({
name: f.name,
buffer: f.audio,
audioUrl: f.audioUrl,
})),
{
enrollmentId: body.enrollmentId,
}
);
return reply.code(201).send({
jobId: result.jobId,
results: result.results.map((r) => ({
id: r.id,
isSynthetic: r.isSynthetic,
confidence: r.confidence,
})),
summary: result.summary,
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Batch analysis failed';
return reply.code(422).send({ error: message });
}
});
}