Files
Kordant/web/src/server/api/routers/spamshield.ts
Michael Freno c02457c66a feat: real-time alerts via WebSocket push notifications
- Add ws WebSocket server (port 3001) with JWT auth and user-socket mapping
- Add WebSocket client with exponential backoff reconnection and heartbeat
- Add useRealtimeAlerts hook with toast notifications and unread badge
- Add alert.publisher service (WS → push → email fallback)
- Integrate publisher into DarkWatch, VoicePrint, HomeTitle, SpamShield, RemoveBrokers
- Update Navbar with connection status indicator and unread count
- Add comprehensive tests (14 passing) for server, client, and publisher
2026-05-25 17:58:47 -04:00

77 lines
2.1 KiB
TypeScript

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, ctx }) => {
return spamshieldService.classifySMS(input.text, ctx.user?.id);
}),
classifyCall: publicProcedure
.input(wrap(ClassifyCallSchema))
.query(async ({ input, ctx }) => {
return spamshieldService.classifyCall(
input.callerNumber,
input.duration,
input.timeOfDay,
ctx.user?.id,
);
}),
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);
}),
});