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>
This commit is contained in:
2026-06-25 13:58:02 +05:30
parent e598073dc7
commit 73956fd1e3
24 changed files with 1277 additions and 225 deletions
+68 -19
View File
@@ -337,20 +337,40 @@ enum ApprovalDecision {
}
model SyncRoute {
id String @id @default(cuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
sourceGroupId String
sourceGroup Group @relation("sourceGroup", fields: [sourceGroupId], references: [id])
targetGroupId String
targetGroup Group @relation("targetGroup", fields: [targetGroupId], references: [id])
isActive Boolean @default(true)
createdAt DateTime @default(now())
id String @id @default(cuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
sourceGroupId String
sourceGroup Group @relation("sourceGroup", fields: [sourceGroupId], references: [id])
targetGroupId String
targetGroup Group @relation("targetGroup", fields: [targetGroupId], references: [id])
isActive Boolean @default(true)
forwardFormat ForwardFormat @default(THREADED)
withAttachment Boolean @default(true)
frequency ForwardFrequency @default(INSTANT)
approvalMode ApprovalMode @default(MANUAL)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignedRules SyncRouteRuleMap[]
@@unique([sourceGroupId, targetGroupId])
@@index([tenantId])
}
model SyncRouteRuleMap {
id String @id @default(cuid())
syncRouteId String
syncRoute SyncRoute @relation(fields: [syncRouteId], references: [id], onDelete: Cascade)
tenantRuleId String
tenantRule TenantRule @relation(fields: [tenantRuleId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([syncRouteId, tenantRuleId])
@@index([syncRouteId])
@@index([tenantRuleId])
}
// ============================================================================
// Group claiming + sharing
// ============================================================================
@@ -515,6 +535,7 @@ enum RuleMatchType {
HASHTAG
PREFIX
REACTION_EMOJI
INTEREST
}
enum RuleAction {
@@ -524,17 +545,45 @@ enum RuleAction {
REJECT
}
enum RuleIntent {
POSITIVE
NEGATIVE
}
enum ForwardFormat {
THREADED
DIGEST
SUMMARY
}
enum ForwardFrequency {
INSTANT
FIVE_MIN
FIFTEEN_MIN
ONE_HOUR
ONE_DAY
SEVEN_DAYS
}
enum ApprovalMode {
MANUAL
AUTO
}
model TenantRule {
id String @id @default(cuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
matchType RuleMatchType
matchValue String
action RuleAction
priority Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
matchType RuleMatchType
matchValue String
action RuleAction
ruleIntent RuleIntent @default(POSITIVE)
priority Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
syncRouteRuleMaps SyncRouteRuleMap[]
@@unique([tenantId, matchType, matchValue])
@@index([tenantId, isActive])