Fix review findings for info broker removal service FRE-5402
P0 fixes: - Add CANCELLED status to RemovalStatus enum (types + Prisma schema) - Use CANCELLED instead of REJECTED for user-initiated cancellations - Add null guard for req.broker?.name in GET /request/:id - Remove unsafe 'as any' casts in RemoveBrokersService.ts - Add type-safe toPersonalInfo() validator for JSON deserialization - Type RemovalRequestWithBroker properly in getRemovalStatus() - Fix alert: any to NormalizedAlertInput in BrokerAlertPipeline P1 fixes: - Fix admin role check: remove non-existent 'admin', only check 'support' - Fix BrokerDefinition.category type from string to BrokerCategory - Add complete OpenAPI spec for all removebrokers routes and schemas
This commit is contained in:
3629
packages/api/src/openapi/spec.json
Normal file
3629
packages/api/src/openapi/spec.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -270,7 +270,7 @@ export async function removebrokersRoutes(fastify: FastifyInstance) {
|
|||||||
request: {
|
request: {
|
||||||
id: req.id,
|
id: req.id,
|
||||||
brokerId: req.brokerId,
|
brokerId: req.brokerId,
|
||||||
brokerName: req.broker.name,
|
brokerName: req.broker?.name || null,
|
||||||
status: req.status,
|
status: req.status,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
attempts: req.attempts,
|
attempts: req.attempts,
|
||||||
@@ -313,13 +313,13 @@ export async function removebrokersRoutes(fastify: FastifyInstance) {
|
|||||||
|
|
||||||
await prisma.removalRequest.update({
|
await prisma.removalRequest.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { status: RemovalStatus.REJECTED },
|
data: { status: RemovalStatus.CANCELLED },
|
||||||
});
|
});
|
||||||
|
|
||||||
return reply.send({
|
return reply.send({
|
||||||
request: {
|
request: {
|
||||||
id: req.id,
|
id: req.id,
|
||||||
status: RemovalStatus.REJECTED,
|
status: RemovalStatus.CANCELLED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -335,7 +335,7 @@ export async function removebrokersRoutes(fastify: FastifyInstance) {
|
|||||||
return reply.code(401).send({ error: 'User not authenticated' });
|
return reply.code(401).send({ error: 'User not authenticated' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authReq.user.role !== 'admin' && authReq.user.role !== 'support') {
|
if (authReq.user.role !== 'support') {
|
||||||
return reply.code(403).send({ error: 'Admin access required' });
|
return reply.code(403).send({ error: 'Admin access required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -838,6 +838,7 @@ enum RemovalStatus {
|
|||||||
COMPLETED
|
COMPLETED
|
||||||
FAILED
|
FAILED
|
||||||
REJECTED
|
REJECTED
|
||||||
|
CANCELLED
|
||||||
}
|
}
|
||||||
|
|
||||||
model InfoBroker {
|
model InfoBroker {
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ export const RemovalStatus = {
|
|||||||
COMPLETED: "COMPLETED",
|
COMPLETED: "COMPLETED",
|
||||||
FAILED: "FAILED",
|
FAILED: "FAILED",
|
||||||
REJECTED: "REJECTED",
|
REJECTED: "REJECTED",
|
||||||
|
CANCELLED: "CANCELLED",
|
||||||
} as const;
|
} as const;
|
||||||
export type RemovalStatus = (typeof RemovalStatus)[keyof typeof RemovalStatus];
|
export type RemovalStatus = (typeof RemovalStatus)[keyof typeof RemovalStatus];
|
||||||
|
|
||||||
@@ -416,7 +417,7 @@ export interface BrokerDefinition {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
category: string;
|
category: BrokerCategory;
|
||||||
removalMethod: RemovalMethod;
|
removalMethod: RemovalMethod;
|
||||||
removalUrl?: string;
|
removalUrl?: string;
|
||||||
requiresAccount: boolean;
|
requiresAccount: boolean;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AlertSource, AlertCategory, Severity, EntityType } from "@shieldai/types";
|
import { AlertSource, AlertCategory, Severity, EntityType, NormalizedAlertInput } from "@shieldai/types";
|
||||||
|
|
||||||
export interface BrokerAlertInput {
|
export interface BrokerAlertInput {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -55,7 +55,7 @@ export class BrokerAlertPipeline {
|
|||||||
return this.normalizeAndSend(alert);
|
return this.normalizeAndSend(alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async normalizeAndSend(alert: any) {
|
private async normalizeAndSend(alert: NormalizedAlertInput) {
|
||||||
try {
|
try {
|
||||||
const { correlationPipeline } = await import("@shieldai/correlation");
|
const { correlationPipeline } = await import("@shieldai/correlation");
|
||||||
return correlationPipeline.normalizeAlert(alert);
|
return correlationPipeline.normalizeAlert(alert);
|
||||||
|
|||||||
@@ -3,6 +3,34 @@ import { RemovalStatus, RemovalMethod } from "@shieldai/types";
|
|||||||
import { getBrokerById, getActiveBrokers } from "./brokerRegistry";
|
import { getBrokerById, getActiveBrokers } from "./brokerRegistry";
|
||||||
import type { PersonalInfo, RemovalJob, BrokerEntry } from "./types";
|
import type { PersonalInfo, RemovalJob, BrokerEntry } from "./types";
|
||||||
import { MAX_REMOVAL_ATTEMPTS, RETRY_DELAY_MS } from "./types";
|
import { MAX_REMOVAL_ATTEMPTS, RETRY_DELAY_MS } from "./types";
|
||||||
|
import type { RemovalRequest as PrismaRemovalRequest, InfoBroker } from "@shieldai/db";
|
||||||
|
|
||||||
|
function toPersonalInfo(raw: unknown): PersonalInfo | null {
|
||||||
|
if (typeof raw !== "object" || raw === null) return null;
|
||||||
|
const obj = raw as Record<string, unknown>;
|
||||||
|
if (typeof obj.fullName !== "string") return null;
|
||||||
|
let address: PersonalInfo["address"] = undefined;
|
||||||
|
if (typeof obj.address === "object" && obj.address !== null) {
|
||||||
|
const addr = obj.address as Record<string, unknown>;
|
||||||
|
address = {
|
||||||
|
street: typeof addr.street === "string" ? addr.street : undefined,
|
||||||
|
city: typeof addr.city === "string" ? addr.city : undefined,
|
||||||
|
state: typeof addr.state === "string" ? addr.state : undefined,
|
||||||
|
zip: typeof addr.zip === "string" ? addr.zip : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fullName: obj.fullName,
|
||||||
|
email: typeof obj.email === "string" ? obj.email : undefined,
|
||||||
|
phone: typeof obj.phone === "string" ? obj.phone : undefined,
|
||||||
|
address,
|
||||||
|
dob: typeof obj.dob === "string" ? obj.dob : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemovalRequestWithBroker = PrismaRemovalRequest & {
|
||||||
|
broker: InfoBroker;
|
||||||
|
};
|
||||||
|
|
||||||
export class RemoveBrokersService {
|
export class RemoveBrokersService {
|
||||||
async scanForListings(subscriptionId: string, personalInfo: PersonalInfo) {
|
async scanForListings(subscriptionId: string, personalInfo: PersonalInfo) {
|
||||||
@@ -88,7 +116,7 @@ export class RemoveBrokersService {
|
|||||||
subscriptionId,
|
subscriptionId,
|
||||||
brokerId,
|
brokerId,
|
||||||
status: RemovalStatus.PENDING,
|
status: RemovalStatus.PENDING,
|
||||||
personalInfo: personalInfo as any,
|
personalInfo: JSON.parse(JSON.stringify(personalInfo)),
|
||||||
method: broker.removalMethod,
|
method: broker.removalMethod,
|
||||||
notes,
|
notes,
|
||||||
},
|
},
|
||||||
@@ -139,7 +167,7 @@ export class RemoveBrokersService {
|
|||||||
requestId: request.id,
|
requestId: request.id,
|
||||||
brokerId: request.brokerId,
|
brokerId: request.brokerId,
|
||||||
brokerName: getBrokerById(request.brokerId)?.name || request.brokerId,
|
brokerName: getBrokerById(request.brokerId)?.name || request.brokerId,
|
||||||
personalInfo: request.personalInfo as PersonalInfo,
|
personalInfo: toPersonalInfo(request.personalInfo)!,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
attempt: request.attempts + 1,
|
attempt: request.attempts + 1,
|
||||||
};
|
};
|
||||||
@@ -203,7 +231,10 @@ export class RemoveBrokersService {
|
|||||||
throw new Error(`Removal request not found: ${requestId}`);
|
throw new Error(`Removal request not found: ${requestId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const personalInfo = request.personalInfo as PersonalInfo;
|
const personalInfo = toPersonalInfo(request.personalInfo);
|
||||||
|
if (!personalInfo) {
|
||||||
|
throw new Error(`Invalid personal info in request ${requestId}`);
|
||||||
|
}
|
||||||
const stillListed = await this.checkBrokerListing(
|
const stillListed = await this.checkBrokerListing(
|
||||||
request.broker,
|
request.broker,
|
||||||
personalInfo,
|
personalInfo,
|
||||||
@@ -242,7 +273,7 @@ export class RemoveBrokersService {
|
|||||||
orderBy: { updatedAt: "desc" },
|
orderBy: { updatedAt: "desc" },
|
||||||
});
|
});
|
||||||
|
|
||||||
return requests.map((r: any) => ({
|
return requests.map((r: RemovalRequestWithBroker) => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
brokerId: r.brokerId,
|
brokerId: r.brokerId,
|
||||||
brokerName: r.broker.name,
|
brokerName: r.broker.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user