Implementing a link tracking and user click logging system.Implementing a link tracking and user click logging system.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
-- Extend AutomationTrigger
|
||||
CREATE TYPE "AutomationTrigger_new" AS ENUM (
|
||||
'person_created',
|
||||
'lead_submitted',
|
||||
'deal_created',
|
||||
'deal_stage_changed',
|
||||
'deal_won',
|
||||
'deal_lost',
|
||||
'ticket_created',
|
||||
'ticket_resolved',
|
||||
'invoice_issued',
|
||||
'invoice_paid',
|
||||
'activity_done',
|
||||
'sms_link_clicked'
|
||||
);
|
||||
|
||||
ALTER TABLE "AutomationRule"
|
||||
ALTER COLUMN "trigger" TYPE "AutomationTrigger_new"
|
||||
USING ("trigger"::text::"AutomationTrigger_new");
|
||||
|
||||
ALTER TYPE "AutomationTrigger" RENAME TO "AutomationTrigger_old";
|
||||
ALTER TYPE "AutomationTrigger_new" RENAME TO "AutomationTrigger";
|
||||
DROP TYPE "AutomationTrigger_old";
|
||||
|
||||
-- Extend MessageAutomationTrigger
|
||||
CREATE TYPE "MessageAutomationTrigger_new" AS ENUM (
|
||||
'person_created',
|
||||
'lead_submitted',
|
||||
'deal_created',
|
||||
'deal_stage_changed',
|
||||
'deal_won',
|
||||
'deal_lost',
|
||||
'ticket_created',
|
||||
'ticket_resolved',
|
||||
'invoice_issued',
|
||||
'invoice_paid',
|
||||
'activity_created',
|
||||
'activity_done',
|
||||
'sms_link_clicked'
|
||||
);
|
||||
|
||||
ALTER TABLE "MessageAutomationRule"
|
||||
ALTER COLUMN "trigger" TYPE "MessageAutomationTrigger_new"
|
||||
USING ("trigger"::text::"MessageAutomationTrigger_new");
|
||||
|
||||
ALTER TABLE "MessageAutomationJob"
|
||||
ALTER COLUMN "trigger" TYPE "MessageAutomationTrigger_new"
|
||||
USING ("trigger"::text::"MessageAutomationTrigger_new");
|
||||
|
||||
ALTER TYPE "MessageAutomationTrigger" RENAME TO "MessageAutomationTrigger_old";
|
||||
ALTER TYPE "MessageAutomationTrigger_new" RENAME TO "MessageAutomationTrigger";
|
||||
DROP TYPE "MessageAutomationTrigger_old";
|
||||
|
||||
-- Recipient: optional leadId for future Lead campaigns
|
||||
ALTER TABLE "SmsCampaignRecipient" ADD COLUMN "leadId" TEXT;
|
||||
CREATE INDEX "SmsCampaignRecipient_personId_idx" ON "SmsCampaignRecipient"("personId");
|
||||
CREATE INDEX "SmsCampaignRecipient_leadId_idx" ON "SmsCampaignRecipient"("leadId");
|
||||
|
||||
-- TrackingLink
|
||||
CREATE TABLE "TrackingLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"originalUrl" TEXT NOT NULL,
|
||||
"channel" TEXT NOT NULL DEFAULT 'sms',
|
||||
"sourceType" TEXT NOT NULL,
|
||||
"sourceId" TEXT NOT NULL,
|
||||
"recipientType" TEXT,
|
||||
"recipientId" TEXT,
|
||||
"smsRecipientId" TEXT,
|
||||
"personId" TEXT,
|
||||
"leadId" TEXT,
|
||||
"clickCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"firstClickedAt" TIMESTAMP(3),
|
||||
"lastClickedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TrackingLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "TrackingLink_token_key" ON "TrackingLink"("token");
|
||||
CREATE INDEX "TrackingLink_organizationId_sourceType_sourceId_idx" ON "TrackingLink"("organizationId", "sourceType", "sourceId");
|
||||
CREATE INDEX "TrackingLink_organizationId_personId_idx" ON "TrackingLink"("organizationId", "personId");
|
||||
CREATE INDEX "TrackingLink_organizationId_leadId_idx" ON "TrackingLink"("organizationId", "leadId");
|
||||
CREATE INDEX "TrackingLink_smsRecipientId_idx" ON "TrackingLink"("smsRecipientId");
|
||||
CREATE INDEX "TrackingLink_token_idx" ON "TrackingLink"("token");
|
||||
|
||||
ALTER TABLE "TrackingLink" ADD CONSTRAINT "TrackingLink_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingLink" ADD CONSTRAINT "TrackingLink_smsRecipientId_fkey" FOREIGN KEY ("smsRecipientId") REFERENCES "SmsCampaignRecipient"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingLink" ADD CONSTRAINT "TrackingLink_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingLink" ADD CONSTRAINT "TrackingLink_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "Lead"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- TrackingEvent
|
||||
CREATE TABLE "TrackingEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"trackingLinkId" TEXT,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"channel" TEXT NOT NULL DEFAULT 'sms',
|
||||
"sourceType" TEXT,
|
||||
"sourceId" TEXT,
|
||||
"recipientType" TEXT,
|
||||
"recipientId" TEXT,
|
||||
"personId" TEXT,
|
||||
"leadId" TEXT,
|
||||
"occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"metadata" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TrackingEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "TrackingEvent_organizationId_eventType_occurredAt_idx" ON "TrackingEvent"("organizationId", "eventType", "occurredAt");
|
||||
CREATE INDEX "TrackingEvent_organizationId_sourceType_sourceId_eventType_idx" ON "TrackingEvent"("organizationId", "sourceType", "sourceId", "eventType");
|
||||
CREATE INDEX "TrackingEvent_organizationId_personId_eventType_idx" ON "TrackingEvent"("organizationId", "personId", "eventType");
|
||||
CREATE INDEX "TrackingEvent_organizationId_leadId_eventType_idx" ON "TrackingEvent"("organizationId", "leadId", "eventType");
|
||||
CREATE INDEX "TrackingEvent_trackingLinkId_occurredAt_idx" ON "TrackingEvent"("trackingLinkId", "occurredAt");
|
||||
|
||||
ALTER TABLE "TrackingEvent" ADD CONSTRAINT "TrackingEvent_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingEvent" ADD CONSTRAINT "TrackingEvent_trackingLinkId_fkey" FOREIGN KEY ("trackingLinkId") REFERENCES "TrackingLink"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingEvent" ADD CONSTRAINT "TrackingEvent_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "TrackingEvent" ADD CONSTRAINT "TrackingEvent_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "Lead"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -119,6 +119,7 @@ enum AutomationTrigger {
|
||||
invoice_issued
|
||||
invoice_paid
|
||||
activity_done
|
||||
sms_link_clicked
|
||||
}
|
||||
|
||||
enum AutomationAction {
|
||||
@@ -215,6 +216,8 @@ model Organization {
|
||||
conversationRoomMembers ConversationRoomMember[]
|
||||
conversationRoomUserMutes ConversationRoomUserMute[]
|
||||
conversationMessageReactions ConversationMessageReaction[]
|
||||
trackingLinks TrackingLink[]
|
||||
trackingEvents TrackingEvent[]
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -455,6 +458,8 @@ model Person {
|
||||
accountingDocuments AccountingDocumentLog[]
|
||||
openingBalances PartyOpeningBalance[]
|
||||
cheques Cheque[]
|
||||
trackingLinks TrackingLink[]
|
||||
trackingEvents TrackingEvent[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -1306,6 +1311,7 @@ enum MessageAutomationTrigger {
|
||||
invoice_paid
|
||||
activity_created
|
||||
activity_done
|
||||
sms_link_clicked
|
||||
}
|
||||
|
||||
enum MessageAutomationDelayUnit {
|
||||
@@ -1787,14 +1793,84 @@ model SmsCampaignRecipient {
|
||||
campaignId String
|
||||
campaign SmsCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
||||
personId String?
|
||||
/// برای پشتیبانی آینده از Lead (فعلاً کمپین روی Person است)
|
||||
leadId String?
|
||||
mobile String?
|
||||
/// pending | sent | failed | skipped
|
||||
status String @default("pending")
|
||||
error String?
|
||||
sentAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
trackingLinks TrackingLink[]
|
||||
|
||||
@@index([campaignId, status])
|
||||
@@index([personId])
|
||||
@@index([leadId])
|
||||
}
|
||||
|
||||
/// لینک ردیابی یکتا — کانالخنثی (SMS / Email / …)
|
||||
model TrackingLink {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
/// توکن امن و غیرقابلحدس (بدون PII)
|
||||
token String @unique
|
||||
originalUrl String
|
||||
/// sms | email | whatsapp | notification | other
|
||||
channel String @default("sms")
|
||||
/// sms_campaign | …
|
||||
sourceType String
|
||||
sourceId String
|
||||
/// sms_campaign_recipient | …
|
||||
recipientType String?
|
||||
recipientId String?
|
||||
smsRecipientId String?
|
||||
smsRecipient SmsCampaignRecipient? @relation(fields: [smsRecipientId], references: [id], onDelete: SetNull)
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
||||
leadId String?
|
||||
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
||||
clickCount Int @default(0)
|
||||
firstClickedAt DateTime?
|
||||
lastClickedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
events TrackingEvent[]
|
||||
|
||||
@@index([organizationId, sourceType, sourceId])
|
||||
@@index([organizationId, personId])
|
||||
@@index([organizationId, leadId])
|
||||
@@index([smsRecipientId])
|
||||
@@index([token])
|
||||
}
|
||||
|
||||
/// رویداد رفتاری عمومی — آمادهٔ Automation
|
||||
model TrackingEvent {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
trackingLinkId String?
|
||||
trackingLink TrackingLink? @relation(fields: [trackingLinkId], references: [id], onDelete: SetNull)
|
||||
/// مثلاً sms_link_clicked | sms_delivered | sms_failed | …
|
||||
eventType String
|
||||
channel String @default("sms")
|
||||
sourceType String?
|
||||
sourceId String?
|
||||
recipientType String?
|
||||
recipientId String?
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
||||
leadId String?
|
||||
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
||||
occurredAt DateTime @default(now())
|
||||
/// ua / referrer / ipHash (اختیاری، بدون PII خام)
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([organizationId, eventType, occurredAt])
|
||||
@@index([organizationId, sourceType, sourceId, eventType])
|
||||
@@index([organizationId, personId, eventType])
|
||||
@@index([organizationId, leadId, eventType])
|
||||
@@index([trackingLinkId, occurredAt])
|
||||
}
|
||||
|
||||
/// اتصال فروشگاه ووکامرس
|
||||
@@ -2009,6 +2085,8 @@ model Lead {
|
||||
convertedAt DateTime?
|
||||
archivedAt DateTime?
|
||||
events LeadEvent[]
|
||||
trackingLinks TrackingLink[]
|
||||
trackingEvents TrackingEvent[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
||||
Reference in New Issue
Block a user