Files
tower/apps/worker/src/core/approve-message.ts
T
maaz519 73956fd1e3 feat: route-centric rules, approve popup, emoji picker, 5-column sync table
- Schema: SyncRouteRuleMap junction table, ruleIntent on TenantRule,
  ForwardFormat/Frequency/ApprovalMode columns on SyncRoute, INTEREST
  match type
- Worker: remove global AUTO_APPROVE forwarding; per-route rule evaluation
  in ingest processor — POSITIVE rules auto-forward that route, NEGATIVE
  rules block it, fallback to approvalMode
- API: PATCH /routes/:id, rule assign/unassign endpoints, approve now
  accepts targetGroupIds, new GET /messages/:id/routes for popup data
- Web: 5-column route table with settings + rules dialogs, ForwardTarget
  popup with rule-based pre-selection, ruleIntent toggle + emoji dropdown
  (👍/👎 only) + INTEREST type in rules manager

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 13:58:02 +05:30

89 lines
2.6 KiB
TypeScript

import { ForwardJobData, IndexJobData } from '@tower/types';
import { createLogger } from '@tower/logger';
const logger = createLogger('approve-message');
export interface ApproveMessageResult {
forwardJobs: ForwardJobData[];
indexDoc: IndexJobData;
}
/**
* Approve a PENDING message: mark APPROVED in a transaction, create an Approval row,
* and return the forward jobs + index document.
* Returns null if the message is not in PENDING state or is already approved.
*/
export async function approveMessage(
messageId: string,
tenantId: string,
adminId: string,
prisma: any,
targetGroupIds?: string[],
): Promise<ApproveMessageResult | null> {
const message = await prisma.message.findUnique({
where: { id: messageId },
include: {
approval: true,
sourceGroup: {
include: {
syncRoutesFrom: {
where: { isActive: true, targetGroup: { isActive: true } },
include: { targetGroup: { select: { platformId: true, isActive: true, accountId: true } } },
},
},
},
},
});
if (!message) return null;
if (message.status !== 'PENDING') return null;
if (message.approval) return null;
let approved = false;
await prisma.$transaction(async (tx: any) => {
const updated = await tx.message.updateMany({
where: { id: message.id, status: 'PENDING' },
data: { status: 'APPROVED' },
});
if (updated.count === 0) return;
approved = true;
await tx.approval.create({
data: {
tenantId: message.tenantId,
messageId: message.id,
adminId,
decision: 'APPROVED',
},
});
});
if (!approved) return null;
const forwardJobs: ForwardJobData[] = (message.sourceGroup?.syncRoutesFrom ?? [])
.filter((route: any) => route.targetGroup != null)
.filter((route: any) => !targetGroupIds || targetGroupIds.includes(route.targetGroupId))
.map((route: any) => ({
tenantId: message.tenantId,
messageId: message.id,
content: message.content,
sourceGroupName: message.sourceGroup.name,
senderName: message.senderName ?? undefined,
toGroupJid: route.targetGroup.platformId,
fromAccountId: route.targetGroup.accountId ?? message.sourceGroup.accountId ?? '',
}));
const indexDoc: IndexJobData = {
tenantId: message.tenantId,
messageId: message.id,
content: message.content,
senderName: message.senderName ?? null,
sourceGroupId: message.sourceGroupId,
sourceGroupName: message.sourceGroup.name,
tags: message.tags ?? [],
platform: message.platform,
approvedAt: new Date().toISOString(),
};
return { forwardJobs, indexDoc };
}