feat: add report material workflows and gateway safeguards
This commit is contained in:
@@ -372,11 +372,10 @@ export class OperationsService {
|
||||
async listGatewaySubmitDeadLetters(query: GatewaySubmitDeadLetterQuery) {
|
||||
const page = Math.max(1, Number(query.page ?? 1));
|
||||
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize ?? 10)));
|
||||
const where: Prisma.GatewaySubmitDeadLetterWhereInput = {
|
||||
const baseWhere: Prisma.GatewaySubmitDeadLetterWhereInput = {
|
||||
tenantId: query.tenantId,
|
||||
applicationId: query.applicationId,
|
||||
channelId: query.channelId,
|
||||
status: query.status && query.status !== 'all' ? query.status : undefined,
|
||||
OR: query.keyword ? [
|
||||
{ streamMessageId: { contains: query.keyword } },
|
||||
{ traceId: { contains: query.keyword } },
|
||||
@@ -386,7 +385,11 @@ export class OperationsService {
|
||||
{ failureMessage: { contains: query.keyword } },
|
||||
] : undefined,
|
||||
};
|
||||
const [items, total] = await Promise.all([
|
||||
const where: Prisma.GatewaySubmitDeadLetterWhereInput = {
|
||||
...baseWhere,
|
||||
status: query.status && query.status !== 'all' ? query.status : undefined,
|
||||
};
|
||||
const [items, total, statusGroups, oldestPending] = await Promise.all([
|
||||
this.prisma.gatewaySubmitDeadLetter.findMany({
|
||||
where,
|
||||
include: { tenant: true, application: true, channel: true },
|
||||
@@ -395,8 +398,39 @@ export class OperationsService {
|
||||
take: pageSize,
|
||||
}),
|
||||
this.prisma.gatewaySubmitDeadLetter.count({ where }),
|
||||
this.prisma.gatewaySubmitDeadLetter.groupBy({
|
||||
by: ['status'],
|
||||
where: baseWhere,
|
||||
_count: { _all: true },
|
||||
}),
|
||||
this.prisma.gatewaySubmitDeadLetter.findFirst({
|
||||
where: { ...baseWhere, status: 'pending' },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: { createdAt: true },
|
||||
}),
|
||||
]);
|
||||
return { items, total, page, pageSize };
|
||||
const statusCounts = new Map(statusGroups.map((item) => [item.status, item._count._all]));
|
||||
const messageIds = items.map((item) => item.messageId).filter((value): value is string => Boolean(value));
|
||||
const messageStates = messageIds.length > 0
|
||||
? await this.prisma.smsMessageRecord.findMany({
|
||||
where: { messageId: { in: messageIds } },
|
||||
select: { messageId: true, status: true, submitStatus: true, receiptStatus: true, phoneNumber: true, content: true },
|
||||
})
|
||||
: [];
|
||||
const messageStateById = new Map(messageStates.map((item) => [item.messageId, item]));
|
||||
return {
|
||||
items: items.map((item) => sanitizeGatewaySubmitException(item, item.messageId ? messageStateById.get(item.messageId) : undefined)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
summary: {
|
||||
pending: statusCounts.get('pending') ?? 0,
|
||||
requeueing: statusCounts.get('requeueing') ?? 0,
|
||||
requeued: statusCounts.get('requeued') ?? 0,
|
||||
resolved: statusCounts.get('resolved') ?? 0,
|
||||
oldestPendingAt: oldestPending?.createdAt ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async listDownstreamDeliveries(query: DownstreamDeliveryQuery) {
|
||||
@@ -1119,3 +1153,47 @@ function normalizeOperationLog(log: Prisma.OperationLogGetPayload<{ include: { t
|
||||
userAgent: log.userAgent ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizeGatewaySubmitException(
|
||||
item: Prisma.GatewaySubmitDeadLetterGetPayload<{ include: { tenant: true; application: true; channel: true } }>,
|
||||
messageState?: { status: string; submitStatus: string | null; receiptStatus: string | null; phoneNumber: string; content: string },
|
||||
) {
|
||||
const { rawPayload, commandPayload, tenant, application, channel, ...record } = item;
|
||||
return {
|
||||
...record,
|
||||
tenant: tenant ? { id: tenant.id, name: tenant.name, code: tenant.code, status: tenant.status } : null,
|
||||
application: application ? { id: application.id, tenantId: application.tenantId, name: application.name, status: application.status } : null,
|
||||
channel: channel ? {
|
||||
id: channel.id,
|
||||
code: channel.code,
|
||||
name: channel.name,
|
||||
status: channel.status,
|
||||
carrier: channel.carrier,
|
||||
sendRegion: channel.sendRegion,
|
||||
rateLimitPerSecond: channel.rateLimitPerSecond,
|
||||
} : null,
|
||||
rawPayloadAvailable: Boolean(rawPayload),
|
||||
commandPayload: redactGatewayCommandValue(commandPayload),
|
||||
messageState: messageState ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function redactGatewayCommandValue(value: Prisma.JsonValue | null): Prisma.JsonValue | null {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => redactGatewayCommandValue(item));
|
||||
}
|
||||
if (value && typeof value === 'object') {
|
||||
const redacted: Record<string, Prisma.JsonValue | null> = {};
|
||||
for (const [key, child] of Object.entries(value)) {
|
||||
const normalizedKey = key.toLowerCase();
|
||||
redacted[key] = [
|
||||
'password', 'passwordcipher', 'secret', 'secrethash', 'authsource',
|
||||
'token', 'apikey', 'accesskey', 'secretkey',
|
||||
].includes(normalizedKey)
|
||||
? '[REDACTED]'
|
||||
: redactGatewayCommandValue(child as Prisma.JsonValue);
|
||||
}
|
||||
return redacted;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user