feat: add SpamShield router for spam detection and call analysis

- Create tRPC router with checkNumber, classifySMS, classifyCall endpoints
- Add protected procedures for rule CRUD, feedback submission, and stats
- Implement service layer with phone number normalization and audit logging
- Add ML engine with BERT stub, feature extraction, and rule engine
- Implement reputation API module with circuit breaker and caching
- Write comprehensive tests (34 tests) for all layers
- Wire spamshield router into app root router
This commit is contained in:
2026-05-25 16:34:08 -04:00
parent e6b07ddf1d
commit fc9a5c4fb2
10 changed files with 1098 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import { wrap } from "@typeschema/valibot";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "../utils";
import {
CheckNumberSchema,
ClassifySMSSchema,
ClassifyCallSchema,
CreateRuleSchema,
DeleteRuleSchema,
FeedbackSchema,
StatsFilterSchema,
} from "../schemas/spamshield";
import * as spamshieldService from "~/server/services/spamshield.service";
export const spamshieldRouter = createTRPCRouter({
checkNumber: publicProcedure
.input(wrap(CheckNumberSchema))
.query(async ({ input }) => {
return spamshieldService.checkNumberReputation(input.phoneNumber);
}),
classifySMS: publicProcedure
.input(wrap(ClassifySMSSchema))
.query(async ({ input }) => {
return spamshieldService.classifySMS(input.text);
}),
classifyCall: publicProcedure
.input(wrap(ClassifyCallSchema))
.query(async ({ input }) => {
return spamshieldService.classifyCall(
input.callerNumber,
input.duration,
input.timeOfDay,
);
}),
getRules: protectedProcedure.query(async ({ ctx }) => {
return spamshieldService.getRules(ctx.user.id);
}),
createRule: protectedProcedure
.input(wrap(CreateRuleSchema))
.mutation(async ({ ctx, input }) => {
return spamshieldService.createRule(
ctx.user.id,
input.ruleType,
input.pattern,
input.action,
input.priority,
);
}),
deleteRule: protectedProcedure
.input(wrap(DeleteRuleSchema))
.mutation(async ({ ctx, input }) => {
return spamshieldService.deleteRule(ctx.user.id, input.ruleId);
}),
submitFeedback: protectedProcedure
.input(wrap(FeedbackSchema))
.mutation(async ({ ctx, input }) => {
return spamshieldService.submitFeedback(
ctx.user.id,
input.phoneNumber,
input.isSpam,
input.feedbackType,
);
}),
getStats: protectedProcedure
.input(wrap(StatsFilterSchema))
.query(async ({ ctx, input }) => {
return spamshieldService.getStats(ctx.user.id, input.period);
}),
});